|
1 | 1 | import os |
2 | 2 | import signal |
| 3 | +import subprocess |
3 | 4 | import sys |
4 | 5 | import time |
5 | 6 | from pathlib import Path |
6 | | -from typing import Optional |
| 7 | +from typing import List, Optional |
7 | 8 |
|
8 | 9 | import questionary |
9 | 10 | import requests |
@@ -339,6 +340,117 @@ def config(): |
339 | 340 | ) |
340 | 341 |
|
341 | 342 |
|
| 343 | +@codecarbon.command( |
| 344 | + "run", |
| 345 | + short_help="Run a command and track its emissions.", |
| 346 | + context_settings={"allow_extra_args": True, "ignore_unknown_options": True}, |
| 347 | +) |
| 348 | +def run( |
| 349 | + ctx: typer.Context, |
| 350 | +): |
| 351 | + """ |
| 352 | + Run a command and track its carbon emissions. |
| 353 | +
|
| 354 | + This command wraps any executable and measures the machine's total power |
| 355 | + consumption during its execution. When the command completes, a summary |
| 356 | + report is displayed and emissions data is saved to a CSV file. |
| 357 | +
|
| 358 | + Note: This tracks machine-level emissions (entire system), not just the |
| 359 | + specific command. For process-specific tracking, the command must be |
| 360 | + instrumented with the CodeCarbon Python library. |
| 361 | +
|
| 362 | + Examples: |
| 363 | +
|
| 364 | + # Run any shell command |
| 365 | + codecarbon run -- ./benchmark.sh |
| 366 | +
|
| 367 | + # Commands with arguments (use single quotes for special chars) |
| 368 | + codecarbon run -- python -c 'print("Hello World!")' |
| 369 | +
|
| 370 | + # Pipe the command output |
| 371 | + codecarbon run -- npm run test > output.txt |
| 372 | +
|
| 373 | + The emissions data is appended to emissions.csv (default) in the current |
| 374 | + directory. The file path is shown in the final report. |
| 375 | + """ |
| 376 | + # Suppress all CodeCarbon logs during execution |
| 377 | + from codecarbon.external.logger import set_logger_level |
| 378 | + |
| 379 | + set_logger_level("critical") |
| 380 | + |
| 381 | + # Get the command from remaining args |
| 382 | + command = ctx.args |
| 383 | + |
| 384 | + if not command: |
| 385 | + print( |
| 386 | + "ERROR: No command provided. Use: codecarbon run -- <command>", |
| 387 | + file=sys.stderr, |
| 388 | + ) |
| 389 | + raise typer.Exit(1) |
| 390 | + |
| 391 | + # Initialize tracker with minimal logging |
| 392 | + tracker = EmissionsTracker(log_level="error", save_to_logger=False) |
| 393 | + |
| 394 | + print("🌱 CodeCarbon: Starting emissions tracking...") |
| 395 | + print(f" Command: {' '.join(command)}") |
| 396 | + print() |
| 397 | + |
| 398 | + tracker.start() |
| 399 | + |
| 400 | + process = None |
| 401 | + try: |
| 402 | + # Run the command, streaming output to console |
| 403 | + process = subprocess.Popen( |
| 404 | + command, |
| 405 | + stdout=sys.stdout, |
| 406 | + stderr=sys.stderr, |
| 407 | + text=True, |
| 408 | + ) |
| 409 | + |
| 410 | + # Wait for completion |
| 411 | + exit_code = process.wait() |
| 412 | + |
| 413 | + except FileNotFoundError: |
| 414 | + print(f"❌ Error: Command not found: {command[0]}", file=sys.stderr) |
| 415 | + exit_code = 127 |
| 416 | + except KeyboardInterrupt: |
| 417 | + print("\n⚠️ Interrupted by user", file=sys.stderr) |
| 418 | + if process is not None: |
| 419 | + process.terminate() |
| 420 | + try: |
| 421 | + process.wait(timeout=5) |
| 422 | + except subprocess.TimeoutExpired: |
| 423 | + process.kill() |
| 424 | + exit_code = 130 |
| 425 | + except Exception as e: |
| 426 | + print(f"❌ Error running command: {e}", file=sys.stderr) |
| 427 | + exit_code = 1 |
| 428 | + finally: |
| 429 | + emissions = tracker.stop() |
| 430 | + print() |
| 431 | + print("=" * 60) |
| 432 | + print("🌱 CodeCarbon Emissions Report") |
| 433 | + print("=" * 60) |
| 434 | + print(f" Command: {' '.join(command)}") |
| 435 | + if emissions is not None: |
| 436 | + print(f" Emissions: {emissions * 1000:.4f} g CO2eq") |
| 437 | + else: |
| 438 | + print(" Emissions: N/A") |
| 439 | + |
| 440 | + # Show where the data was saved |
| 441 | + if hasattr(tracker, "_conf") and "output_file" in tracker._conf: |
| 442 | + output_path = tracker._conf["output_file"] |
| 443 | + # Make it absolute if it's relative |
| 444 | + if not os.path.isabs(output_path): |
| 445 | + output_path = os.path.abspath(output_path) |
| 446 | + print(f" Saved to: {output_path}") |
| 447 | + |
| 448 | + print(" ⚠️ Note: Measured entire machine (includes all system processes)") |
| 449 | + print("=" * 60) |
| 450 | + |
| 451 | + raise typer.Exit(exit_code) |
| 452 | + |
| 453 | + |
342 | 454 | @codecarbon.command("monitor", short_help="Monitor your machine's carbon emissions.") |
343 | 455 | def monitor( |
344 | 456 | measure_power_secs: Annotated[ |
|
0 commit comments