Overview
Every Joule here comes from the runner’s own power sensor, read about ten times a second, minus its idle baseline, then split across the pipeline’s steps by time overlap. The screen below is one real capture, exactly as it was attributed.
Energy & Carbon per Run
All scenarios of a run summed
CH1 AMD · CPU package, on its own scale
CH2 M2 · CPU cluster, on its own scale
CH1 AMD · average energy per run, by stage
CPU package power, on its own scale: never read against CH2.
CH2 M2 · average energy per run, by stage
CPU cluster power, on its own scale: never read against CH1.
Platform Comparison
Each runner is measured in its own electrical domain: the whole CPU package on the AMD PC, the CPU cluster only on the M2. Read the two side by side; their Joules are never compared.
CH1 · AMD Windows runner CPU package power, AMD µProf
CH2 · Apple M2 runner CPU cluster power, powermetrics (excludes GPU and ANE)
CH1 AMD · average energy per run, by stage
CPU package power, on its own scale: never read against CH2.
CH2 M2 · average energy per run, by stage
CPU cluster power, on its own scale: never read against CH1.
Carbon Impact
Software Carbon Intensity (SCI) tracking
Carbon Intensity Over Time
CH1 AMD · carbon per stage
Grams follow this runner’s own energy domain: its own scale.
CH2 M2 · carbon per stage
Grams follow this runner’s own energy domain: its own scale.
Pipeline Runs
Full energy_metrics table data
Span Attribution
Request-level energy mapping
CH1 AMD · energy by span
The twelve spans that used the most energy, on this runner’s own scale.
CH2 M2 · energy by span
The twelve spans that used the most energy, on this runner’s own scale.
Database Explorer
Raw SQLite tabular views
Methodology — how a pipeline's energy is attributed
Read in three levels. Level 1 is the whole idea and takes a minute. Level 2 is the procedure and the arithmetic. Level 3 is the corpus, the quality gates and the raw artefacts — the evidence that any number here can be re-derived.
Level 1: the idea
Everything on this page follows from one problem and one trick. If you read only this much, you have the method.
The problem: nobody can price a test suite in Joules
A CI pipeline burns real electricity. You can already find out what the whole machine drew, and how long each step took. What no existing tool will tell you is how many Joules that one test suite cost — and you cannot optimise what you cannot attribute.
| What you can use today | What it gives you | What it cannot tell you |
|---|---|---|
docker stats, top | CPU %, memory | Joules — CPU % is not energy |
| Cloud invoice | Money per instance-hour | Which job, stage or test spent it |
RAPL / MSR, powermetrics | Real Joules, whole CPU socket | Which operation caused them |
| Tracing / APM (OpenTelemetry) | Milliseconds per operation | Any energy figure at all |
One side of the split knows energy but not structure. The other knows structure but not energy. Nothing joins them — and inside a container it gets worse, because the workload cannot see the host's power counters at all.
The trick: join them on the clock
Run the hardware power sensor and the operation timeline at the same time, against the same clock. Then walk the power trace one sample at a time and ask a question a database person would recognise — this is an interval join:
“During this 100 ms of measured energy, which operations were running, and for how much of it?”
Split that sample's Joules between them in proportion to the overlap. Do it for every sample and each operation accumulates its own energy total. Nothing is estimated from a model — every Joule attributed was physically measured at the socket first.
The operations are timed from outside the workload, by the CI job that launches them, so no source file in any profiled project is modified. That is what makes the method applicable to software you do not own.
See it happen — one sample being split
Press play. The cursor sweeps the power trace one sample at a time; watch the Joules drop into whichever operations were running. This single figure is the method.
Press Play, or drag the slider, to step through the samples.
Illustrative trace with a 4 s window so individual samples are visible; real runs are 60 s or longer. The arithmetic shown is exactly what analyst_agent.deconvolve() performs.
Three rules that keep the number honest
The interval join above is the easy part. These three corrections are what make the result defensible — each one exists because leaving it out produces a specific, quantifiable lie.
Subtract the machine's own idle draw
Attribute only max(0, P − P_idle(t)) — the surplus above what the machine burns doing nothing.
Without it: an operation that merely exists for 10 s gets billed for 10 s of the operating system, screen, and background daemons. Long operations would look expensive purely for being long.
The idle level is measured per run — the median of the 100 samples before the workload and the 100 after it, interpolated between the two, so thermal drift is tracked rather than assumed away.
Integrate over the real sample spacing
Multiply each sample's surplus watts by dt, that sample's own measured gap — never by a hard-coded 0.1 s.
Without it: silent undercounting. Profilers do not hold their requested interval under load, and the error is invisible because the output still looks like a plausible number of Joules.
Real extract from a run: timestamps …59:358, …59:563, …59:669. Those gaps are 205 ms and 106 ms, not 100. Assuming 0.1 s would have undercounted that window by about a third.
Cap concurrent shares at 100 % of a sample
Scale the shares by min(1, dt / Σoverlaps) whenever several operations run at once.
Without it: you invent energy. Two operations each covering 60 ms of a 100 ms sample would take 0.6 + 0.6 = 120 % of it — attributing 3.0 J out of a 2.5 J sample.
The min(1, …) matters in the other direction too: when operations do not fill a sample, the remainder must stay unattributed rather than being inflated to fill the gap.
The check that all three rules worked. Every measured Joule is either attributed to an operation or left in E_infrastructure — the container start-up, image pulls and scheduling that no single operation owns. That remainder can never go negative, which means the method cannot quietly invent or lose energy. It is the one property worth testing, so it is asserted on every run and re-checked on every stored row.
Level 2: how one measurement is produced
Concept settled, here is the actual procedure: how a single run is timed, how its idle baseline is removed, and the exact arithmetic that turns watts into per-operation Joules.
Profiling timeline (animated)
10 s pre-baseline → workload → 10 s cooldown. The profiler runs end-to-end at a requested 100 ms interval. Press play to see how data fills the window.
Baseline subtraction — see it happen
The raw power trace mixes the workload's own energy with the runner's idle background. Phase 3 strips the background out. Step through the four operations the analyst performs on every CSV.
Click 1. Raw trace to begin.
Synthetic trace for illustration — the analyst applies the same four operations to every real timechart.csv. Real runs at ResearchData/cpudata/ request 100 ms sampling and use the live runner's own idle level. The drift shown here is deliberately modest (about 7 % of the run's energy), so this illustrates a run that passes the quality screen rather than one the screen would flag.
Phase 1: Hardware Acquisition
AMD µProf: 100 ms requested, socket0-package-power (W) — whole CPU package
Apple powermetrics: 100 ms requested, --samplers cpu_power → the CPU Power line only — CPU cluster, excluding GPU and ANE
The two sensors read different electrical domains, so absolute Joules are never compared across platforms — only within-platform ratios are.
100 ms is the requested interval; the interval actually achieved is measured per run and stored as mean_sample_interval_ms, and every sample is integrated over its own spacing. Nyquist still limits resolution of sub-200 ms spikes — explicitly disclosed.
Phase 3: Signal Deconvolution
P_post = median(100 samples after END)
P_idle(t) = P_pre + (P_post − P_pre) ⋅ progress(t)
E_software = Σ max(0, P_t − P_idle(t)) ⋅ dt_t
The baseline is a median (robust to idle spikes), taken twice and linearly interpolated, so thermal drift across the workload is tracked rather than assumed away. dt_t is each sample's real spacing, not a hard-coded 0.1 s.
Phase 4: Temporal Correlation
E_t = ΔP_t ⋅ dt_t
overlap_ratio = span_overlap_in_sample / dt_t
share_scale = min(1, dt_t / Σ overlaps)
E_span += E_t × overlap_ratio × share_scale
Only the baseline-subtracted surplus ΔP is attributable — idle power is not a span's fault. share_scale caps concurrent spans at 100 % of a sample, which is what makes conservation hold.
Conservation: E_Total = Σ E_Spans + E_Infrastructure, with E_Infrastructure ≥ 0 — true by construction and asserted in test/test_attribution.py.
Conservation — how we know
This is a claim with a failure history, so it is stated with one. Conservation is not an assumption of the method; it is a property that share_scale makes hold, and it did not hold before that factor existed.
| Period | Rows | Violations |
|---|---|---|
| before 2026-09-17 | 277 | 12 |
| on/after 2026-09-17 | 223 | 0 |
The 12 violations (bookstack 7, thelounge 3, fider 2, dated 12–16 Sep 2026) are detected by the spans_exceed_total rule and excluded from every model and report. They are not deleted — the original telemetry is in archived-telemetry/, so they can be re-derived under the corrected algorithm rather than re-measured.
Two independent checks: 12 unit tests assert the identity on synthetic spans including the concurrent case, and the read-time screen re-checks it against the stored numbers for every row.
Validation Packet
Full methodology audit, validity threats, and reviewer checklist:
-
methodology/data_collection_validation.md— full packet -
methodology/data_collection_validation_summary.md— 2–3 page summary -
methodology/bias_remediation_log.md— v4 audit history
Level 3: what has been measured, and how bad data is kept out
A method is only as good as the corpus it produced. This level covers what has actually been profiled, the gates that stop unusable data from being believed, and the raw artefacts kept for every run so any number here can be re-derived.
Pick a scenario
Each CI/CD stage is profiled with the same baseline/workload/cooldown protocol, on two architectures. The walkthrough below follows BookStack in detail; it is one of five profiled projects, and the protocol is identical for all of them.
| Project | Stack | Runs | Scenarios profiled |
|---|---|---|---|
| Loading from the live database… | |||
Scenario names differ per project because each one runs that project's own CI commands — no source file in any profiled repository is modified. default is the original Node.js proof-of-concept, retained because its spans are synthetic and therefore independently verifiable.
The 7 pipeline gates — click any gate to expand
Every run passes through these gates in order. The first five block bad data from entering the research database; Screen then decides what is fit to be used, because some defects are only visible once the energy has been computed. Govern is what closes the loop and schedules the next run.
What we keep — click an artefact to see a real sample
Per scenario we persist 4–5 files. Together they let anyone reconstruct, audit, and re-process the run from scratch.
W3C trace context — the seam between power data and span data
Hover any part of the traceparent below to see its role.
Artifacts
Raw pipeline ZIP files available in MinIO
Database Schema & Relations
Loading schema…Relationship Explorer
Pick a pipeline run to see how one energy_metrics row links to its child span_metrics rows (joined on project_name · run_id · scenario).
Table Data (all rows)
Download Full Dataset (CSV)Download = one unified dataset (energy_metrics joined to span_metrics), not separate files.
| Project | Run ID | Scenario | Prefix (bucket area) | Size | Last Modified | Download |
|---|---|---|---|---|---|---|
| Loading... | ||||||
CSV Viewer
Drop any CSV to inspect it. Files are parsed in your browser only — nothing is uploaded.
Drag & drop a CSV file here
or click to browse
Governance Agent
Distributed FIPA agents over RabbitMQ decide when a pipeline runs (grid carbon) and where (each runner's energy quota and the job's deadline). The runners price bids from their own measured history — the ML Energy Predictor page holds that estimator's accuracy evidence.
Agent Platform
Every agent is its own process with its own RabbitMQ queue, and they talk in FIPA ACL messages. The one shortcut: the governance agent reads each task's energy history straight from the attribution agent's database tables. This list is read live from the Directory Facilitator, where each agent registers with a 3-minute lease.
Grid Carbon at Each Decision
Dashed: the deferral threshold (median of the previous 14 days). Amber points: jobs held for a cleaner hour.
What the Runners Bid: Quota Share
Share of each runner's daily energy quota the job would leave used. Runners are compared on this and on finish time — never on Joules: AMD µProf measures the whole CPU package, powermetrics only the M2 CPU cluster.
Governance Audit Trail
Click a row to see the agents' conversation. Energy is the winning runner's, in its own measurement domain.
| Date | Project | Priority | Decision | Status | Winner | Quota | Predicted | Measured | Error |
|---|---|---|---|---|---|---|---|---|---|
| Loading... | |||||||||
Dry-Run Negotiation
Runs the real agents in-process (in-memory transport) on the live history. Nothing is dispatched; the decision is stored as simulated.
Deadlines: urgent 2 h and never held for carbon; normal and batch 14 h (up to 12 h held + 2 h to run).
Energy Regression Alerts — recorded with each result
Spans the agent flagged in a governed run's measured result (or in spans supplied to a dry run), against the history at that moment. A historical record: it does not change as the corpus grows. For the detector's verdict on the corpus as it stands now, see Flagged Outlier Spans.
| Date | Job | Flagged Span | Ratio vs Median | Robust z |
|---|---|---|---|---|
| Loading... | ||||
Run a Governed Job — team devices only
Asks the governance agent to run a pipeline, as python -m agents submit does: the runners bid, the winner starts the workflow, and the measured result and its energy-gate verdict appear in the tables on this page. Only approved team devices can submit, over Tailscale and with the admin token, and every attempt is logged.
Checking access…
Recent submissions
| Time | Device | Project | Priority | PR | Kind | Outcome | Detail |
|---|---|---|---|---|---|---|---|
| No submissions yet | |||||||
Energy Gate — task-level verdicts
After a run, every task is judged against its own main-branch history, and a pull request gets one decision (PASS, WARN, BLOCK, RE-MEASURE, IMPROVED or ACCEPTED). The last column is what the whole-run total alone would have said about the same run: the whole-run (A1) comparison. Click a row for the agents' conversation.
| Date | Project | Runner | PR | Decision | Flagged tasks | Extra per run | Whole-run view (A1) |
|---|---|---|---|---|---|---|---|
| Loading... | |||||||
How the Governance Agent Works
Five kinds of agent, four FIPA protocols, one RabbitMQ broker.
Who talks to whom
The Directory Facilitator, governance and attribution agents run on the VPS; a runner agent runs on each runner machine, where it can see whether its GitHub runner is idle or busy. Agents find each other in the directory (FIPA Request), runners fetch their measured history (Query) and are told of every new attributed run (Subscribe), and jobs are awarded by Contract Net. Messages carry a conversation id, so every decision can be replayed.
When: carbon
From Electricity Maps: a normal or batch job is held while the grid is above the median of the last 14 days, re-checked every 30 minutes, and never held more than 12 hours. Urgent jobs are never held. Timing is the only lever that reduces carbon.
Where: quota and deadline
Each runner bids the share of its daily quota the job would use (the quota is in that runner's own units, by default 4 pipelines a day) and when it would finish. Lowest U = 0.6·quota share + 0.4·time to finish ÷ time to deadline wins. A runner refuses if it has no history, no quota left, cannot meet the deadline, or is offline.
Afterwards: measured result
The winning runner dispatches the project's workflow on its own machine (a PR's own branch for a PR job), waits for the attribution agent to analyse every stage, and reports the measured energy. The energy gate then judges every task against its own main-branch history and gives the PR one verdict, as a GitHub status and comment. A run is atomic, so timing only uses its total; the gate is where request-level data decides.
Proof 3: Pre-Execution Energy Predictor
Energy estimates made before a job runs, and regression detection against each span's own history
Predictor Comparison
R² on unseen runs (cross-validation grouped by run)
Why the earlier R² ≈ 0.85 was not a real prediction
Stored energy is exactly software_tax_w × duration_s, and both values are only known after a job has run. A model given those inputs rebuilds the answer (R² = 1.0 by plain multiplication) instead of predicting it. The predictors above only use what is known before execution.
Live Energy & Carbon Sandbox
The bid each runner would make for a job, from its measured history
Live Span Anomaly Scorer
Compare a span's energy with past runs of the same span (flag at robust z ≥ 3.5 and ≥ 1.2× the median)
Flagged Outlier Spans — recomputed now
Measured spans far above the other runs of the same project, span and platform. Recalculated from the screened history on every load, so this list changes as runs accumulate. For what the agent flagged at the moment it decided, see Energy Regression Alerts.
| Span / Operation | Duration | Energy (J) | Robust z |
|---|---|---|---|
| Loading anomaly dataset... | |||
Analyst Agent Log
Live journalctl output of analyst-agent.service on the VPS — the agent that ingests telemetry from MinIO and writes the attributed energy. Once the agent platform is installed this shows green-mas-platform.service instead, which carries the attribution, governance and Directory Facilitator agents' logs; each decision's full message exchange is on the Governance Agent page.
Awaiting connection...
Settings & About
Display Settings
-
Timezone:
Database Stats
- Loading...
Research Team
- Project: A Multi-Agent Framework for Request-Level Energy Attribution
- Member 1: M.M.N.D. Seneviratne (TG/2021/1017)
- Member 2: B.D.D. Devendra (TG/2021/1038)
- Supervisor: Dr. Chandana Pushpakumara
Governance Live
The agents, the process each one runs in, and every message they send through RabbitMQ.
Governance Live is for team devices
Checking this device…
Live traffic
| Time (UTC) | From → to | Message | Protocol | Detail |
|---|