From fff1c7971e34621dd06ea2d61093f2d174fb9c46 Mon Sep 17 00:00:00 2001 From: inimaz Date: Fri, 12 Dec 2025 16:46:40 +0100 Subject: [PATCH] feat: codecarbon run -- any_command --- codecarbon/cli/main.py | 112 ++++++++++++++++++++++++++++++++++ examples/command_line_tool.py | 10 ++- 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/codecarbon/cli/main.py b/codecarbon/cli/main.py index 9b3daf699..f517448dc 100644 --- a/codecarbon/cli/main.py +++ b/codecarbon/cli/main.py @@ -1,5 +1,6 @@ import os import signal +import subprocess import sys import time from pathlib import Path @@ -339,6 +340,117 @@ def config(): ) +@codecarbon.command( + "run", + short_help="Run a command and track its emissions.", + context_settings={"allow_extra_args": True, "ignore_unknown_options": True}, +) +def run( + ctx: typer.Context, +): + """ + Run a command and track its carbon emissions. + + This command wraps any executable and measures the machine's total power + consumption during its execution. When the command completes, a summary + report is displayed and emissions data is saved to a CSV file. + + Note: This tracks machine-level emissions (entire system), not just the + specific command. For process-specific tracking, the command must be + instrumented with the CodeCarbon Python library. + + Examples: + + # Run any shell command + codecarbon run -- ./benchmark.sh + + # Commands with arguments (use single quotes for special chars) + codecarbon run -- python -c 'print("Hello World!")' + + # Pipe the command output + codecarbon run -- npm run test > output.txt + + The emissions data is appended to emissions.csv (default) in the current + directory. The file path is shown in the final report. + """ + # Suppress all CodeCarbon logs during execution + from codecarbon.external.logger import set_logger_level + + set_logger_level("critical") + + # Get the command from remaining args + command = ctx.args + + if not command: + print( + "ERROR: No command provided. Use: codecarbon run -- ", + file=sys.stderr, + ) + raise typer.Exit(1) + + # Initialize tracker with minimal logging + tracker = EmissionsTracker(log_level="error", save_to_logger=False) + + print("🌱 CodeCarbon: Starting emissions tracking...") + print(f" Command: {' '.join(command)}") + print() + + tracker.start() + + process = None + try: + # Run the command, streaming output to console + process = subprocess.Popen( + command, + stdout=sys.stdout, + stderr=sys.stderr, + text=True, + ) + + # Wait for completion + exit_code = process.wait() + + except FileNotFoundError: + print(f"❌ Error: Command not found: {command[0]}", file=sys.stderr) + exit_code = 127 + except KeyboardInterrupt: + print("\n⚠️ Interrupted by user", file=sys.stderr) + if process is not None: + process.terminate() + try: + process.wait(timeout=5) + except subprocess.TimeoutExpired: + process.kill() + exit_code = 130 + except Exception as e: + print(f"❌ Error running command: {e}", file=sys.stderr) + exit_code = 1 + finally: + emissions = tracker.stop() + print() + print("=" * 60) + print("🌱 CodeCarbon Emissions Report") + print("=" * 60) + print(f" Command: {' '.join(command)}") + if emissions is not None: + print(f" Emissions: {emissions * 1000:.4f} g CO2eq") + else: + print(" Emissions: N/A") + + # Show where the data was saved + if hasattr(tracker, "_conf") and "output_file" in tracker._conf: + output_path = tracker._conf["output_file"] + # Make it absolute if it's relative + if not os.path.isabs(output_path): + output_path = os.path.abspath(output_path) + print(f" Saved to: {output_path}") + + print(" ⚠️ Note: Measured entire machine (includes all system processes)") + print("=" * 60) + + raise typer.Exit(exit_code) + + @codecarbon.command("monitor", short_help="Monitor your machine's carbon emissions.") def monitor( measure_power_secs: Annotated[ diff --git a/examples/command_line_tool.py b/examples/command_line_tool.py index da6f6584e..5308f3dfe 100644 --- a/examples/command_line_tool.py +++ b/examples/command_line_tool.py @@ -1,8 +1,16 @@ """ This example demonstrates how to use CodeCarbon with command line tools. -Here we measure the emissions of an speech-to-text with WhisperX. +⚠️ IMPORTANT LIMITATION: +CodeCarbon tracks emissions at the MACHINE level when monitoring external commands +via subprocess. It measures total system power during the command execution, which +includes the command itself AND all other system processes. +For accurate process-level tracking, the tracking code must be embedded in the +application being measured (not possible with external binaries like WhisperX). + +This example measures emissions during WhisperX execution, but cannot isolate +WhisperX's exact contribution from other system activity. """ import subprocess