Backend Architecture
The carbon monitoring backend is implemented in ada-carbon-monitoring-api, a FastAPI service that calculates carbon footprint from CPU usage data.
Overview
ada-carbon-monitoring-api/
├── main.py # FastAPI application entry point
├── src/
│ ├── api/carbon/ # API route handlers
│ │ ├── carbon_route.py # Carbon calculation endpoints
│ │ ├── workspace_route.py # Workspace/user/group endpoints
│ │ ├── history_route.py # Historical data endpoints
│ │ └── carbon_schema.py # Pydantic models
│ ├── calculators/ # Calculation logic
│ │ ├── electricity_estimator.py # kWh from CPU seconds
│ │ ├── carbon_calculator.py # gCO2eq from kWh
│ │ └── carbon_equivalency.py # Real-world equivalencies
│ ├── clients/ # External service clients
│ │ ├── prometheus_client.py # Prometheus queries
│ │ ├── mongodb_client.py # MongoDB queries
│ │ ├── register_client.py # ada-db-interface client
│ │ └── carbon_intensity_client.py # UK Grid API
│ ├── models/ # Domain models
│ │ ├── workspace_tracker.py # Main tracking logic
│ │ └── workspace_usage.py # Usage data model
│ └── config.py # Configuration management
├── tests/ # Test suite
└── ada-carbon-monitoring-api.ini # Configuration file
Configuration
The API is configured via ada-carbon-monitoring-api.ini:
[GENERAL]
version = 1.0.0
port = 8000
cors_allowed_origins = http://localhost:3000,https://ada.stfc.ac.uk
[PROMETHEUS]
url = https://host-172-16-100-248.nubes.stfc.ac.uk/
timeout = 120
[CARBON_INTENSITY]
api_url = https://api.carbonintensity.org.uk/intensity
[POWER]
busy_power_w = 12.0
idle_power_w = 1.0
cpu_tdp_w = 100.0
[TESTING]
use_fake_prometheus = false
use_fake_mongodb = false
use_fake_carbon_intensity = false
API Routers
Carbon Router (/carbon)
Core carbon calculation endpoints:
| Endpoint | Method | Description |
|---|---|---|
/carbon/intensity/current | GET | Current UK grid carbon intensity |
/carbon/intensity/forecast | GET | 24/48 hour intensity forecast |
/carbon/calculate | POST | Calculate carbon from CPU seconds |
/carbon/equivalencies | POST | Calculate equivalencies |
/carbon/equivalencies/{gco2eq} | GET | Get equivalencies for value |
/carbon/electricity/estimate | GET | Estimate electricity usage |
Workspace Router (/workspaces)
Workspace carbon tracking:
| Endpoint | Method | Description |
|---|---|---|
/workspaces/active | GET | List active workspaces |
/workspaces/track | POST | Track all active workspaces |
/workspaces/summary | GET | Get summary statistics |
/workspaces/{id} | GET | Get specific workspace usage |
User Router (/users)
Per-user carbon attribution:
| Endpoint | Method | Description |
|---|---|---|
/users/{username}/usage | GET | User’s workspace carbon usage |
/users/{username}/summary | GET | User’s aggregated carbon summary |
Group Router (/groups)
Per-group carbon attribution:
| Endpoint | Method | Description |
|---|---|---|
/groups | GET | List all groups (cloud_project + machine_name) |
/groups/{name}/usage | GET | Group’s carbon usage |
/groups/{name}/summary | GET | Group’s aggregated carbon summary |
History Router (/carbon)
Historical data for charts:
| Endpoint | Method | Description |
|---|---|---|
/carbon/history | GET | Historical data (day/month/year view) |
/carbon/heatmap | GET | Heatmap data (year of daily values) |
Calculators
ElectricityEstimator
Converts CPU seconds to energy (kWh):
class ElectricityEstimator:
def __init__(self, busy_power_w=12.0, idle_power_w=1.0):
self.busy_power_w = busy_power_w
self.idle_power_w = idle_power_w
def estimate_total_kwh(self, busy_seconds, idle_seconds):
watt_seconds = (self.busy_power_w * busy_seconds +
self.idle_power_w * idle_seconds)
return watt_seconds / 3_600_000 # Convert to kWh
CarbonCalculator
Converts energy to carbon emissions:
class CarbonCalculator:
def estimate_carbon_footprint(self, kwh, carbon_intensity_g_per_kwh):
return kwh * carbon_intensity_g_per_kwh # Returns gCO2eq
CarbonEquivalencyCalculator
Converts gCO2eq to relatable equivalencies:
EQUIVALENCIES = {
"miles_driven_car": 400.0, # gCO2eq per mile
"smartphone_charge": 8.22, # gCO2eq per charge
"tree_day": 59.6, # gCO2eq absorbed per tree per day
"streaming_hour": 55.0, # gCO2eq per hour of HD streaming
"kettle_boil": 70.0, # gCO2eq per liter boiled
# ... 18+ total equivalencies
}
Clients
PrometheusAPIClient
Queries CPU metrics from Prometheus:
client = PrometheusAPIClient(prometheus_url="https://...")
# Get CPU usage breakdown
cpu_data = client.get_cpu_usage_breakdown(
timestamp=datetime.now(),
cloud_project_name="IDAaaS",
machine_name="Muon",
host="172.16.100.50"
)
# Returns: {"busy": 1234.5, "idle": 56789.0}
Query used:
increase(node_cpu_seconds_total{
cloud_project_name="IDAaaS",
machine_name="Muon",
instance=~"172.16.100.50.*"
}[1h])
MongoDBClient
Queries workspace and user data:
client = MongoDBClient(
mongo_uri="mongodb://...",
database_name="ada"
)
# Get active workspaces
workspaces = client.get_active_workspaces(timestamp=datetime.now())
# Get user by platform name
user = client.get_user_by_platform_name("aa123456")
CarbonIntensityAPIClient
Queries UK Grid carbon intensity:
client = CarbonIntensityAPIClient()
# Current intensity
current = client.get_current_intensity()
# Returns: {"intensity": 185, "index": "moderate"}
# Forecast
forecast = client.get_forecast(hours=24)
# Returns: {"forecasts": [...]}
WorkspaceTracker
Main orchestration class that combines all components:
class WorkspaceTracker:
def __init__(self, ...):
self.data_client = MongoDBClient(...)
self.prometheus_client = PrometheusAPIClient(...)
self.carbon_client = CarbonIntensityAPIClient()
self.electricity_estimator = ElectricityEstimator(...)
self.carbon_calculator = CarbonCalculator(...)
def track_workspace(self, workspace, timestamp):
# 1. Get CPU data from Prometheus
cpu_data = self.prometheus_client.get_cpu_usage_breakdown(...)
# 2. Calculate energy
energy_kwh = self.electricity_estimator.estimate_total_kwh(...)
# 3. Get carbon intensity
intensity = self.carbon_client.get_current_intensity()
# 4. Calculate carbon
carbon_gco2eq = self.carbon_calculator.estimate_carbon_footprint(...)
# 5. Get equivalencies
equivalencies = self.equivalency_calc.get_top_equivalencies(...)
return WorkspaceUsageEntry(...)
def track_group(self, cloud_project_name, machine_name, timestamp):
# Track aggregated usage for a group (no host filter)
...
Data Models
WorkspaceUsageEntry
class WorkspaceUsageEntry:
workspace_id: str
hostname: str
owner: Optional[str]
timestamp: datetime
# CPU metrics
busy_cpu_seconds_total: float
idle_cpu_seconds_total: float
# Energy (kWh)
busy_usage_kwh: float
idle_usage_kwh: float
total_usage_kwh: float
# Carbon (gCO2eq)
busy_usage_gco2eq: float
idle_usage_gco2eq: float
total_usage_gco2eq: float
carbon_intensity_g_per_kwh: float
# Equivalencies
carbon_equivalencies: dict
# Status
status: str # "initialized", "downloaded", "processed", "complete"
Running the API
# Development
python main.py
# Production with uvicorn
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
API documentation available at:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
Testing
# Run all tests
pytest tests/ -v
# Run with coverage
pytest tests/ --cov=src --cov-report=html
Environment Variables
| Variable | Description | Default |
|---|---|---|
PROMETHEUS_URL | Prometheus server URL | localhost:9090 |
MONGO_URI | MongoDB connection URI | localhost:27017 |
MONGO_DATABASE | MongoDB database name | ada |
USE_FAKE_PROMETHEUS | Use fake Prometheus data | false |
USE_FAKE_MONGODB | Use fake MongoDB data | false |