From a296673df53ac65489ba573552f18f363ea51ec4 Mon Sep 17 00:00:00 2001 From: Evan Komp Date: Mon, 10 Feb 2025 09:39:53 -0700 Subject: [PATCH 1/4] added option for CLI to monitor offline --- codecarbon/cli/main.py | 66 ++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/codecarbon/cli/main.py b/codecarbon/cli/main.py index 32c78f58..50130e54 100644 --- a/codecarbon/cli/main.py +++ b/codecarbon/cli/main.py @@ -23,7 +23,7 @@ ) from codecarbon.core.api_client import ApiClient, get_datetime_with_timezone from codecarbon.core.schemas import ExperimentCreate, OrganizationCreate, ProjectCreate -from codecarbon.emissions_tracker import EmissionsTracker +from codecarbon.emissions_tracker import EmissionsTracker, OfflineEmissionsTracker AUTH_CLIENT_ID = os.environ.get( "AUTH_CLIENT_ID", @@ -327,47 +327,45 @@ def config(): @codecarbon.command("monitor", short_help="Monitor your machine's carbon emissions.") def monitor( - measure_power_secs: Annotated[ - int, typer.Argument(help="Interval between two measures.") - ] = 10, - api_call_interval: Annotated[ - int, typer.Argument(help="Number of measures between API calls.") - ] = 30, - api: Annotated[ - bool, typer.Option(help="Choose to call Code Carbon API or not") - ] = True, + measure_power_secs: Annotated[int, typer.Argument(help="Interval between two measures.")] = 10, + api_call_interval: Annotated[int, typer.Argument(help="Number of measures between API calls.")] = 30, + api: Annotated[bool, typer.Option(help="Choose to call Code Carbon API or not")] = True, + offline: Annotated[bool, typer.Option(help="Run in offline mode")] = False, + country_iso_code: Annotated[str, typer.Option(help="3-letter country ISO code for offline mode")] = None, + region: Annotated[str, typer.Option(help="Region/province for offline mode")] = None, ): - """Monitor your machine's carbon emissions. + """Monitor your machine's carbon emissions.""" + if offline: + if not country_iso_code: + print("ERROR: country_iso_code is required for offline mode", file=sys.stderr) + raise typer.Exit(1) + + tracker = OfflineEmissionsTracker( + measure_power_secs=measure_power_secs, + country_iso_code=country_iso_code, + region=region, + ) + else: + experiment_id = get_existing_local_exp_id() + if api and experiment_id is None: + print("ERROR: No experiment id, call 'codecarbon config' first.", file=sys.stderr) + raise typer.Exit(1) + + tracker = EmissionsTracker( + measure_power_secs=measure_power_secs, + api_call_interval=api_call_interval, + save_to_api=api, + ) - Args: - measure_power_secs (Annotated[int, typer.Argument, optional): Interval between two measures. Defaults to 10. - api_call_interval (Annotated[int, typer.Argument, optional): Number of measures before calling API. Defaults to 30. - api (Annotated[bool, typer.Option, optional): Choose to call Code Carbon API or not. Defaults to True. - """ - experiment_id = get_existing_local_exp_id() - if api: - if experiment_id is None: - print( - "ERROR: No experiment id, call 'codecarbon config' first.", - file=sys.stderr, - ) print("CodeCarbon is going in an infinite loop to monitor this machine.") - with EmissionsTracker( - measure_power_secs=measure_power_secs, - api_call_interval=api_call_interval, - save_to_api=api, - ) as tracker: - # Infinite loop + with tracker: while True: - if ( - hasattr(tracker, "_another_instance_already_running") - and tracker._another_instance_already_running - ): + if (hasattr(tracker, "_another_instance_already_running") + and tracker._another_instance_already_running): print("Another instance of CodeCarbon is already running. Exiting.") break time.sleep(300) - def questionary_prompt(prompt, list_options, default): value = questionary.select( prompt, From 08447eb0cd55b1da1abb36e9b3a0b8ca3a93fa7b Mon Sep 17 00:00:00 2001 From: Evan Komp Date: Mon, 10 Feb 2025 09:48:22 -0700 Subject: [PATCH 2/4] added option for CLI to monitor offline --- codecarbon/cli/main.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/codecarbon/cli/main.py b/codecarbon/cli/main.py index 50130e54..ac42dc3e 100644 --- a/codecarbon/cli/main.py +++ b/codecarbon/cli/main.py @@ -3,6 +3,7 @@ import time from pathlib import Path from typing import Optional +import signal import questionary import requests @@ -357,14 +358,30 @@ def monitor( save_to_api=api, ) + def signal_handler(signum, frame): + print("\nReceived signal to stop. Saving emissions data...") + tracker.stop() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + print("CodeCarbon is going in an infinite loop to monitor this machine.") - with tracker: + print("Press Ctrl+C to stop and save emissions data.") + + tracker.start() + try: while True: if (hasattr(tracker, "_another_instance_already_running") and tracker._another_instance_already_running): print("Another instance of CodeCarbon is already running. Exiting.") break time.sleep(300) + except Exception as e: + print(f"\nError occurred: {e}") + tracker.stop() + raise e def questionary_prompt(prompt, list_options, default): value = questionary.select( From ccd5a12ed4be5325de0ed2a787b2a4d76d5510ac Mon Sep 17 00:00:00 2001 From: benoit-cty <4-benoit-cty@users.noreply.git.leximpact.dev> Date: Sun, 15 Jun 2025 12:11:42 +0200 Subject: [PATCH 3/4] doc --- docs/edit/usage.rst | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/edit/usage.rst b/docs/edit/usage.rst index ca09f43b..ce2634ae 100644 --- a/docs/edit/usage.rst +++ b/docs/edit/usage.rst @@ -15,17 +15,17 @@ Command line ~~~~~~~~~~~~ -Create a minimal configuration file (just follow the prompts) : +Create a minimal configuration file (just follow the prompts) : .. code-block:: console - codecarbon config + codecarbon config .. image:: https://asciinema.org/a/667970.svg :align: center :alt: Init config :target: https://asciinema.org/a/667970 - + You can use the same command to modify an existing config : .. image:: https://asciinema.org/a/667971.svg @@ -38,12 +38,12 @@ If you want to track the emissions of a computer without having to modify your c .. code-block:: console - codecarbon monitor + codecarbon monitor You have to stop the monitoring manually with ``Ctrl+C``. -In the following example you will see how to use the CLI to monitor all the emissions of you computer and sending everything -to an API running on "localhost:8008" (Or you can start a private local API with "docker-compose up"). Using the public API with +In the following example you will see how to use the CLI to monitor all the emissions of you computer and sending everything +to an API running on "localhost:8008" (Or you can start a private local API with "docker-compose up"). Using the public API with this is not supported yet (coming soon!) .. image:: https://asciinema.org/a/667984.svg @@ -52,6 +52,11 @@ this is not supported yet (coming soon!) :target: https://asciinema.org/a/667984 +The command line could also works without internet by providing the country code like this: + +.. code-block:: console + + codecarbon monitor --offline --country-iso-code FRA Implementing CodeCarbon in your code allows you to track the emissions of a specific block of code. From f73024ac1350189dacc5ddcdd08547f90404d955 Mon Sep 17 00:00:00 2001 From: benoit-cty <4-benoit-cty@users.noreply.git.leximpact.dev> Date: Sun, 15 Jun 2025 12:12:11 +0200 Subject: [PATCH 4/4] lint --- codecarbon/cli/main.py | 43 +++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/codecarbon/cli/main.py b/codecarbon/cli/main.py index ac42dc3e..b5f60330 100644 --- a/codecarbon/cli/main.py +++ b/codecarbon/cli/main.py @@ -1,9 +1,9 @@ import os +import signal import sys import time from pathlib import Path from typing import Optional -import signal import questionary import requests @@ -328,19 +328,31 @@ def config(): @codecarbon.command("monitor", short_help="Monitor your machine's carbon emissions.") def monitor( - measure_power_secs: Annotated[int, typer.Argument(help="Interval between two measures.")] = 10, - api_call_interval: Annotated[int, typer.Argument(help="Number of measures between API calls.")] = 30, - api: Annotated[bool, typer.Option(help="Choose to call Code Carbon API or not")] = True, + measure_power_secs: Annotated[ + int, typer.Argument(help="Interval between two measures.") + ] = 10, + api_call_interval: Annotated[ + int, typer.Argument(help="Number of measures between API calls.") + ] = 30, + api: Annotated[ + bool, typer.Option(help="Choose to call Code Carbon API or not") + ] = True, offline: Annotated[bool, typer.Option(help="Run in offline mode")] = False, - country_iso_code: Annotated[str, typer.Option(help="3-letter country ISO code for offline mode")] = None, - region: Annotated[str, typer.Option(help="Region/province for offline mode")] = None, + country_iso_code: Annotated[ + str, typer.Option(help="3-letter country ISO code for offline mode") + ] = None, + region: Annotated[ + str, typer.Option(help="Region/province for offline mode") + ] = None, ): """Monitor your machine's carbon emissions.""" if offline: if not country_iso_code: - print("ERROR: country_iso_code is required for offline mode", file=sys.stderr) + print( + "ERROR: country_iso_code is required for offline mode", file=sys.stderr + ) raise typer.Exit(1) - + tracker = OfflineEmissionsTracker( measure_power_secs=measure_power_secs, country_iso_code=country_iso_code, @@ -349,9 +361,12 @@ def monitor( else: experiment_id = get_existing_local_exp_id() if api and experiment_id is None: - print("ERROR: No experiment id, call 'codecarbon config' first.", file=sys.stderr) + print( + "ERROR: No experiment id, call 'codecarbon config' first.", + file=sys.stderr, + ) raise typer.Exit(1) - + tracker = EmissionsTracker( measure_power_secs=measure_power_secs, api_call_interval=api_call_interval, @@ -366,15 +381,16 @@ def signal_handler(signum, frame): signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) - print("CodeCarbon is going in an infinite loop to monitor this machine.") print("Press Ctrl+C to stop and save emissions data.") tracker.start() try: while True: - if (hasattr(tracker, "_another_instance_already_running") - and tracker._another_instance_already_running): + if ( + hasattr(tracker, "_another_instance_already_running") + and tracker._another_instance_already_running + ): print("Another instance of CodeCarbon is already running. Exiting.") break time.sleep(300) @@ -383,6 +399,7 @@ def signal_handler(signum, frame): tracker.stop() raise e + def questionary_prompt(prompt, list_options, default): value = questionary.select( prompt,