Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0eea792
feat: add telemetry module with Tier 1 payload builder
davidberenstein1957 Apr 29, 2026
0347f01
fix: improve code quality for Task 1 (security, docs, types)
davidberenstein1957 Apr 29, 2026
0ae7783
feat: add Tier 1 telemetry send with session dedup and silent fail
davidberenstein1957 Apr 29, 2026
ed2d3d2
feat: wire Tier 1 telemetry into BaseEmissionsTracker (opt-out via se…
davidberenstein1957 Apr 29, 2026
5c568e7
feat: first version of telemetry
inimaz May 3, 2026
39eef0b
tests: add a test so that client and server do not differ
inimaz May 3, 2026
f2ada76
merge: integrate telemetry backend from PR #1171
davidberenstein1957 May 19, 2026
9e97561
refactor: use TelemetryClient for minimal tracker telemetry
davidberenstein1957 May 19, 2026
453860f
feat: enhance telemetry module with minimal payload builder and impro…
davidberenstein1957 May 19, 2026
954910e
feat: enhance telemetry functionality with new configuration and sess…
davidberenstein1957 May 19, 2026
2a865f6
refactor: enhance telemetry level resolution and configuration handling
davidberenstein1957 May 19, 2026
1ffc45d
Delete docs/plans/2026-05-19-telemetry-configuration.md
davidberenstein1957 May 19, 2026
0a319bc
fix: improve error handling and logging for AMD GPU metrics
davidberenstein1957 May 19, 2026
dcec0c4
feat: enhance telemetry schema and collection methods
davidberenstein1957 May 19, 2026
1ecc237
refactor: update telemetry schema and enhance data collection
davidberenstein1957 May 19, 2026
6c873eb
refactor: streamline telemetry schema and enhance privacy measures
davidberenstein1957 May 19, 2026
e3dca3f
refactor: simplify telemetry framework detection and enhance privacy
davidberenstein1957 May 20, 2026
de3fff1
refactor: simplify telemetry client and enhance telemetry handling
davidberenstein1957 May 20, 2026
5a65220
refactor: overhaul telemetry handling and structure
davidberenstein1957 May 20, 2026
aefea4c
refactor: improve telemetry imports and code formatting
davidberenstein1957 May 20, 2026
adb50c3
feat: introduce telemetry module and enhance telemetry context manage…
davidberenstein1957 May 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,6 @@ tests/test_data/rapl/*
credentials*
.codecarbon.config*
scripts/agent-vm.personal.config.sh

# Added by ggshield
.cache_ggshield
10 changes: 10 additions & 0 deletions carbonserver/carbonserver/api/domain/telemetry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import abc
from uuid import UUID

from carbonserver.api import schemas_telemetry


class Telemetry(abc.ABC):
@abc.abstractmethod
def add_telemetry(self, telemetry: schemas_telemetry.TelemetryCreate) -> UUID:
raise NotImplementedError
104 changes: 104 additions & 0 deletions carbonserver/carbonserver/api/infra/database/telemetry_sql_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""SQLAlchemy models for telemetry data in the CarbonServer API."""

import uuid

from sqlalchemy import JSON, Boolean, Column, DateTime, Float, Integer, String
from sqlalchemy.dialects.postgresql import UUID

from carbonserver.database.database import Base


class Telemetry(Base):
__tablename__ = "telemetry"

id = Column(UUID(as_uuid=True), primary_key=True, index=True, default=uuid.uuid4)
timestamp = Column(DateTime, nullable=False)
telemetry_level = Column(String, nullable=False)

os = Column(String, nullable=True)
country_name = Column(String, nullable=True)
country_iso_code = Column(String, nullable=True)
region = Column(String, nullable=True)
cloud_provider = Column(String, nullable=True)
cloud_region = Column(String, nullable=True)
longitude = Column(Float, nullable=True)
latitude = Column(Float, nullable=True)

cpu_count = Column(Integer, nullable=True)
cpu_physical_count = Column(Integer, nullable=True)
cpu_model = Column(String, nullable=True)
cpu_architecture = Column(String, nullable=True)
gpu_count = Column(Integer, nullable=True)
gpu_model = Column(String, nullable=True)
gpu_driver_version = Column(String, nullable=True)
gpu_memory_total_gb = Column(Float, nullable=True)
ram_total_size_gb = Column(Float, nullable=True)
cuda_version = Column(String, nullable=True)
cudnn_version = Column(String, nullable=True)

python_version = Column(String, nullable=True)
python_implementation = Column(String, nullable=True)
python_executable_hash = Column(String, nullable=True)
python_env_type = Column(String, nullable=True)
codecarbon_version = Column(String, nullable=True)
codecarbon_install_method = Column(String, nullable=True)

total_emissions_kg = Column(Float, nullable=True)
emissions_rate_kg_per_sec = Column(Float, nullable=True)
energy_consumed_kwh = Column(Float, nullable=True)
cpu_energy_kwh = Column(Float, nullable=True)
gpu_energy_kwh = Column(Float, nullable=True)
ram_energy_kwh = Column(Float, nullable=True)
duration_seconds = Column(Float, nullable=True)
cpu_utilization_avg = Column(Float, nullable=True)
gpu_utilization_avg = Column(Float, nullable=True)
ram_utilization_avg = Column(Float, nullable=True)

tracking_mode = Column(String, nullable=True)
api_mode = Column(String, nullable=True)
output_methods = Column(JSON, nullable=True)
hardware_tracked = Column(JSON, nullable=True)
task_tracking_used = Column(Boolean, nullable=True)
decorator_vs_context = Column(String, nullable=True)
measure_power_interval_secs = Column(Float, nullable=True)

hardware_detection_success = Column(Boolean, nullable=True)
rapl_available = Column(Boolean, nullable=True)
gpu_detection_method = Column(String, nullable=True)
first_measurement_time_ms = Column(Float, nullable=True)
tracking_overhead_percent = Column(Float, nullable=True)
errors_encountered = Column(JSON, nullable=True)
warning_count = Column(Integer, nullable=True)

ide_used = Column(String, nullable=True)
notebook_environment = Column(String, nullable=True)
ci_environment = Column(String, nullable=True)
python_package_manager = Column(String, nullable=True)
framework_detected = Column(String, nullable=True)

has_torch = Column(Boolean, nullable=True)
torch_version = Column(String, nullable=True)
has_transformers = Column(Boolean, nullable=True)
transformers_version = Column(String, nullable=True)
has_diffusers = Column(Boolean, nullable=True)
diffusers_version = Column(String, nullable=True)
has_tensorflow = Column(Boolean, nullable=True)
tensorflow_version = Column(String, nullable=True)
has_keras = Column(Boolean, nullable=True)
keras_version = Column(String, nullable=True)
has_pytorch_lightning = Column(Boolean, nullable=True)
pytorch_lightning_version = Column(String, nullable=True)
has_fastai = Column(Boolean, nullable=True)
fastai_version = Column(String, nullable=True)
ml_framework_primary = Column(String, nullable=True)

container_runtime = Column(String, nullable=True)
in_container = Column(Boolean, nullable=True)
host_machine_hash = Column(String, nullable=True)

def __repr__(self):
return (
f'<Telemetry(id="{self.id}", '
f'timestamp="{self.timestamp}", '
f'telemetry_level="{self.telemetry_level}")>'
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Repository implementation for telemetry data using SQLAlchemy."""

import uuid
from contextlib import AbstractContextManager
from uuid import UUID

from dependency_injector.providers import Callable

from carbonserver.api.domain.telemetry import Telemetry
from carbonserver.api.infra.database.telemetry_sql_models import (
Telemetry as SqlModelTelemetry,
)
from carbonserver.api.schemas_telemetry import TelemetryCreate


class SqlAlchemyRepository(Telemetry):
def __init__(self, session_factory) -> Callable[..., AbstractContextManager]:
self.session_factory = session_factory

def add_telemetry(self, telemetry: TelemetryCreate) -> UUID:
with self.session_factory() as session:
db_telemetry = SqlModelTelemetry(
id=uuid.uuid4(),
**telemetry.model_dump(),
)
session.add(db_telemetry)
session.commit()
return db_telemetry.id
31 changes: 31 additions & 0 deletions carbonserver/carbonserver/api/routers/telemetry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""API router for handling telemetry data in the CarbonServer API."""

from uuid import UUID

from dependency_injector.wiring import Provide, inject
from fastapi import APIRouter, Depends
from starlette import status

from carbonserver.api.schemas_telemetry import TelemetryCreate
from carbonserver.api.services.telemetry_service import TelemetryService
from carbonserver.container import ServerContainer

TELEMETRY_ROUTER_TAGS = ["Telemetry"]

router = APIRouter()


@router.post(
"/telemetry",
tags=TELEMETRY_ROUTER_TAGS,
status_code=status.HTTP_201_CREATED,
response_model=UUID,
)
@inject
def add_telemetry(
telemetry: TelemetryCreate,
telemetry_service: TelemetryService = Depends(
Provide[ServerContainer.telemetry_service]
),
) -> UUID:
return telemetry_service.add_telemetry(telemetry)
129 changes: 129 additions & 0 deletions carbonserver/carbonserver/api/schemas_telemetry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""Schemas for telemetry data submitted to the CarbonServer API."""

from datetime import datetime
from enum import Enum
from typing import List, Optional

from pydantic import BaseModel, ConfigDict, Field, model_validator


class TelemetryLevel(str, Enum):
disabled = "disabled"
minimal = "minimal"
extensive = "extensive"


class TelemetryBase(BaseModel):
model_config = ConfigDict(
extra="forbid",
use_enum_values=True,
json_schema_extra={
"example": {
"timestamp": "2026-05-03T12:00:00+00:00",
"telemetry_level": "minimal",
"os": "Linux-5.10.0-x86_64",
"country_name": "France",
"country_iso_code": "FRA",
"cpu_count": 12,
"cpu_model": "Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz",
"python_version": "3.11.5",
"codecarbon_version": "3.0.0",
}
},
)

timestamp: datetime
telemetry_level: TelemetryLevel

os: Optional[str] = None
country_name: Optional[str] = None
country_iso_code: Optional[str] = Field(default=None, min_length=2, max_length=3)
region: Optional[str] = None
cloud_provider: Optional[str] = None
cloud_region: Optional[str] = None
on_cloud: Optional[bool] = None

cpu_count: Optional[int] = Field(default=None, ge=0)
cpu_physical_count: Optional[int] = Field(default=None, ge=0)
cpu_model: Optional[str] = None
cpu_architecture: Optional[str] = None
gpu_count: Optional[int] = Field(default=None, ge=0)
gpu_model: Optional[str] = None
gpu_driver_version: Optional[str] = None
gpu_memory_total_gb: Optional[float] = Field(default=None, ge=0)
ram_total_size_gb: Optional[float] = Field(default=None, ge=0)
cuda_version: Optional[str] = None
cudnn_version: Optional[str] = None

python_version: Optional[str] = None
python_implementation: Optional[str] = None
python_env_type: Optional[str] = None
codecarbon_version: Optional[str] = None
codecarbon_install_method: Optional[str] = None
python_package_manager: Optional[str] = None

total_emissions_kg: Optional[float] = Field(default=None, ge=0)
emissions_rate_kg_per_sec: Optional[float] = Field(default=None, ge=0)
energy_consumed_kwh: Optional[float] = Field(default=None, ge=0)
cpu_energy_kwh: Optional[float] = Field(default=None, ge=0)
gpu_energy_kwh: Optional[float] = Field(default=None, ge=0)
ram_energy_kwh: Optional[float] = Field(default=None, ge=0)
duration_seconds: Optional[float] = Field(default=None, ge=0)
cpu_utilization_avg: Optional[float] = Field(default=None, ge=0, le=100)
gpu_utilization_avg: Optional[float] = Field(default=None, ge=0, le=100)
ram_utilization_avg: Optional[float] = Field(default=None, ge=0, le=100)

tracking_mode: Optional[str] = None
api_mode: Optional[str] = None
output_methods: Optional[List[str]] = None
hardware_tracked: Optional[List[str]] = None
task_tracking_used: Optional[bool] = None
measure_power_interval_secs: Optional[float] = Field(default=None, ge=0)
integration_surface: Optional[str] = None
offline_mode: Optional[bool] = None
save_to_api_enabled: Optional[bool] = None

hardware_detection_success: Optional[bool] = None
rapl_available: Optional[bool] = None
gpu_detection_method: Optional[str] = None

ide_used: Optional[str] = None
notebook_environment: Optional[str] = None
ci_environment: Optional[str] = None
framework_detected: Optional[str] = None

has_torch: Optional[bool] = None
torch_version: Optional[str] = None
has_transformers: Optional[bool] = None
transformers_version: Optional[str] = None
has_diffusers: Optional[bool] = None
diffusers_version: Optional[str] = None
has_tensorflow: Optional[bool] = None
tensorflow_version: Optional[str] = None
has_keras: Optional[bool] = None
keras_version: Optional[str] = None
has_pytorch_lightning: Optional[bool] = None
pytorch_lightning_version: Optional[str] = None
has_fastai: Optional[bool] = None
fastai_version: Optional[str] = None
ml_framework_primary: Optional[str] = None

container_runtime: Optional[str] = None
in_container: Optional[bool] = None

@model_validator(mode="after")
def validate_telemetry_level(self):
if self.telemetry_level == TelemetryLevel.disabled:
raise ValueError("Disabled telemetry must not be submitted")
return self


PRIVATE_TELEMETRY_FIELDS = frozenset(TelemetryBase.model_fields)


class TelemetryCreate(TelemetryBase):
pass


class Telemetry(TelemetryBase):
id: str
16 changes: 16 additions & 0 deletions carbonserver/carbonserver/api/services/telemetry_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Service layer for handling telemetry data in the CarbonServer API."""

from uuid import UUID

from carbonserver.api.infra.repositories.repository_telemetry import (
SqlAlchemyRepository as TelemetrySqlRepository,
)
from carbonserver.api.schemas_telemetry import TelemetryCreate


class TelemetryService:
def __init__(self, telemetry_repository: TelemetrySqlRepository):
self._repository = telemetry_repository

def add_telemetry(self, telemetry: TelemetryCreate) -> UUID:
return self._repository.add_telemetry(telemetry)
12 changes: 12 additions & 0 deletions carbonserver/carbonserver/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
repository_projects,
repository_projects_tokens,
repository_runs,
repository_telemetry,
repository_users,
)
from carbonserver.api.services.auth_context import AuthContext
Expand All @@ -21,6 +22,7 @@
from carbonserver.api.services.project_token_service import ProjectTokenService
from carbonserver.api.services.run_service import RunService
from carbonserver.api.services.signup_service import SignUpService
from carbonserver.api.services.telemetry_service import TelemetryService
from carbonserver.api.services.user_service import UserService
from carbonserver.api.usecases.experiment.project_sum_by_experiment import (
ProjectSumsByExperimentUsecase,
Expand Down Expand Up @@ -63,6 +65,11 @@ class ServerContainer(containers.DeclarativeContainer):
session_factory=db.provided.session,
)

telemetry_repository = providers.Factory(
repository_telemetry.SqlAlchemyRepository,
session_factory=db.provided.session,
)

experiment_repository = providers.Factory(
repository_experiments.SqlAlchemyRepository,
session_factory=db.provided.session,
Expand Down Expand Up @@ -99,6 +106,11 @@ class ServerContainer(containers.DeclarativeContainer):
emission_repository=emission_repository,
)

telemetry_service = providers.Factory(
TelemetryService,
telemetry_repository=telemetry_repository,
)

experiment_service = providers.Factory(
ExperimentService,
auth_context=auth_context,
Expand Down
Loading
Loading