Repository navigation
feat: add SDK telemetry into emissions tracker - #1200
davidberenstein1957 wants to merge 36 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1200 +/- ##
==========================================
+ Coverage 93.66% 94.08% +0.42%
==========================================
Files 50 55 +5
Lines 5591 5853 +262
==========================================
+ Hits 5237 5507 +270
+ Misses 354 346 -8 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@inimaz, before merging, we should still add the hardcoded experiment and project for the codecarbon api. |
Add CLI helper and interactive-flow tests, cover coordinate rounding in ApiClient, and extend collect environment probes to satisfy Codecov patch and project thresholds on PR #1200. Co-authored-by: Cursor <cursoragent@cursor.com>
inimaz
left a comment
There was a problem hiding this comment.
Thanks for this PR @davidberenstein1957 . I have left some comments
| raise typer.BadParameter(str(error)) from error | ||
|
|
||
|
|
||
| def resolve_config_path(config: Optional[Path], *, create: bool = False) -> Path: |
There was a problem hiding this comment.
Maybe this is not needed. get_hierarchical_config handles it?
There was a problem hiding this comment.
Kept on purpose: set has to pick one file to write to, while get_hierarchical_config only merges what it reads.
e102d91 to
fd7a63b
Compare
Verdict: 🔧 Request changesMaintainer direction on the telemetry policy (@benoit-cty): telemetry should be opt-out by default, and the user should be asked the question explicitly. A suggested implementation:
Must fix:
Housekeeping: Nits:
|
|
Made the changes in cd8ccf6: opt-out with a one-time prompt/notice, stop() non-blocking, experiment_id reverted, null coords, atexit join, docs list every field. Merged master, redid config.py on top of get_hierarchical_config, refreshed the description. |
|
@benoit-cty @inimaz I'd like to move ahead with a simpler version of this PR and get it merged, then build on it. What changes:
Three things I need from you before this can be deployed:
Tier 2, the token question, retention and the endpoint hardening move to #1429. |
|
Thanks @davidberenstein1957 ! Good idea to start small and iterate.
I did some fixes, with Opus 5.5. Review fixes: telemetry collection & CLIFour fixes on the package side of the telemetry feature. The server side (migration, rate limiter, schema, repository) needed no change. 1. Don't import
|
|
@inimaz could you deploy the server side of this PR to api.codecarbon.io before the next package release? It needs the alembic migration Decisions made: telemetry stays on by default at |
Telemetry is an open ingestion endpoint: remove the x-api-token check on POST /telemetry, the client-side telemetry api_key/experiment_id settings, the public run summary (post_public_summary) and the ApiClient deadline change that only existed for it. Remove the extensive tier from client, server, CLI and docs; levels left are minimal and disabled. Drop latitude and longitude from the payload, schemas and SQL model. Non-201 responses now log at debug: with no key gate every default install posts, so a 404 warning would reach every user of an older server. Tests default CODECARBON_TELEMETRY_LEVEL=disabled so the suite never posts to the real endpoint.
Remove every field the SDK never fills (run metrics, framework flags and versions, host/executable hashes, error counters, usage diagnostics) from the client schema, the server schema and the SQL model. extra="forbid" now does the job of the minimal-tier allowlist validator, and every free text field is capped at 256 characters. No list fields remain.
Create the trimmed telemetry table through alembic instead of a dedicated create_all call at startup.
The payload describes the environment, which does not change within a process, so only the first stop() of a run lasting at least one second sends it. Sub-second runs still do not count, so a quick smoke run does not use up the single send. POST /telemetry is open, so cap it per client IP (60 requests a minute, 429 beyond). The limit is in-process; with several API instances it needs a proxy or Redis limit instead.
…evel Legacy 'extensive' still maps to minimal, but any other unparseable value (including privacy-intent strings like off/false/none/0) now falls back to disabled instead of minimal, so a typo or misunderstood value can never cause telemetry to be sent.
Wrap thread creation/start in try/except so stop() can never crash from telemetry; _sent is now only set once a thread has actually started (under the lock, so concurrent stop() calls stay atomic), and a failed start no longer permanently suppresses later sends. Also reset _sent and rebuild the lock after fork via os.register_at_fork, guarded for platforms without it, so a forked child can still send its own one-per-process telemetry.
Reduces precision of the one identifying field in the minimal payload; a row still shows usage trends over time without pinning a process to the exact second it ran.
FastAPI runs sync path operations in a threadpool, so concurrent requests could race past the len(hits) >= RATE_LIMIT check in _rate_limited and both get admitted. Guard the check-and-record with a lock. Also documents, next to --forwarded-allow-ips=*, that it trusts X-Forwarded-For from any peer and so the client IP the limiter keys on can be spoofed without a real reverse proxy in front (kept as-is; maintainer decision).
…roblems pg_isready timing out silently fell through to migrating a database that was never ready. The API startup loop could also silently succeed against something else already bound to :8008, or keep polling after uvicorn had already died. Now: fail if pg_isready never succeeds, fail if the port is already in use before starting uvicorn, and fail (with the log) if the API process exits or never comes up.
…f sensitive information' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Since `telemetry set` writes the global config by default, every caller of resolve_config_path passed an explicit --config, so its local/global lookup was unreachable. Use the given path directly; write_telemetry_level already creates a missing file. `telemetry status` now reads the config files with read_config_file, so the get_config_file_settings helper added to core/config.py is no longer needed. This answers the review question on whether the CLI needs its own lookup. Also fix the --config help text, which still said "local then global" and "creates ./.codecarbon.config". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JfRKgUd281URY6fMyNAQmh
The retention trigger deletes rows whose `timestamp` is older than 3 years, but that timestamp comes from the client. A client with a future clock would store rows that are never purged, breaking the documented 3-year retention. The server now treats naive values as UTC, caps the value at the current time and truncates it to the hour, so the hour truncation the docs promise also holds whatever the client sends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JfRKgUd281URY6fMyNAQmh
docs/how-to/telemetry.md promises to list every field sent. Fail the suite if a field is added to TelemetryCreate without being documented, or documented without being sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JfRKgUd281URY6fMyNAQmh
tests/cli/test_telemetry_cli.py already has the same check (test_monitor_rejects_invalid_telemetry_level). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JfRKgUd281URY6fMyNAQmh
- configuration.md and reference/api.md called telemetry "optional" without saying it is on by default at `minimal`. - reference/api.md said OfflineEmissionsTracker takes the same parameter, but offline mode never sends telemetry whatever it is set to. - reference/cli.md said `--config` defaults to "local then global"; `set` writes the global file, `status` merges both, the wizard asks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JfRKgUd281URY6fMyNAQmh
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JfRKgUd281URY6fMyNAQmh
442e781 to
81d0a9a
Compare
|
Rebased onto master (squashed fd7a63b+cd8ccf6 and the two master merges, tree unchanged; conflicts in emissions_tracker.py, test_monitor.py, testutils.py kept both sides). Alembic chain OK: master added no migrations,
Generated by Claude Code |
Description
CodeCarbon can now send a small, anonymous report about the environment it runs in, so we can see which platforms and versions people use. It is a reduced first version of #1106. Tier 2 (
extensive) and endpoint hardening move to #1429.What users see
minimallevel. Until someone picks a level, every run prints a short notice to stderr explaining what is sent and how to turn it off. The notice stops once a level is chosen.codecarbon configandcodecarbon monitorask which level to use.codecarbon telemetry set disabled(applies in every directory),CODECARBON_TELEMETRY_LEVEL=disabled, ortelemetry_level=on the tracker.OfflineEmissionsTracker,codecarbon monitor --offline) never sends anything.What is sent
docs/how-to/telemetry.md. Rows contain no IP address and are deleted automatically after 3 years.When
Settings
CODECARBON_TELEMETRY_LEVEL, thentelemetry_levelin.codecarbon.config, then the default. An oldextensivevalue falls back tominimal; an empty or unknown value (off,false,0) meansdisabled.telemetry_api_url) and does not follow the dashboard'sapi_endpoint.Server
POST /telemetryneeds no token. It rejects unknown fields, caps strings at 256 characters, and returns 429 above 60 requests a minute per IP.20260927_add_telemetry. It creates the table, or, where the table from feat: first version of telemetry #1171 already exists, drops the removed columns, keeps existing rows and convertstimestampto a time-zone-aware column. It also adds an index ontimestampand a trigger that deletes rows older than 3 years on insert, so retention needs no separate job.Related Issue
Part of #1106. Follow-up: #1429.
Motivation and Context
The project has no view of how CodeCarbon is used. This sends the smallest useful set of environment fields, with a clear notice, a one-line opt-out and a documented field list. A token shipped inside an open-source package would be public and would also allow writes to
/runsand/emissions, so the endpoint takes no token and relies on validation and rate limiting.How Has This Been Tested?
uv run task test-packageanduv run task test-api-unit: all passed.codecarbon/core/telemetry/, the CLI, the router and the migration.scripts/e2e_telemetry.shagainst a real Postgres and a local API: two processes produce exactly two rows with only the documented columns.telemetrytable and a row: upgrade keeps the row, downgrade restores the old columns, upgrade again works.carbonserver/tests/database/test_telemetry_retention_db.pyrepeats this whenDATABASE_URLpoints at a Postgres.Screenshots (if appropriate):
N/A
Before deploying
--forwarded-allow-ips=*, so the per-IP limit can be bypassed with a forgedX-Forwarded-For, and the limit is per process. Tracked in Telemetry follow-up: tier 2, endpoint hardening, retention #1429.Types of changes
AI Usage Disclosure
Please refer to docs/how-to/ai-policy.md for detailed guidelines on how to disclose AI usage in your PR. Accurately completing this section is mandatory.
Checklist:
Commits since the original PR