codecarbon 2.5.0__tar.gz → 2.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codecarbon-2.5.0 → codecarbon-2.6.0}/.gitignore +3 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/PKG-INFO +5 -2
- {codecarbon-2.5.0 → codecarbon-2.6.0}/README.md +1 -1
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/__init__.py +1 -0
- codecarbon-2.6.0/codecarbon/_version.py +1 -0
- codecarbon-2.6.0/codecarbon/cli/cli_utils.py +103 -0
- codecarbon-2.6.0/codecarbon/cli/main.py +283 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/api_client.py +144 -10
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/gpu.py +11 -2
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/schemas.py +29 -0
- codecarbon-2.6.0/codecarbon/data/cloud/impact.csv +41 -0
- codecarbon-2.6.0/codecarbon/data/private_infra/global_energy_mix.json +1640 -0
- codecarbon-2.5.0/codecarbon/data/private_infra/our_world_in_data-2022_data.ipynb → codecarbon-2.6.0/codecarbon/data/private_infra/our_world_in_data.ipynb +420 -419
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/emissions_tracker.py +10 -10
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/input.py +25 -9
- {codecarbon-2.5.0 → codecarbon-2.6.0}/pyproject.toml +12 -2
- codecarbon-2.5.0/codecarbon/_version.py +0 -1
- codecarbon-2.5.0/codecarbon/cli/cli_utils.py +0 -39
- codecarbon-2.5.0/codecarbon/cli/main.py +0 -89
- codecarbon-2.5.0/codecarbon/data/cloud/impact.csv +0 -37
- codecarbon-2.5.0/codecarbon/data/private_infra/global_energy_mix.json +0 -1703
- {codecarbon-2.5.0 → codecarbon-2.6.0}/LICENSE +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/cli/__init__.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/__init__.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/cloud.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/co2_signal.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/config.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/cpu.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/emissions.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/measure.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/powermetrics.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/rapl.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/units.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/core/util.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/canada_provinces.geojson +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/hardware/cpu_power.csv +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/2016/canada_energy_mix.json +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/2016/global_energy_mix-old.json +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/2016/usa_emissions.json +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/2020/01_get_world_carbon_intensity.ipynb +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/2020/02_convert_csv_to_json.ipynb +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/2020/03_add_eu_data.ipynb +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/2020/eu-carbon-intensity-electricity.csv +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/2023-07-07-22-40-48.png +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/carbon_intensity_per_source.json +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/data/private_infra/world_energy_mix.csv +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/external/__init__.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/external/geography.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/external/hardware.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/external/logger.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/external/scheduler.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/external/task.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/__init__.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/base_output.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/emissions_data.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/file.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/http.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/logger.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/metrics/__init__.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/metrics/logfire.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/metrics/metric_docs.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/output_methods/metrics/prometheus.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/__init__.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/assets/__init__.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/assets/car_icon.png +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/assets/house_icon.png +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/assets/tv_icon.png +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/carbonboard.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/carbonboard_on_api.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/components.py +0 -0
- {codecarbon-2.5.0 → codecarbon-2.6.0}/codecarbon/viz/data.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: codecarbon
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
4
4
|
Project-URL: Homepage, https://codecarbon.io/
|
|
5
5
|
Project-URL: Repository, https://github.com/mlco2/codecarbon
|
|
6
6
|
Project-URL: Dashboard, http://dashboard.codecarbon.io/
|
|
@@ -25,8 +25,11 @@ Requires-Dist: prometheus-client
|
|
|
25
25
|
Requires-Dist: psutil
|
|
26
26
|
Requires-Dist: py-cpuinfo
|
|
27
27
|
Requires-Dist: pynvml
|
|
28
|
+
Requires-Dist: questionary
|
|
28
29
|
Requires-Dist: rapidfuzz
|
|
29
30
|
Requires-Dist: requests
|
|
31
|
+
Requires-Dist: rich
|
|
32
|
+
Requires-Dist: typer
|
|
30
33
|
Provides-Extra: viz
|
|
31
34
|
Requires-Dist: dash; extra == 'viz'
|
|
32
35
|
Requires-Dist: dash-bootstrap-components<1.0.0; extra == 'viz'
|
|
@@ -190,7 +193,7 @@ Here is a sample for BibTeX:
|
|
|
190
193
|
Marc Alencon and
|
|
191
194
|
Michał Stęchły and
|
|
192
195
|
Christian Bauer and
|
|
193
|
-
Lucas
|
|
196
|
+
Lucas Otávio N. de Araújo and
|
|
194
197
|
JPW and
|
|
195
198
|
MinervaBooks},
|
|
196
199
|
title = {mlco2/codecarbon: v2.4.1},
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.6.0"
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import configparser
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.prompt import Confirm
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_config(path: Optional[Path] = None):
|
|
10
|
+
p = path or Path.cwd().resolve() / ".codecarbon.config"
|
|
11
|
+
|
|
12
|
+
if p.exists():
|
|
13
|
+
config = configparser.ConfigParser()
|
|
14
|
+
config.read(str(p))
|
|
15
|
+
if "codecarbon" in config.sections():
|
|
16
|
+
d = dict(config["codecarbon"])
|
|
17
|
+
return d
|
|
18
|
+
|
|
19
|
+
else:
|
|
20
|
+
raise FileNotFoundError(
|
|
21
|
+
"No .codecarbon.config file found in the current directory."
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_api_endpoint(path: Optional[Path] = None):
|
|
26
|
+
p = path or Path.cwd().resolve() / ".codecarbon.config"
|
|
27
|
+
if p.exists():
|
|
28
|
+
config = configparser.ConfigParser()
|
|
29
|
+
config.read(str(p))
|
|
30
|
+
if "codecarbon" in config.sections():
|
|
31
|
+
d = dict(config["codecarbon"])
|
|
32
|
+
if "api_endpoint" in d:
|
|
33
|
+
return d["api_endpoint"]
|
|
34
|
+
else:
|
|
35
|
+
with p.open("a") as f:
|
|
36
|
+
f.write("api_endpoint=https://api.codecarbon.io\n")
|
|
37
|
+
return "https://api.codecarbon.io"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_existing_local_exp_id(path: Optional[Path] = None):
|
|
41
|
+
p = path or Path.cwd().resolve() / ".codecarbon.config"
|
|
42
|
+
if p.exists():
|
|
43
|
+
config = configparser.ConfigParser()
|
|
44
|
+
config.read(str(p))
|
|
45
|
+
if "codecarbon" in config.sections():
|
|
46
|
+
d = dict(config["codecarbon"])
|
|
47
|
+
if "experiment_id" in d:
|
|
48
|
+
return d["experiment_id"]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def write_local_exp_id(exp_id, path: Optional[Path] = None):
|
|
52
|
+
p = path or Path.cwd().resolve() / ".codecarbon.config"
|
|
53
|
+
|
|
54
|
+
config = configparser.ConfigParser()
|
|
55
|
+
if p.exists():
|
|
56
|
+
config.read(str(p))
|
|
57
|
+
if "codecarbon" not in config.sections():
|
|
58
|
+
config.add_section("codecarbon")
|
|
59
|
+
|
|
60
|
+
config["codecarbon"]["experiment_id"] = exp_id
|
|
61
|
+
|
|
62
|
+
with p.open("w") as f:
|
|
63
|
+
config.write(f)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def overwrite_local_config(config_name, value, path: Optional[Path] = None):
|
|
67
|
+
p = path or Path.cwd().resolve() / ".codecarbon.config"
|
|
68
|
+
|
|
69
|
+
config = configparser.ConfigParser()
|
|
70
|
+
if p.exists():
|
|
71
|
+
config.read(str(p))
|
|
72
|
+
if "codecarbon" not in config.sections():
|
|
73
|
+
config.add_section("codecarbon")
|
|
74
|
+
|
|
75
|
+
config["codecarbon"][config_name] = value
|
|
76
|
+
with p.open("w") as f:
|
|
77
|
+
config.write(f)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def create_new_config_file():
|
|
81
|
+
typer.echo("Creating new config file")
|
|
82
|
+
file_path = typer.prompt(
|
|
83
|
+
"Where do you want to put your config file ?",
|
|
84
|
+
type=str,
|
|
85
|
+
default="~/.codecarbon.config",
|
|
86
|
+
)
|
|
87
|
+
if file_path[0] == "~":
|
|
88
|
+
file_path = Path.home() / file_path[2:]
|
|
89
|
+
else:
|
|
90
|
+
file_path = Path(file_path)
|
|
91
|
+
|
|
92
|
+
if not file_path.parent.exists():
|
|
93
|
+
create = Confirm.ask(
|
|
94
|
+
"Parent folder does not exist do you want to create it (and parents) ?"
|
|
95
|
+
)
|
|
96
|
+
if create:
|
|
97
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
|
|
99
|
+
file_path.touch()
|
|
100
|
+
with open(file_path, "w") as f:
|
|
101
|
+
f.write("[codecarbon]\n")
|
|
102
|
+
typer.echo(f"Config file created at {file_path}")
|
|
103
|
+
return file_path
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import questionary
|
|
6
|
+
import typer
|
|
7
|
+
from rich import print
|
|
8
|
+
from rich.prompt import Confirm
|
|
9
|
+
from typing_extensions import Annotated
|
|
10
|
+
|
|
11
|
+
from codecarbon import __app_name__, __version__
|
|
12
|
+
from codecarbon.cli.cli_utils import (
|
|
13
|
+
create_new_config_file,
|
|
14
|
+
get_api_endpoint,
|
|
15
|
+
get_config,
|
|
16
|
+
get_existing_local_exp_id,
|
|
17
|
+
overwrite_local_config,
|
|
18
|
+
)
|
|
19
|
+
from codecarbon.core.api_client import ApiClient, get_datetime_with_timezone
|
|
20
|
+
from codecarbon.core.schemas import ExperimentCreate, OrganizationCreate, ProjectCreate
|
|
21
|
+
from codecarbon.emissions_tracker import EmissionsTracker
|
|
22
|
+
|
|
23
|
+
DEFAULT_PROJECT_ID = "e60afa92-17b7-4720-91a0-1ae91e409ba1"
|
|
24
|
+
DEFAULT_ORGANIzATION_ID = "e60afa92-17b7-4720-91a0-1ae91e409ba1"
|
|
25
|
+
|
|
26
|
+
codecarbon = typer.Typer()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _version_callback(value: bool) -> None:
|
|
30
|
+
if value:
|
|
31
|
+
print(f"{__app_name__} v{__version__}")
|
|
32
|
+
raise typer.Exit()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@codecarbon.callback()
|
|
36
|
+
def main(
|
|
37
|
+
version: Optional[bool] = typer.Option(
|
|
38
|
+
None,
|
|
39
|
+
"--version",
|
|
40
|
+
"-v",
|
|
41
|
+
help="Show the application's version and exit.",
|
|
42
|
+
callback=_version_callback,
|
|
43
|
+
is_eager=True,
|
|
44
|
+
),
|
|
45
|
+
) -> None:
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def show_config(path: Path = Path("./.codecarbon.config")) -> None:
|
|
50
|
+
d = get_config(path)
|
|
51
|
+
api_endpoint = get_api_endpoint(path)
|
|
52
|
+
api = ApiClient(endpoint_url=api_endpoint)
|
|
53
|
+
print("Current configuration : \n")
|
|
54
|
+
print("Config file content : ")
|
|
55
|
+
print(d)
|
|
56
|
+
try:
|
|
57
|
+
if "organization_id" not in d:
|
|
58
|
+
print(
|
|
59
|
+
"No organization_id in config, follow setup instruction to complete your configuration file!",
|
|
60
|
+
)
|
|
61
|
+
else:
|
|
62
|
+
org = api.get_organization(d["organization_id"])
|
|
63
|
+
|
|
64
|
+
if "project_id" not in d:
|
|
65
|
+
print(
|
|
66
|
+
"No project_id in config, follow setup instruction to complete your configuration file!",
|
|
67
|
+
)
|
|
68
|
+
else:
|
|
69
|
+
project = api.get_project(d["project_id"])
|
|
70
|
+
if "experiment_id" not in d:
|
|
71
|
+
print(
|
|
72
|
+
"No experiment_id in config, follow setup instruction to complete your configuration file!",
|
|
73
|
+
)
|
|
74
|
+
else:
|
|
75
|
+
experiment = api.get_experiment(d["experiment_id"])
|
|
76
|
+
print("\nExperiment :")
|
|
77
|
+
print(experiment)
|
|
78
|
+
print("\nProject :")
|
|
79
|
+
print(project)
|
|
80
|
+
print("\nOrganization :")
|
|
81
|
+
print(org)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"Your configuration is invalid, please run `codecarbon config --init` first! (error: {e})"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@codecarbon.command("config", short_help="Generate or show config")
|
|
89
|
+
def config():
|
|
90
|
+
"""
|
|
91
|
+
Initialize CodeCarbon, this will prompt you for configuration of Organisation/Team/Project/Experiment.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
print("Welcome to CodeCarbon configuration wizard")
|
|
95
|
+
home = Path.home()
|
|
96
|
+
global_path = (home / ".codecarbon.config").expanduser().resolve()
|
|
97
|
+
|
|
98
|
+
if global_path.exists():
|
|
99
|
+
print("Existing global config file found :")
|
|
100
|
+
show_config(global_path)
|
|
101
|
+
|
|
102
|
+
use_config = questionary_prompt(
|
|
103
|
+
"Use existing global ~/.codecarbon.config to configure or create a new file somewhere else ?",
|
|
104
|
+
["~/.codecarbon.config", "Create New Config"],
|
|
105
|
+
default="~/.codecarbon.config",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if use_config == "~/.codecarbon.config":
|
|
109
|
+
modify = Confirm.ask("Do you want to modify the existing config file ?")
|
|
110
|
+
if modify:
|
|
111
|
+
print(f"Modifying existing config file {global_path}:")
|
|
112
|
+
file_path = global_path
|
|
113
|
+
else:
|
|
114
|
+
print(f"Using already existing global config file {global_path}")
|
|
115
|
+
|
|
116
|
+
return
|
|
117
|
+
else:
|
|
118
|
+
file_path = create_new_config_file()
|
|
119
|
+
else:
|
|
120
|
+
file_path = create_new_config_file()
|
|
121
|
+
|
|
122
|
+
api_endpoint = get_api_endpoint(file_path)
|
|
123
|
+
api_endpoint = typer.prompt(
|
|
124
|
+
f"Current API endpoint is {api_endpoint}. Press enter to continue or input other url",
|
|
125
|
+
type=str,
|
|
126
|
+
default=api_endpoint,
|
|
127
|
+
)
|
|
128
|
+
overwrite_local_config("api_endpoint", api_endpoint, path=file_path)
|
|
129
|
+
api = ApiClient(endpoint_url=api_endpoint)
|
|
130
|
+
organizations = api.get_list_organizations()
|
|
131
|
+
org = questionary_prompt(
|
|
132
|
+
"Pick existing organization from list or Create new organization ?",
|
|
133
|
+
[org["name"] for org in organizations] + ["Create New Organization"],
|
|
134
|
+
default="Create New Organization",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if org == "Create New Organization":
|
|
138
|
+
org_name = typer.prompt("Organization name", default="Code Carbon user test")
|
|
139
|
+
org_description = typer.prompt(
|
|
140
|
+
"Organization description", default="Code Carbon user test"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
organization_create = OrganizationCreate(
|
|
144
|
+
name=org_name,
|
|
145
|
+
description=org_description,
|
|
146
|
+
)
|
|
147
|
+
organization = api.create_organization(organization=organization_create)
|
|
148
|
+
print(f"Created organization : {organization}")
|
|
149
|
+
else:
|
|
150
|
+
organization = [orga for orga in organizations if orga["name"] == org][0]
|
|
151
|
+
org_id = organization["id"]
|
|
152
|
+
overwrite_local_config("organization_id", org_id, path=file_path)
|
|
153
|
+
|
|
154
|
+
projects = api.list_projects_from_organization(org_id)
|
|
155
|
+
project_names = [project["name"] for project in projects] if projects else []
|
|
156
|
+
project = questionary_prompt(
|
|
157
|
+
"Pick existing project from list or Create new project ?",
|
|
158
|
+
project_names + ["Create New Project"],
|
|
159
|
+
default="Create New Project",
|
|
160
|
+
)
|
|
161
|
+
if project == "Create New Project":
|
|
162
|
+
project_name = typer.prompt("Project name", default="Code Carbon user test")
|
|
163
|
+
project_description = typer.prompt(
|
|
164
|
+
"Project description", default="Code Carbon user test"
|
|
165
|
+
)
|
|
166
|
+
project_create = ProjectCreate(
|
|
167
|
+
name=project_name,
|
|
168
|
+
description=project_description,
|
|
169
|
+
organization_id=org_id,
|
|
170
|
+
)
|
|
171
|
+
project = api.create_project(project=project_create)
|
|
172
|
+
print(f"Created project : {project}")
|
|
173
|
+
else:
|
|
174
|
+
project = [p for p in projects if p["name"] == project][0]
|
|
175
|
+
project_id = project["id"]
|
|
176
|
+
overwrite_local_config("project_id", project_id, path=file_path)
|
|
177
|
+
|
|
178
|
+
experiments = api.list_experiments_from_project(project_id)
|
|
179
|
+
experiments_names = (
|
|
180
|
+
[experiment["name"] for experiment in experiments] if experiments else []
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
experiment = questionary_prompt(
|
|
184
|
+
"Pick existing experiment from list or Create new experiment ?",
|
|
185
|
+
experiments_names + ["Create New Experiment"],
|
|
186
|
+
default="Create New Experiment",
|
|
187
|
+
)
|
|
188
|
+
if experiment == "Create New Experiment":
|
|
189
|
+
print("Creating new experiment")
|
|
190
|
+
exp_name = typer.prompt("Experiment name :", default="Code Carbon user test")
|
|
191
|
+
exp_description = typer.prompt(
|
|
192
|
+
"Experiment description :",
|
|
193
|
+
default="Code Carbon user test ",
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
exp_on_cloud = Confirm.ask("Is this experiment running on the cloud ?")
|
|
197
|
+
if exp_on_cloud is True:
|
|
198
|
+
cloud_provider = typer.prompt(
|
|
199
|
+
"Cloud provider (AWS, GCP, Azure, ...)", default="AWS"
|
|
200
|
+
)
|
|
201
|
+
cloud_region = typer.prompt(
|
|
202
|
+
"Cloud region (eu-west-1, us-east-1, ...)", default="eu-west-1"
|
|
203
|
+
)
|
|
204
|
+
else:
|
|
205
|
+
cloud_provider = None
|
|
206
|
+
cloud_region = None
|
|
207
|
+
country_name = typer.prompt("Country name :", default="Auto")
|
|
208
|
+
country_iso_code = typer.prompt("Country ISO code :", default="Auto")
|
|
209
|
+
region = typer.prompt("Region :", default="Auto")
|
|
210
|
+
if country_name == "Auto":
|
|
211
|
+
country_name = None
|
|
212
|
+
if country_iso_code == "Auto":
|
|
213
|
+
country_iso_code = None
|
|
214
|
+
if region == "Auto":
|
|
215
|
+
region = None
|
|
216
|
+
experiment_create = ExperimentCreate(
|
|
217
|
+
timestamp=get_datetime_with_timezone(),
|
|
218
|
+
name=exp_name,
|
|
219
|
+
description=exp_description,
|
|
220
|
+
on_cloud=exp_on_cloud,
|
|
221
|
+
project_id=project["id"],
|
|
222
|
+
country_name=country_name,
|
|
223
|
+
country_iso_code=country_iso_code,
|
|
224
|
+
region=region,
|
|
225
|
+
cloud_provider=cloud_provider,
|
|
226
|
+
cloud_region=cloud_region,
|
|
227
|
+
)
|
|
228
|
+
experiment = api.add_experiment(experiment=experiment_create)
|
|
229
|
+
|
|
230
|
+
else:
|
|
231
|
+
experiment = [e for e in experiments if e["name"] == experiment][0]
|
|
232
|
+
|
|
233
|
+
overwrite_local_config("experiment_id", experiment["id"], path=file_path)
|
|
234
|
+
show_config(file_path)
|
|
235
|
+
print(
|
|
236
|
+
"Consult [link=https://mlco2.github.io/codecarbon/usage.html#configuration]configuration documentation[/link] for more configuration options"
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@codecarbon.command("monitor", short_help="Monitor your machine's carbon emissions.")
|
|
241
|
+
def monitor(
|
|
242
|
+
measure_power_secs: Annotated[
|
|
243
|
+
int, typer.Argument(help="Interval between two measures.")
|
|
244
|
+
] = 10,
|
|
245
|
+
api_call_interval: Annotated[
|
|
246
|
+
int, typer.Argument(help="Number of measures between API calls.")
|
|
247
|
+
] = 30,
|
|
248
|
+
api: Annotated[
|
|
249
|
+
bool, typer.Option(help="Choose to call Code Carbon API or not")
|
|
250
|
+
] = True,
|
|
251
|
+
):
|
|
252
|
+
"""Monitor your machine's carbon emissions.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
measure_power_secs (Annotated[int, typer.Argument, optional): Interval between two measures. Defaults to 10.
|
|
256
|
+
api_call_interval (Annotated[int, typer.Argument, optional): Number of measures before calling API. Defaults to 30.
|
|
257
|
+
api (Annotated[bool, typer.Option, optional): Choose to call Code Carbon API or not. Defaults to True.
|
|
258
|
+
"""
|
|
259
|
+
experiment_id = get_existing_local_exp_id()
|
|
260
|
+
if api and experiment_id is None:
|
|
261
|
+
print("ERROR: No experiment id, call 'codecarbon init' first.", err=True)
|
|
262
|
+
print("CodeCarbon is going in an infinite loop to monitor this machine.")
|
|
263
|
+
with EmissionsTracker(
|
|
264
|
+
measure_power_secs=measure_power_secs,
|
|
265
|
+
api_call_interval=api_call_interval,
|
|
266
|
+
save_to_api=api,
|
|
267
|
+
):
|
|
268
|
+
# Infinite loop
|
|
269
|
+
while True:
|
|
270
|
+
time.sleep(300)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def questionary_prompt(prompt, list_options, default):
|
|
274
|
+
value = questionary.select(
|
|
275
|
+
prompt,
|
|
276
|
+
list_options,
|
|
277
|
+
default,
|
|
278
|
+
).ask()
|
|
279
|
+
return value
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
if __name__ == "__main__":
|
|
283
|
+
codecarbon()
|
|
@@ -14,7 +14,13 @@ from datetime import timedelta, tzinfo
|
|
|
14
14
|
import arrow
|
|
15
15
|
import requests
|
|
16
16
|
|
|
17
|
-
from codecarbon.core.schemas import
|
|
17
|
+
from codecarbon.core.schemas import (
|
|
18
|
+
EmissionCreate,
|
|
19
|
+
ExperimentCreate,
|
|
20
|
+
OrganizationCreate,
|
|
21
|
+
ProjectCreate,
|
|
22
|
+
RunCreate,
|
|
23
|
+
)
|
|
18
24
|
from codecarbon.external.logger import logger
|
|
19
25
|
|
|
20
26
|
# from codecarbon.output import EmissionsData
|
|
@@ -28,7 +34,6 @@ def get_datetime_with_timezone():
|
|
|
28
34
|
class ApiClient: # (AsyncClient)
|
|
29
35
|
"""
|
|
30
36
|
This class call the Code Carbon API
|
|
31
|
-
Note : The project, team and organization must have been created in the interface.
|
|
32
37
|
"""
|
|
33
38
|
|
|
34
39
|
run_id = None
|
|
@@ -61,6 +66,105 @@ class ApiClient: # (AsyncClient)
|
|
|
61
66
|
stacklevel=2,
|
|
62
67
|
)
|
|
63
68
|
|
|
69
|
+
def get_list_organizations(self):
|
|
70
|
+
"""
|
|
71
|
+
List all organizations
|
|
72
|
+
"""
|
|
73
|
+
url = self.url + "/organizations"
|
|
74
|
+
r = requests.get(url=url, timeout=2)
|
|
75
|
+
if r.status_code != 200:
|
|
76
|
+
self._log_error(url, {}, r)
|
|
77
|
+
return None
|
|
78
|
+
return r.json()
|
|
79
|
+
|
|
80
|
+
def check_organization_exists(self, organization_name: str):
|
|
81
|
+
"""
|
|
82
|
+
Check if an organization exists
|
|
83
|
+
"""
|
|
84
|
+
organizations = self.get_list_organizations()
|
|
85
|
+
if organizations is None:
|
|
86
|
+
return False
|
|
87
|
+
for organization in organizations:
|
|
88
|
+
if organization["name"] == organization_name:
|
|
89
|
+
return organization
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
def create_organization(self, organization: OrganizationCreate):
|
|
93
|
+
"""
|
|
94
|
+
Create an organization
|
|
95
|
+
"""
|
|
96
|
+
payload = dataclasses.asdict(organization)
|
|
97
|
+
url = self.url + "/organizations"
|
|
98
|
+
if organization := self.check_organization_exists(organization.name):
|
|
99
|
+
logger.warning(
|
|
100
|
+
f"Organization {organization['name']} already exists. Skipping creation."
|
|
101
|
+
)
|
|
102
|
+
return organization
|
|
103
|
+
else:
|
|
104
|
+
r = requests.post(url=url, json=payload, timeout=2)
|
|
105
|
+
if r.status_code != 201:
|
|
106
|
+
self._log_error(url, payload, r)
|
|
107
|
+
return None
|
|
108
|
+
return r.json()
|
|
109
|
+
|
|
110
|
+
def get_organization(self, organization_id):
|
|
111
|
+
"""
|
|
112
|
+
Get an organization
|
|
113
|
+
"""
|
|
114
|
+
url = self.url + "/organizations/" + organization_id
|
|
115
|
+
r = requests.get(url=url, timeout=2)
|
|
116
|
+
if r.status_code != 200:
|
|
117
|
+
self._log_error(url, {}, r)
|
|
118
|
+
return None
|
|
119
|
+
return r.json()
|
|
120
|
+
|
|
121
|
+
def update_organization(self, organization: OrganizationCreate):
|
|
122
|
+
"""
|
|
123
|
+
Update an organization
|
|
124
|
+
"""
|
|
125
|
+
payload = dataclasses.asdict(organization)
|
|
126
|
+
url = self.url + "/organizations/" + organization.id
|
|
127
|
+
r = requests.patch(url=url, json=payload, timeout=2)
|
|
128
|
+
if r.status_code != 200:
|
|
129
|
+
self._log_error(url, payload, r)
|
|
130
|
+
return None
|
|
131
|
+
return r.json()
|
|
132
|
+
|
|
133
|
+
def list_projects_from_organization(self, organization_id):
|
|
134
|
+
"""
|
|
135
|
+
List all projects
|
|
136
|
+
"""
|
|
137
|
+
url = self.url + "/organizations/" + organization_id + "/projects"
|
|
138
|
+
|
|
139
|
+
r = requests.get(url=url, timeout=2)
|
|
140
|
+
if r.status_code != 200:
|
|
141
|
+
self._log_error(url, {}, r)
|
|
142
|
+
return None
|
|
143
|
+
return r.json()
|
|
144
|
+
|
|
145
|
+
def create_project(self, project: ProjectCreate):
|
|
146
|
+
"""
|
|
147
|
+
Create a project
|
|
148
|
+
"""
|
|
149
|
+
payload = dataclasses.asdict(project)
|
|
150
|
+
url = self.url + "/projects"
|
|
151
|
+
r = requests.post(url=url, json=payload, timeout=2)
|
|
152
|
+
if r.status_code != 201:
|
|
153
|
+
self._log_error(url, payload, r)
|
|
154
|
+
return None
|
|
155
|
+
return r.json()
|
|
156
|
+
|
|
157
|
+
def get_project(self, project_id):
|
|
158
|
+
"""
|
|
159
|
+
Get a project
|
|
160
|
+
"""
|
|
161
|
+
url = self.url + "/projects/" + project_id
|
|
162
|
+
r = requests.get(url=url, timeout=2)
|
|
163
|
+
if r.status_code != 200:
|
|
164
|
+
self._log_error(url, {}, r)
|
|
165
|
+
return None
|
|
166
|
+
return r.json()
|
|
167
|
+
|
|
64
168
|
def add_emission(self, carbon_emission: dict):
|
|
65
169
|
assert self.experiment_id is not None
|
|
66
170
|
self._previous_call = time.time()
|
|
@@ -97,7 +201,7 @@ class ApiClient: # (AsyncClient)
|
|
|
97
201
|
)
|
|
98
202
|
try:
|
|
99
203
|
payload = dataclasses.asdict(emission)
|
|
100
|
-
url = self.url + "/
|
|
204
|
+
url = self.url + "/emissions"
|
|
101
205
|
r = requests.post(url=url, json=payload, timeout=2)
|
|
102
206
|
if r.status_code != 201:
|
|
103
207
|
self._log_error(url, payload, r)
|
|
@@ -137,7 +241,7 @@ class ApiClient: # (AsyncClient)
|
|
|
137
241
|
tracking_mode=self.conf.get("tracking_mode"),
|
|
138
242
|
)
|
|
139
243
|
payload = dataclasses.asdict(run)
|
|
140
|
-
url = self.url + "/
|
|
244
|
+
url = self.url + "/runs"
|
|
141
245
|
r = requests.post(url=url, json=payload, timeout=2)
|
|
142
246
|
if r.status_code != 201:
|
|
143
247
|
self._log_error(url, payload, r)
|
|
@@ -157,24 +261,54 @@ class ApiClient: # (AsyncClient)
|
|
|
157
261
|
except Exception as e:
|
|
158
262
|
logger.error(e, exc_info=True)
|
|
159
263
|
|
|
264
|
+
def list_experiments_from_project(self, project_id: str):
|
|
265
|
+
"""
|
|
266
|
+
List all experiments for a project
|
|
267
|
+
"""
|
|
268
|
+
url = self.url + "/projects/" + project_id + "/experiments"
|
|
269
|
+
r = requests.get(url=url, timeout=2)
|
|
270
|
+
if r.status_code != 200:
|
|
271
|
+
self._log_error(url, {}, r)
|
|
272
|
+
return []
|
|
273
|
+
return r.json()
|
|
274
|
+
|
|
275
|
+
def set_experiment(self, experiment_id: str):
|
|
276
|
+
"""
|
|
277
|
+
Set the experiment id
|
|
278
|
+
"""
|
|
279
|
+
self.experiment_id = experiment_id
|
|
280
|
+
|
|
160
281
|
def add_experiment(self, experiment: ExperimentCreate):
|
|
161
282
|
"""
|
|
162
283
|
Create an experiment, used by the CLI, not the package.
|
|
163
284
|
::experiment:: The experiment to create.
|
|
164
285
|
"""
|
|
165
286
|
payload = dataclasses.asdict(experiment)
|
|
166
|
-
url = self.url + "/
|
|
287
|
+
url = self.url + "/experiments"
|
|
167
288
|
r = requests.post(url=url, json=payload, timeout=2)
|
|
168
289
|
if r.status_code != 201:
|
|
169
290
|
self._log_error(url, payload, r)
|
|
170
291
|
return None
|
|
171
|
-
|
|
172
|
-
|
|
292
|
+
return r.json()
|
|
293
|
+
|
|
294
|
+
def get_experiment(self, experiment_id):
|
|
295
|
+
"""
|
|
296
|
+
Get an experiment by id
|
|
297
|
+
"""
|
|
298
|
+
url = self.url + "/experiments/" + experiment_id
|
|
299
|
+
r = requests.get(url=url, timeout=2)
|
|
300
|
+
if r.status_code != 200:
|
|
301
|
+
self._log_error(url, {}, r)
|
|
302
|
+
return None
|
|
303
|
+
return r.json()
|
|
173
304
|
|
|
174
305
|
def _log_error(self, url, payload, response):
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
306
|
+
if len(payload) > 0:
|
|
307
|
+
logger.error(
|
|
308
|
+
f"ApiClient Error when calling the API on {url} with : {json.dumps(payload)}"
|
|
309
|
+
)
|
|
310
|
+
else:
|
|
311
|
+
logger.error(f"ApiClient Error when calling the API on {url}")
|
|
178
312
|
logger.error(
|
|
179
313
|
f"ApiClient API return http code {response.status_code} and answer : {response.text}"
|
|
180
314
|
)
|
|
@@ -25,7 +25,10 @@ class GPUDevice:
|
|
|
25
25
|
self._init_static_details()
|
|
26
26
|
|
|
27
27
|
def _get_energy_kwh(self):
|
|
28
|
-
|
|
28
|
+
total_energy_consumption = self._get_total_energy_consumption()
|
|
29
|
+
if total_energy_consumption is None:
|
|
30
|
+
return self.last_energy
|
|
31
|
+
return Energy.from_millijoules(total_energy_consumption)
|
|
29
32
|
|
|
30
33
|
def delta(self, duration: Time) -> dict:
|
|
31
34
|
"""
|
|
@@ -92,7 +95,13 @@ class GPUDevice:
|
|
|
92
95
|
"""Returns total energy consumption for this GPU in millijoules (mJ) since the driver was last reloaded
|
|
93
96
|
https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceQueries.html#group__nvmlDeviceQueries_1g732ab899b5bd18ac4bfb93c02de4900a
|
|
94
97
|
"""
|
|
95
|
-
|
|
98
|
+
try:
|
|
99
|
+
return pynvml.nvmlDeviceGetTotalEnergyConsumption(self.handle)
|
|
100
|
+
except pynvml.NVMLError:
|
|
101
|
+
logger.warning(
|
|
102
|
+
"Failed to retrieve gpu total energy consumption", exc_info=True
|
|
103
|
+
)
|
|
104
|
+
return None
|
|
96
105
|
|
|
97
106
|
def _get_gpu_name(self):
|
|
98
107
|
"""Returns the name of the GPU device
|