Energy Estimation
Overview
CI Sizer estimates the energy consumption and carbon footprint of CI/CD runner executions using established industry models. CPU utilization data collected by the collector sidecar (from /proc/stat) is combined with hardware power characteristics and grid carbon intensity to produce per-run energy scores.
These are statistical estimates, not real power measurements. They are suitable for trend analysis, cross-run comparison, and sustainability reporting.
Power Estimation Models
Teads SPECpower Curve (TDP-based)
When the hardware’s Thermal Design Power (TDP) is known and no per-vCPU min/max watt bounds are set, power is estimated using a 4-point piecewise linear interpolation derived from SPECpower benchmark data.
| CPU Utilization | Power Coefficient (x TDP) |
|---|---|
| 0% | 0.12 |
| 10% | 0.32 |
| 50% | 0.75 |
| 100% | 1.02 |
Power(u) = TDP x interpolate(u, [0, 10, 50, 100], [0.12, 0.32, 0.75, 1.02])
Between anchor points, values are linearly interpolated. The 1.02 coefficient at 100% accounts for turbo-boost overshoot above nominal TDP.
Source: Benjamin Davy, Teads Engineering (2021), standardized by the Green Software Foundation Impact Framework.
References:
- Teads Engineering: Estimating AWS EC2 Instances Power Consumption
- Green Software Foundation IF: CPU to Carbon Pipeline
CCF Linear Model (vCPU-based)
When only the vCPU count is known (or when per-vCPU min/max watt bounds are available), the Cloud Carbon Footprint linear interpolation model is used.
Power = vCPUs x (MinWatts + u x (MaxWatts - MinWatts))
Default coefficients (AWS average from SPECpower_ssj2008 benchmarks):
| Parameter | Value | Meaning |
|---|---|---|
MinWatts | 0.74 W/vCPU | Idle power per vCPU |
MaxWatts | 3.50 W/vCPU | Max-load power per vCPU |
When a specific CPU is auto-detected, TDP-derived bounds replace the defaults:
MinWatts = TDP x 0.12 / vCPUs (idle fraction from Teads curve)
MaxWatts = TDP x 1.02 / vCPUs (max fraction from Teads curve)
Reference: Cloud Carbon Footprint Methodology
Model Selection
| Condition | Model Used |
|---|---|
| Profile has per-vCPU min/max watts (both > 0) | CCF Linear |
| Profile has TDP only (no min/max watts) | Teads Curve |
In practice, auto-detected CPUs derive min/max watts from TDP, so the CCF linear model is used for both generic and auto-detected profiles. The Teads curve is only used when a user provides a raw TDP-only hardware profile.
Energy Calculation
Energy_raw (kWh) = Power (W) x Duration (s) / 3600 / 1000
Energy_adjusted (kWh) = Energy_raw x PUE
Power Usage Effectiveness (PUE)
| Parameter | Value |
|---|---|
| Default | 1.3 |
A PUE of 1.3 means the datacenter uses 30% more energy than the IT equipment alone. This is a conservative middle ground:
| Context | Typical PUE |
|---|---|
| Hyperscalers (Google, AWS, Azure) | 1.10–1.18 |
| New datacenter builds (Uptime 2024) | ~1.3 |
| Industry average | 1.56 |
Provider-specific PUEs (from Cloud Carbon Footprint):
| Provider | PUE |
|---|---|
| AWS | 1.135 |
| GCP | 1.1 |
| Azure | 1.125 |
References:
Carbon Intensity
Carbon emissions are calculated as:
Carbon (gCO2eq) = Energy (kWh) x CarbonIntensity (gCO2eq/kWh)
Carbon intensity is resolved through a 3-tier fallback chain. Each tier is tried in order; the first successful response wins. The methodology string always reflects which tier actually supplied the data.
Data Quality Comparison
| Energy Charts (Tier 1) | FfE Projection (Tier 2) | Static Table (Tier 3) | |
|---|---|---|---|
| Data type | Real MW from actual power plants | Modeled scenario projection | Derived seasonal averages |
| Updates | Every 15 minutes | Static per projection year | Never (compiled into binary) |
| Accuracy | Actual grid state right now | Weather-year-2012 estimate | Seasonal/hourly average |
| Zones | 13 EU countries | DE only | 13 EU countries |
| Availability | Sometimes delayed or unavailable | Always available | Always available |
| Reflects today’s weather | ✅ Yes — real wind/solar/demand | ❌ No — same values every year for the same hour | ❌ No — averaged over months |
Tier 1: Energy Charts Real-Time (default)
| Property | Value |
|---|---|
| Source | Fraunhofer Institute for Solar Energy Systems (Fraunhofer ISE) |
| API | https://api.energy-charts.info/public_power?country={cc} |
| Data | Real-time actual generation — MW output from real power plants operating right now |
| Resolution | 15-minute intervals, updated continuously |
| Authentication | None required |
| Cache | In-memory, 15-minute TTL (matching data resolution) |
| Methodology string | energy-charts |
| Supported zones | DE, AT, FR, NL, PL, DK, CH, ES, IT, BE, SE, NO, FI |
Direct carbon intensity calculation: Carbon intensity is calculated directly from the generation mix using IPCC AR5 lifecycle emission factors per fuel type: grid_intensity = Σ(fuel_MW × emission_factor) / Σ(generation_MW). Each 15-minute interval’s generation data is fetched from the /public_power endpoint. For each production type with a known emission factor, the MW output is multiplied by the factor; the weighted sum is divided by total generation to yield gCO₂eq/kWh. Negative values (storage consumption) and non-generation keys (Load, Battery Consumption, etc.) are excluded.
IPCC AR5 lifecycle emission factors used by CI Sizer:
| Fuel Type (Energy Charts name) | gCO₂eq/kWh | Source |
|---|---|---|
| Fossil peat | 1100 | IPCC AR5 |
| Fossil brown coal / lignite | 1054 | IPCC AR5 |
| Fossil hard coal | 888 | IPCC AR5 |
| Fossil coal-derived gas | 850 | IPCC AR5 |
| Fossil oil | 733 | IPCC AR5 |
| Fossil gas | 410 | IPCC AR5 |
| Others | 400 | Conservative estimate |
| Waste | 330 | IPCC AR5 (mixed waste) |
| Biomass | 230 | IPCC AR5 |
| Solar | 45 | IPCC AR5 |
| Geothermal | 38 | IPCC AR5 |
| Other renewables | 30 | IPCC AR5 |
| Hydro Run-of-River | 24 | IPCC AR5 |
| Hydro water reservoir | 24 | IPCC AR5 |
| Hydro pumped storage | 24 | IPCC AR5 |
| Nuclear | 12 | IPCC AR5 |
| Wind offshore | 12 | IPCC AR5 |
| Wind onshore | 11 | IPCC AR5 |
For example, when the grid runs on 5000 MW lignite and 5000 MW gas: (5000×1054 + 5000×410) / 10000 = 732 gCO₂eq/kWh.
This approach calculates intensity directly from the actual fuel dispatch, providing more accurate values than the previous simplified formula.
Reference: Fraunhofer ISE — Energy Charts
Tier 2: FfE Projection Data (first fallback)
| Property | Value |
|---|---|
| Source | Forschungsstelle für Energiewirtschaft (FfE), Munich |
| Data | Modeled projection — 8760 hourly values from the Dynamis energy scenario model, based on weather reference year 2012 |
| Storage | Azure blob storage (no rate limits): ffeopendatastorage.blob.core.windows.net |
| License | CC-BY-4.0 |
| Year selection | Nearest available projection year (2020, 2025, 2030, 2035, 2040, 2045, 2050) |
| Cache | In-memory, 24-hour TTL (data is static per year) |
| Methodology string | ffe-projection |
These are modeled projections, NOT actual measurements. The simulation uses weather reference year 2012 to produce a plausible hourly carbon intensity profile. This means:
- The same hour-of-year always returns the same value, regardless of when you query
- It captures realistic seasonal and diurnal patterns (e.g., midday solar dips, winter peaks)
- It cannot reflect today’s actual wind speed, cloud cover, or demand conditions
Note: FfE projection data is only available for Germany (zone
DE). For other zones, Tier 2 is skipped and the chain falls through directly to Tier 3.
The data is produced under the InDEED research project (Integrating Decentralized Energy Data).
Reference: FfE OpenData (InDEED project)
Tier 3: Static Lookup Table (last resort)
A 192-value lookup table for all 13 supported zones, indexed by season (4), day type (weekday/weekend), and hour (24). Derived from Energy Charts /public_power data (2025–2026, IPCC AR5 emission factors).
| Pattern | Range (gCO2/kWh) | Cause |
|---|---|---|
| Summer midday (10:00–14:00) | 220–265 | High solar generation (DE example) |
| Summer night (00:00–05:00) | 470–525 | Fossil baseload (DE example) |
| Winter (all day) | 350–435 | Flatter, wind-dependent (DE example) |
| Weekend vs. weekday | 10–20% lower | Reduced industrial demand |
Methodology string: static
Methodological basis:
- Kono, J., Ostermeyer, Y. & Wallbaum, H. (2017). “The trends of hourly carbon emission factors in Germany and investigation on relevant consumption patterns for its application.” International Journal of Life Cycle Assessment, 22, 1493–1501. DOI: 10.1007/s11367-017-1277-z
- Holzapfel, P., Bach, V. & Finkbeiner, M. (2023). “Increasing temporal resolution in greenhouse gas accounting of electricity consumption divided into Scopes 2 and 3.” International Journal of Life Cycle Assessment, 28, 1622–1639. DOI: 10.1007/s11367-023-02240-3
Last-Resort Fallback
| Parameter | Value |
|---|---|
| Intensity | 380 gCO2eq/kWh |
Updated from 400 g/kWh in v0.2.x to reflect declining German grid intensity. Per Umweltbundesamt (UBA), the annual average was 386 g/kWh (2023) and 363 g/kWh (2024). The carbon_source field is set to "fallback" to flag this condition.
Reference: Umweltbundesamt — Strom- und Wärmeversorgung in Zahlen
Provider Selection
The --carbon-provider flag (or RUNNER_CARBON_PROVIDER env var) controls which providers are used:
| Value | Behavior | Use Case |
|---|---|---|
energy-charts (default) | Full 3-tier chain: Energy Charts → FfE projection → static table | Best accuracy; requires internet access |
ffe | Legacy alias — creates the same full 3-tier chain as energy-charts | Backward compatibility |
static | Static lookup table only (no external dependencies) | Air-gapped environments, deterministic testing |
Multi-Country Carbon Zones
CI Sizer supports 13 European electricity grid zones for carbon intensity estimation. The zone is configured via the --carbon-zone flag or RUNNER_CARBON_ZONE environment variable (default: DE).
Supported Zones
| Zone | Country | Typical CI (gCO₂eq/kWh) | Notes |
|---|---|---|---|
AT | Austria | ~67 | Hydro-dominated |
BE | Belgium | ~179 | Gas + nuclear mix |
CH | Switzerland | ~42 | Hydro + nuclear |
DE | Germany | ~258 | Mixed (coal/gas/wind/solar) |
DK | Denmark | ~88 | Wind-heavy |
ES | Spain | ~94 | Solar + wind + gas |
FI | Finland | ~59 | Nuclear + hydro + biomass |
FR | France | ~24 | Nuclear-dominated |
IT | Italy | ~206 | Gas-heavy |
NL | Netherlands | ~369 | Gas-dominated |
NO | Norway | ~28 | Hydro-dominated |
PL | Poland | ~505 | Coal-dominated |
SE | Sweden | ~33 | Hydro + nuclear |
Configuration
# French grid (nuclear-dominated, very low CI)
./collector --carbon-zone FR
# Or via environment variable
RUNNER_CARBON_ZONE=PL ./collector
Provider Chain by Zone
The fallback chain differs depending on the selected zone:
- DE (Germany): Energy Charts → FfE blob projection → static (seasonal/weekday table with 192 values)
- All other zones: Energy Charts → static (seasonal/weekday table with 192 values)
FfE projection data (Tier 2) is only available for Germany. For all other zones, the provider chain skips FfE and falls back directly to the static table. All 13 supported zones have full 192-value static tables (4 seasons × 2 day types × 24 hours) derived from Energy Charts data. If all providers fail, the hardcoded 380 gCO₂/kWh fallback is used regardless of zone.
CPU TDP Database
A built-in database of 38 CPU models maps processor names to TDP (Thermal Design Power) values:
| Family | Generations | TDP Range |
|---|---|---|
| Intel Xeon Platinum | Skylake, Cascade Lake, Ice Lake, Sapphire Rapids | 195–350 W |
| Intel Xeon Gold | Various | 165–205 W |
| Intel Xeon Silver | Various | 100–135 W |
| Intel Xeon E5 | Ivy Bridge, Haswell, Broadwell | 115–145 W |
| AMD EPYC Rome | 7000-series | 225–280 W |
| AMD EPYC Milan | 7000-series | 225–280 W |
| AMD EPYC Genoa | 9000-series | 360 W |
| AWS Graviton | Graviton2, Graviton3, Graviton4 | 130–210 W |
| Ampere | Altra, AmpereOne | 160–210 W |
Sources:
- Intel ARK — Intel Xeon processors
- AMD Product Specifications — EPYC processors
- CodeCarbon
cpu_power.csv(MIT license) — cross-validation - Community estimates for AWS Graviton processors (no official TDP published by AWS)
Graviton TDP values (Graviton2 ~130 W, Graviton3 ~180 W, Graviton4 ~210 W) are engineering estimates based on ARM Neoverse power characteristics, not manufacturer specifications.
Confidence Levels
Each energy score includes a confidence field indicating the quality of the estimate:
| Level | Hardware Source | Typical Accuracy |
|---|---|---|
user-provided | User explicitly specified hardware | Depends on user |
auto-detected | CPU model matched in TDP database | ±15–20% |
generic-estimate | Fell back to average cloud defaults | ±50% |
The confidence level reflects the hardware profile quality. Carbon data source quality is captured separately in the carbon_source field (energy-charts, ffe-projection, static, or fallback).
Limitations
| Limitation | Impact |
|---|---|
| Statistical approximation | Power estimates are modeled, not measured from hardware power meters |
| CPU-only power model | Memory, storage, network, and GPU power are not modeled separately |
| Carbon intensity variability | Hourly/15-min data is preferred over annual averages; actual intensity varies by time of day and season |
| Energy Charts emission factors | IPCC AR5 lifecycle emission factors per fuel type are median values; actual plant-level emissions vary, giving ±10–15% uncertainty |
| FfE projection data | Based on the Dynamis energy scenario model; projection years (2025, 2030, etc.) may not match actual grid conditions |
| Graviton/ARM TDP estimates | Not manufacturer specifications; based on ARM Neoverse power characteristics |
| Uniform PUE | Single global default; actual PUE varies by datacenter location, load, and ambient temperature |
| Limited zone support | Carbon intensity available for 13 European zones via Energy Charts (DE, AT, FR, NL, PL, DK, CH, ES, IT, BE, SE, NO, FI). FfE projection data is DE-only. Static fallback covers all 13 zones with full seasonal/weekday/hourly resolution. Unsupported zones use 380 gCO₂/kWh fallback |
| Average utilization | Mean CPU utilization over the run smooths out short spikes |
Verifying the Data
You can query the upstream carbon intensity sources directly to verify what CI Sizer is seeing.
Reading the Methodology String
Each energy score in CI Sizer includes a methodology string like:
ccf-linear+energy-charts+DE+pue-1.30
| Part | Meaning |
|---|---|
ccf-linear | Power model — Cloud Carbon Footprint linear interpolation (vCPUs × watts) |
energy-charts | Carbon intensity source that successfully returned data |
DE | Electricity grid zone |
pue-1.30 | Power Usage Effectiveness multiplier (1.3× datacenter overhead) |
If the carbon source shows ffe-projection or static instead of energy-charts, the system fell back because Energy Charts was unavailable.
Querying Energy Charts (Tier 1)
The Energy Charts API requires lowercase country codes and date-only format (no timestamps):
# German grid generation mix for today (replace dates with today/tomorrow)
curl -s "https://api.energy-charts.info/public_power?country=de&start=2026-05-20&end=2026-05-21" \
| jq '{
timestamps: (.unix_seconds | length),
first: (.unix_seconds[0] | todate),
last: (.unix_seconds[-1] | todate),
fuels: [.production_types[] | .name]
}'
# See actual MW per fuel type at a specific timestamp
curl -s "https://api.energy-charts.info/public_power?country=de&start=2026-05-20&end=2026-05-21" \
| jq '{
time: (.unix_seconds[40] | todate),
generation_MW: [.production_types[] | select(.data[40] != null and .data[40] > 0) | {(.name): (.data[40] | round)}] | add
}'
# French grid (nuclear-dominated, very low carbon)
curl -s "https://api.energy-charts.info/public_power?country=fr&start=2026-05-20&end=2026-05-21" \
| jq '[.production_types[] | .name]'
The response contains unix_seconds[] (15-minute intervals) and production_types[{name, data[]}] with MW output per fuel type. CI Sizer multiplies each fuel’s MW by its IPCC AR5 emission factor and divides by total generation to compute gCO₂eq/kWh.
Important: Replace the dates in the examples with today’s date. The API returns data up to the most recent 15-minute interval.
Computing Grid Carbon Intensity
To replicate the exact calculation CI Sizer performs — weighted average of generation MW × emission factor:
# Compute current German grid carbon intensity (same formula as ci-sizer)
curl -s "https://api.energy-charts.info/public_power?country=de&start=$(date -u +%Y-%m-%d)&end=$(date -u -v+1d +%Y-%m-%d)" | jq '
(.unix_seconds | length - 1) as $idx |
(.unix_seconds[$idx] | todate) as $time |
{"Fossil brown coal / lignite":1054,"Fossil hard coal":888,"Fossil gas":410,
"Fossil oil":733,"Fossil coal-derived gas":850,"Fossil peat":1100,
"Nuclear":12,"Biomass":230,"Geothermal":38,"Wind offshore":12,
"Wind onshore":11,"Solar":45,"Hydro Run-of-River":24,
"Hydro water reservoir":24,"Hydro pumped storage":24,
"Waste":330,"Others":400,"Other renewables":30} as $f |
[.production_types[] | select(.data[$idx]!=null and .data[$idx]>0) |
{name,mw:.data[$idx]} | select($f[.name]!=null) |
{name,mw,w:(.mw*$f[.name])}] as $g |
{timestamp: $time,
grid_carbon_intensity_gCO2_per_kWh: ([$g[]|.w]|add)/([$g[]|.mw]|add)|round,
total_generation_MW: ([$g[]|.mw]|add)|round,
top_contributors: [$g | sort_by(-.w)[0:5][] | {fuel:.name, MW:(.mw|round), share_pct:((.w/([$g[]|.w]|add)*100)*10|round/10)}]}'
This takes the most recent 15-minute interval, multiplies each fuel type’s MW output by its IPCC emission factor, sums the weighted values, and divides by total generation to get gCO₂eq/kWh. The top_contributors field shows which fuels are driving most of the carbon impact.
Note: On Linux, replace
date -v+1dwithdate -d "+1 day". Or simply hardcode tomorrow’s date.
Querying FfE Projection (Tier 2)
The FfE blob contains 8760 hourly projection values for an entire year (DE only):
# Get 2025 projection metadata
curl -s "https://ffeopendatastorage.blob.core.windows.net/opendata/id_opendata_2/id_opendata_2_year_2025.json" \
| jq '[.[] | select(.internal_id == [2,1,1])][0] | {
year: .year,
weather_reference_year: .year_weather,
total_hours: (.values | length),
annual_average_gCO2_per_kWh: (.value * 1000 | round),
unit: "values are in kg/kWh, multiply by 1000 for g/kWh"
}'
# Get projection for a specific hour (e.g., May 20 at 09:00 UTC = hour index 3345)
curl -s "https://ffeopendatastorage.blob.core.windows.net/opendata/id_opendata_2/id_opendata_2_year_2025.json" \
| jq '[.[] | select(.internal_id == [2,1,1])][0] | {
hour_index: 3345,
carbon_intensity_gCO2_per_kWh: (.values[3345] * 1000 | round),
note: "This is a modeled projection, not real-time data"
}'
Note: Hour index = (day_of_year - 1) × 24 + hour_utc. These values are static projections based on weather year 2012 — they will return the same result regardless of when you query them.
Tier 3 (Static Table)
The static fallback table is compiled into the binary — there is no external API to query. It provides 192 values per zone (4 seasons × 2 day types × 24 hours) derived from Energy Charts historical data.
Related Standards
| Standard | Reference |
|---|---|
| Green Software Foundation — Software Carbon Intensity (SCI) | sci-guide.greensoftware.foundation |
| Cloud Carbon Footprint | cloudcarbonfootprint.org |
| SPECpower_ssj2008 | spec.org/power_ssj2008 |
| Green Software Foundation Impact Framework | if.greensoftware.foundation |