Skip to content

Commit 92e2d4b

Browse files
committed
feat: codecarbon run -- any_command
1 parent 233fcf7 commit 92e2d4b

File tree

2 files changed

+122
-2
lines changed

2 files changed

+122
-2
lines changed

codecarbon/cli/main.py

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import os
22
import signal
3+
import subprocess
34
import sys
45
import time
56
from pathlib import Path
6-
from typing import Optional
7+
from typing import List, Optional
78

89
import questionary
910
import requests
@@ -339,6 +340,117 @@ def config():
339340
)
340341

341342

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+
342454
@codecarbon.command("monitor", short_help="Monitor your machine's carbon emissions.")
343455
def monitor(
344456
measure_power_secs: Annotated[

examples/command_line_tool.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
"""
22
This example demonstrates how to use CodeCarbon with command line tools.
33
4-
Here we measure the emissions of an speech-to-text with WhisperX.
4+
⚠️ IMPORTANT LIMITATION:
5+
CodeCarbon tracks emissions at the MACHINE level when monitoring external commands
6+
via subprocess. It measures total system power during the command execution, which
7+
includes the command itself AND all other system processes.
58
9+
For accurate process-level tracking, the tracking code must be embedded in the
10+
application being measured (not possible with external binaries like WhisperX).
11+
12+
This example measures emissions during WhisperX execution, but cannot isolate
13+
WhisperX's exact contribution from other system activity.
614
"""
715

816
import subprocess

0 commit comments

Comments
 (0)