Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 23 additions & 11 deletions METHODOLOGY.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,22 @@ Run `greenops-cli --coverage` to see the full instance and region list per provi

### Power Model

GreenOps uses the **linear interpolation model** from the Cloud Carbon Footprint (CCF) methodology:
GreenOps uses the **linear interpolation model** from the Cloud Carbon Footprint (CCF) methodology, extended with memory power draw:

```
W_effective = W_idle + (W_max - W_idle) × utilization
W_cpu = W_idle + (W_max - W_idle) × utilization
W_memory = memory_gb × 0.392 W/GB
W_effective = W_cpu + W_memory
```

Where:
- `W_idle` = idle TDP (watts) from `factors.json`
- `W_max` = maximum TDP (watts) from `factors.json`
- `utilization` = CPU utilisation fraction (default: 0.50, matching CCF baseline)
- `memory_gb` = RAM size from `factors.json`
- `0.392 W/GB` = CCF memory power coefficient (constant, not utilization-dependent)

Memory power draw is **constant** regardless of CPU utilisation. This reflects that DRAM draws near-constant power whether or not it is actively being written to, consistent with CCF v3 methodology.

### Carbon Calculation

Expand All @@ -64,21 +70,27 @@ GCP's 1.10 PUE is the best in class among the three major providers, producing ~

### Worked Example — AWS m5.large in us-east-1 at 50% utilisation

1. **Power:** `W = 6.8 + (20.4 - 6.8) × 0.50 = 13.6W`
2. **Energy:** `13.6W × 1.13 PUE × 730h / 1000 = 11.219 kWh/month`
3. **Carbon:** `11.219 × 384.5 = 4,313.6g CO2e/month`
1. **CPU power:** `W_cpu = 6.8 + (20.4 - 6.8) × 0.50 = 13.6W`
2. **Memory power:** `W_mem = 8GB × 0.392 = 3.136W`
3. **Total:** `W = 13.6 + 3.136 = 16.736W`
4. **Energy:** `16.736W × 1.13 PUE × 730h / 1000 = 13.816 kWh/month`
5. **Carbon:** `13.816 × 384.5 = 5,308.2g CO2e/month`

### Worked Example — Azure Standard_D2s_v3 in eastus at 50% utilisation

1. **Power:** `W = 6.8 + (20.4 - 6.8) × 0.50 = 13.6W`
2. **Energy:** `13.6W × 1.125 PUE × 730h / 1000 = 11.178 kWh/month`
3. **Carbon:** `11.178 × 380.0 = 4,244.2g CO2e/month`
1. **CPU power:** `W_cpu = 6.8 + (20.4 - 6.8) × 0.50 = 13.6W`
2. **Memory power:** `W_mem = 8GB × 0.392 = 3.136W`
3. **Total:** `W = 16.736W`
4. **Energy:** `16.736W × 1.125 PUE × 730h / 1000 = 13.745 kWh/month`
5. **Carbon:** `13.745 × 380.0 = 5,222.9g CO2e/month`

### Worked Example — GCP n2-standard-2 in us-central1 at 50% utilisation

1. **Power:** `W = 6.8 + (20.4 - 6.8) × 0.50 = 13.6W`
2. **Energy:** `13.6W × 1.10 PUE × 730h / 1000 = 10.921 kWh/month`
3. **Carbon:** `10.921 × 340.0 = 3,713.1g CO2e/month`
1. **CPU power:** `W_cpu = 6.8 + (20.4 - 6.8) × 0.50 = 13.6W`
2. **Memory power:** `W_mem = 8GB × 0.392 = 3.136W`
3. **Total:** `W = 16.736W`
4. **Energy:** `16.736W × 1.10 PUE × 730h / 1000 = 13.445 kWh/month`
5. **Carbon:** `13.445 × 340.0 = 4,569.3g CO2e/month`

---

Expand Down
20 changes: 13 additions & 7 deletions dist/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -1968,7 +1968,7 @@ var factors_default = {
// package.json
var package_default = {
name: "greenops-cli",
version: "0.6.0",
version: "0.7.0",
description: "Carbon footprint linting for Terraform plans \u2014 AWS, Azure, and GCP. Analyses infrastructure changes for Scope 2, Scope 3, and water impact. Posts recommendations directly on GitHub PRs.",
main: "dist/index.cjs",
bin: {
Expand Down Expand Up @@ -2241,6 +2241,7 @@ function extractResourceInputs(planFilePath) {
// engine.ts
var HOURS_PER_MONTH = 730;
var GRAMS_PER_KWH = 1e3;
var MEMORY_WATTS_PER_GB = 0.392;
function resolveUtilization(input, ledger) {
if (input.avgUtilization !== void 0 && (input.avgUtilization < 0 || input.avgUtilization > 1)) {
throw new RangeError(`avgUtilization must be between 0 and 1, got ${input.avgUtilization}`);
Expand All @@ -2250,8 +2251,10 @@ function resolveUtilization(input, ledger) {
}
return input.avgUtilization ?? ledger.metadata.assumptions.default_utilization.value;
}
function linearInterpolationWatts(idle, max, utilization) {
return idle + (max - idle) * utilization;
function effectiveTotalWatts(idle, max, utilization, memoryGb) {
const cpuWatts = idle + (max - idle) * utilization;
const memoryWatts = memoryGb * MEMORY_WATTS_PER_GB;
return cpuWatts + memoryWatts;
}
function wattsToScope2Carbon(watts, hours, pue, gridIntensity) {
return watts * pue * hours / GRAMS_PER_KWH * gridIntensity;
Expand Down Expand Up @@ -2333,7 +2336,8 @@ function calculateBaseline(input, ledger = factors_default) {
gridIntensityApplied: gridIntensity,
powerModelUsed: "LINEAR_INTERPOLATION",
embodiedCo2ePerVcpuPerMonthApplied: embodied,
waterIntensityLitresPerKwhApplied: waterIntensity
waterIntensityLitresPerKwhApplied: waterIntensity,
memoryWattsApplied: 0
}
});
const regionData = providerLedger.regions[input.region];
Expand All @@ -2359,10 +2363,11 @@ function calculateBaseline(input, ledger = factors_default) {
);
}
const powerModel = "LINEAR_INTERPOLATION";
const effectiveWatts = linearInterpolationWatts(
const effectiveWatts = effectiveTotalWatts(
instanceData.power_watts.idle,
instanceData.power_watts.max,
utilization
utilization,
instanceData.memory_gb
);
const totalCo2eGramsPerMonth = wattsToScope2Carbon(
effectiveWatts,
Expand All @@ -2388,7 +2393,8 @@ function calculateBaseline(input, ledger = factors_default) {
gridIntensityApplied: regionData.grid_intensity_gco2e_per_kwh,
powerModelUsed: powerModel,
embodiedCo2ePerVcpuPerMonthApplied: instanceData.embodied_co2e_grams_per_month,
waterIntensityLitresPerKwhApplied: regionData.water_intensity_litres_per_kwh
waterIntensityLitresPerKwhApplied: regionData.water_intensity_litres_per_kwh,
memoryWattsApplied: instanceData.memory_gb * MEMORY_WATTS_PER_GB
}
};
}
Expand Down
Loading
Loading