Skip to content

Commit e1da179

Browse files
authored
feat: custom emissions file (#93)
1 parent 4726834 commit e1da179

7 files changed

Lines changed: 74 additions & 4 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ This extension contributes the following settings:
9090

9191
- `codecarbon.launchOnStartup`: If true, the extension will start tracking the emissions when you open a new window. Defaults to true.
9292
- `codecarbon.notifications`: Notification policy for popups. Use `default` to show start/stop info and recoverable warnings, or `minimal` to only show blocking errors.
93+
- `codecarbon.emissionsFile`: Optional CSV destination path. Set an absolute path (for example in your home directory) to use one central `emissions.csv` across workspaces.
9394

9495
## Contributing
9596

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@
9494
"Only show blocking errors as popups. Use output logs for diagnostics.",
9595
"Show start/stop info popups and warnings for recoverable setup issues."
9696
]
97+
},
98+
"codecarbon.emissionsFile": {
99+
"default": "",
100+
"description": "Optional path for emissions CSV output. Use an absolute path for a central file shared across workspaces, or a relative path resolved from the workspace.",
101+
"type": "string"
97102
}
98103
}
99104
},
@@ -157,4 +162,4 @@
157162
"prettier": "^3.8.1",
158163
"typescript": "^5.9.3"
159164
}
160-
}
165+
}

src/scripts/tracker.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,35 @@
55
import time
66
import signal
77
import json
8+
import argparse
9+
import os
810
from codecarbon import EmissionsTracker
911
from metrics_utils import to_number
1012

1113

1214
# Redirect stderr to stdout
1315
sys.stderr = sys.stdout
1416

15-
# Force single-run mode to prevent parallel trackers writing to the same outputs.
16-
tracker = EmissionsTracker(allow_multiple_runs=False)
17+
def create_tracker(emissions_file_path=None):
18+
"""Create tracker in single-run mode, optionally overriding CSV destination."""
19+
if not emissions_file_path:
20+
return EmissionsTracker(allow_multiple_runs=False)
21+
22+
normalized = os.path.abspath(os.path.expanduser(emissions_file_path))
23+
output_dir = os.path.dirname(normalized)
24+
output_file = os.path.basename(normalized)
25+
if not output_dir:
26+
output_dir = "."
27+
28+
return EmissionsTracker(
29+
allow_multiple_runs=False,
30+
output_dir=output_dir,
31+
output_file=output_file,
32+
)
33+
1734
running = True # Global variable to control the loop
1835
DEFAULT_UPDATE_INTERVAL = 5.0
36+
tracker = None
1937

2038

2139
def get_measure_power_secs():
@@ -143,6 +161,11 @@ def signal_handler(sig, frame):
143161
print("Starting the tracker...")
144162
command = sys.argv[1].lower()
145163
if command == "start":
164+
parser = argparse.ArgumentParser(add_help=False)
165+
parser.add_argument("--emissions-file", dest="emissions_file", default="")
166+
args, _ = parser.parse_known_args(sys.argv[2:])
167+
tracker = create_tracker(args.emissions_file or None)
168+
146169
# Register the signal handlers for graceful shutdown
147170
signal.signal(signal.SIGINT, signal_handler) # Handle Ctrl+C
148171
signal.signal(signal.SIGTERM, signal_handler) # Handle termination signal

src/services/trackerService.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ChildProcess, spawn } from 'child_process';
77
import * as vscode from 'vscode';
88
import * as path from 'path';
99
import * as fs from 'fs/promises';
10+
import * as os from 'os';
1011
import { LogService } from './logService';
1112
import { PythonService } from './pythonService';
1213
import { ConfigService } from '../utils/config';
@@ -59,10 +60,14 @@ export class TrackerService {
5960

6061
const pythonPath = ConfigService.getPythonPath();
6162
const workspacePath = ConfigService.getWorkspaceFolderPath();
63+
const emissionsFilePath = this.resolveEmissionsFilePath(ConfigService.getEmissionsFilePath(), workspacePath);
6264
if (workspacePath) {
6365
this.logService.log(`Using workspace as tracker cwd for CodeCarbon config discovery: ${workspacePath}`);
6466
await this.cleanupStaleWorkspaceTracker(workspacePath);
6567
}
68+
if (emissionsFilePath) {
69+
this.logService.log(`Using configured emissions CSV path: ${emissionsFilePath}`);
70+
}
6671
this.logService.log('Run policy: single active CodeCarbon tracker per workspace (recommended).');
6772

6873
try {
@@ -74,7 +79,11 @@ export class TrackerService {
7479

7580
// Start the tracker process
7681
this.stoppingRequested = false;
77-
this.pythonProcess = spawn(pythonPath, [TRACKER_SCRIPT, 'start'], workspacePath ? { cwd: workspacePath } : undefined);
82+
const trackerArgs = [TRACKER_SCRIPT, 'start'];
83+
if (emissionsFilePath) {
84+
trackerArgs.push('--emissions-file', emissionsFilePath);
85+
}
86+
this.pythonProcess = spawn(pythonPath, trackerArgs, workspacePath ? { cwd: workspacePath } : undefined);
7887
if (workspacePath && this.pythonProcess.pid) {
7988
await this.writePidFile(workspacePath, this.pythonProcess.pid);
8089
}
@@ -240,4 +249,21 @@ export class TrackerService {
240249
this.logService.parseLogs(routed.parsePayload);
241250
}
242251
}
252+
253+
private resolveEmissionsFilePath(configuredPath: string | undefined, workspacePath?: string): string | undefined {
254+
if (!configuredPath) {
255+
return undefined;
256+
}
257+
if (configuredPath.startsWith('~')) {
258+
const withoutTilde = configuredPath.slice(1).replace(/^[/\\]/, '');
259+
return path.resolve(os.homedir(), withoutTilde);
260+
}
261+
if (path.isAbsolute(configuredPath)) {
262+
return configuredPath;
263+
}
264+
if (workspacePath) {
265+
return path.resolve(workspacePath, configuredPath);
266+
}
267+
return path.resolve(configuredPath);
268+
}
243269
}

src/utils/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ export class ConfigService {
5353
return resolveNotificationMode((section, defaultValue) => config.get(section, defaultValue));
5454
}
5555

56+
public static getEmissionsFilePath(): string | undefined {
57+
const config = this.getConfiguration();
58+
const value = config.get<string>(CONFIGURATION_KEYS.EMISSIONS_FILE, '').trim();
59+
return value || undefined;
60+
}
61+
5662
public static getWorkspaceFolderPath(): string | undefined {
5763
const folder = vscode.workspace.workspaceFolders?.[0];
5864
return folder?.uri.fsPath;

src/utils/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const CONFIGURATION_KEYS = {
2121
INSTALL_STRATEGY: 'installStrategy',
2222
CUSTOM_PIP_ARGS: 'customPipArgs',
2323
NOTIFICATIONS: 'notifications',
24+
EMISSIONS_FILE: 'emissionsFile',
2425
} as const;
2526

2627
export const MESSAGES = {

tests/python/test_tracker_policy.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ def test_tracker_disables_multiple_runs(self):
1313
"tracker.py must enforce allow_multiple_runs=False to prevent duplicate tracker sessions.",
1414
)
1515

16+
def test_tracker_supports_emissions_file_override(self):
17+
tracker_script = pathlib.Path("src/scripts/tracker.py").read_text(encoding="utf-8")
18+
self.assertIn(
19+
'--emissions-file',
20+
tracker_script,
21+
"tracker.py should support overriding the CSV destination via --emissions-file.",
22+
)
23+
1624

1725
if __name__ == "__main__":
1826
unittest.main()

0 commit comments

Comments
 (0)