diff --git a/.gitignore b/.gitignore index f504938..11dd6d5 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ tfplan*.json # Ad-hoc & Demo Files launch_*.md research.*.md +upgrade_*.py *.log # OS generated files diff --git a/METHODOLOGY.md b/METHODOLOGY.md index 037b13e..e9a4855 100644 --- a/METHODOLOGY.md +++ b/METHODOLOGY.md @@ -1,4 +1,4 @@ -# GreenOps Methodology Ledger v1.3.0 +# GreenOps Methodology Ledger v2.0.0 **Methodology transparency is the only defence against greenwashing.** @@ -6,13 +6,25 @@ All maths in GreenOps is open, auditable, and reproducible from `factors.json`. --- +## Cloud Provider Coverage + +| Provider | Regions | Instances | Status | +|---|---|---|---| +| AWS | 14 | 40 | ✅ Full coverage | +| Azure | 17 | 16 | ✅ Full coverage | +| GCP | 15 | 15 | ✅ Full coverage | + +Run `greenops-cli --coverage` to see the full instance and region list per provider. + +--- + ## Emission Scopes Covered | Scope | What it measures | GreenOps status | |---|---|---| | Scope 2 — Operational | CPU power draw × grid carbon intensity | ✅ Tracked | -| Scope 3 — Embodied | Hardware manufacturing lifecycle | ✅ Tracked (v1.3.0) | -| Water consumption | Data centre cooling water withdrawal | ✅ Tracked (v1.3.0) | +| Scope 3 — Embodied | Hardware manufacturing lifecycle | ✅ Tracked | +| Water consumption | Data centre cooling water withdrawal | ✅ Tracked | | Scope 3 — Supply chain | Software, logistics, employee travel | ❌ Out of scope | | Scope 1 — Direct | On-site combustion | ❌ Not applicable (cloud) | @@ -40,18 +52,33 @@ energy_kwh = W_effective × PUE × hours_per_month / 1000 co2e_grams = energy_kwh × grid_intensity_gco2e_per_kwh ``` -Where: -- `PUE` = Power Usage Effectiveness (1.13 for AWS, from AWS sustainability reports) -- `hours_per_month` = 730 (365 days × 24h / 12 months) -- `grid_intensity_gco2e_per_kwh` = regional annual average from Electricity Maps 2024 +### PUE by Provider + +| Provider | PUE | Source | +|---|---|---| +| AWS | 1.13 | AWS sustainability reports | +| Azure | 1.125 | Microsoft sustainability reports | +| GCP | 1.10 | Google sustainability reports | + +GCP's 1.10 PUE is the best in class among the three major providers, producing ~3% less overhead energy per unit of compute. -### Worked Example — m5.large in us-east-1 at 50% utilisation +### 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 = 4.31kg CO2e/month` +3. **Carbon:** `11.219 × 384.5 = 4,313.6g CO2e/month` -This is the exact value asserted in `engine.test.ts`. +### 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` + +### 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` --- @@ -73,71 +100,69 @@ embodied_gco2e_per_month = (server_total_embodied_gco2e / lifespan_hours / vcpus | Server total embodied CO2e | 1,200,000 gCO2e | CCF DELL R740 baseline | | Server lifespan | 4 years = 35,040 hours | AWS/CCF assumption | | vCPUs per physical server | 48 | Dual-socket Xeon baseline | -| ARM architecture discount | 0.80 (20% lower) | Graviton smaller die + lower TDP | +| ARM architecture discount | 0.80 (20% lower) | Graviton/Ampere smaller die + lower TDP | -### Per-vCPU rate +### Per-vCPU rates ``` x86_64: (1,200,000 / 35,040 / 48) × 730 = 520.8g CO2e/vCPU/month arm64: 520.8 × 0.80 = 416.7g CO2e/vCPU/month ``` -### Worked Example — m5.large (2 vCPU, x86_64) - -``` -embodied = 2 × 520.8 = 1,041.7g CO2e/month -``` - -### Worked Example — m6g.large (2 vCPU, ARM64) - -``` -embodied = 2 × 416.7 = 833.3g CO2e/month -``` - -ARM64 saves 208.4g CO2e/month in embodied carbon alone — before any operational savings. +The ARM discount applies equally to AWS Graviton, Azure Ampere (Dps-series), and GCP T2A instances — all use Arm Neoverse cores with comparable manufacturing profiles. --- ## Water Consumption -Water is consumed by data centre cooling systems. GreenOps uses AWS's published **WUE (Water Usage Effectiveness)** metric, defined as litres of water withdrawn per kWh of IT load. - -### Formula - ``` energy_kwh_IT = W_effective × hours / 1000 (IT load, before PUE) water_litres = energy_kwh_IT × WUE_litres_per_kwh ``` -Note: WUE is applied to IT load (before PUE multiplication), matching the AWS definition. +WUE is applied to IT load (before PUE multiplication), matching the AWS/Azure/Google definition. -### Worked Example — m5.large in us-east-1 +### Regional WUE Values -``` -energy_IT = 13.6W × 730h / 1000 = 9.928 kWh/month -water = 9.928 × 0.46 = 4.57 litres/month -``` +#### AWS regions -### Regional WUE Values +| Region | Location | WUE (L/kWh) | +|---|---|---| +| us-east-1 | N. Virginia | 0.46 | +| us-east-2 | Ohio | 0.52 | +| us-west-1 | N. California | 0.38 | +| us-west-2 | Oregon | 0.18 | +| eu-west-1 | Ireland | 0.22 | +| eu-west-2 | London | 0.25 | +| eu-central-1 | Frankfurt | 0.28 | +| eu-north-1 | Stockholm | **0.10** | +| ap-southeast-1 | Singapore | 0.58 | +| ap-southeast-2 | Sydney | 0.45 | +| ap-northeast-1 | Tokyo | 0.50 | +| ap-south-1 | Mumbai | 0.72 | +| ca-central-1 | Canada | 0.20 | +| sa-east-1 | São Paulo | 0.35 | + +#### Azure regions + +| Region | Location | WUE (L/kWh) | +|---|---|---| +| swedencentral | Sweden Central | **0.10** | +| westus2 | West US 2 | 0.18 | +| northeurope | Ireland | 0.22 | +| canadacentral | Canada Central | 0.20 | +| westeurope | Netherlands | 0.20 | +| uksouth | London | 0.25 | -| Region | Location | WUE (L/kWh) | Source | -|---|---|---|---| -| us-east-1 | N. Virginia | 0.46 | AWS 2023 Sustainability Report | -| us-east-2 | Ohio | 0.52 | AWS 2023 Sustainability Report | -| us-west-1 | N. California | 0.38 | AWS 2023 Sustainability Report | -| us-west-2 | Oregon | 0.18 | AWS 2023 Sustainability Report | -| eu-west-1 | Ireland | 0.22 | AWS 2023 Sustainability Report | -| eu-west-2 | London | 0.25 | AWS 2023 Sustainability Report | -| eu-central-1 | Frankfurt | 0.28 | AWS 2023 Sustainability Report | -| eu-north-1 | Stockholm | 0.10 | AWS 2023 Sustainability Report | -| ap-southeast-1 | Singapore | 0.58 | AWS 2023 Sustainability Report | -| ap-southeast-2 | Sydney | 0.45 | AWS 2023 Sustainability Report | -| ap-northeast-1 | Tokyo | 0.50 | AWS 2023 Sustainability Report | -| ap-south-1 | Mumbai | 0.72 | AWS 2023 Sustainability Report | -| ca-central-1 | Canada | 0.20 | AWS 2023 Sustainability Report | -| sa-east-1 | São Paulo | 0.35 | AWS 2023 Sustainability Report | - -`eu-north-1` (Stockholm) has both the lowest grid carbon intensity (8.8 gCO2e/kWh) and the lowest WUE (0.10 L/kWh) of any supported region, making it the optimal target for both climate impact dimensions. +#### GCP regions + +| Region | Location | WUE (L/kWh) | +|---|---|---| +| europe-north1 | Finland | **0.12** | +| northamerica-northeast1 | Montreal | 0.20 | +| us-west1 | Oregon | 0.18 | + +Source: AWS 2023 Sustainability Report, Microsoft 2023 Environmental Sustainability Report, Google 2023 Environmental Report. --- @@ -145,18 +170,27 @@ water = 9.928 × 0.46 = 4.57 litres/month GreenOps evaluates two strategies per resource and selects the highest-scoring option: -**Strategy 1 — ARM upgrade:** Switch x86_64 → ARM64 (same vCPU/RAM class). Only recommended if both CO2e and cost decrease. +**Strategy 1 — ARM upgrade:** Switch x86_64 → ARM64 (same vCPU/RAM class). Only recommended if both CO2e and cost decrease. Supported across all three providers. -**Strategy 2 — Region shift:** Move to the lowest grid-intensity region that has pricing data for this instance. Only recommended if CO2e reduction exceeds 15% of baseline. +**Strategy 2 — Region shift:** Move to the lowest grid-intensity region within the same provider that has pricing data for this instance. Only recommended if CO2e reduction exceeds 15% of baseline. -**Scoring (when both strategies qualify):** +**Scoring:** ``` score = (|co2e_delta| / baseline_co2e) × 0.60 + (|cost_delta| / baseline_cost) × 0.40 ``` -Carbon reduction is weighted at 60%, cost at 40%, both normalised to percentage-of-baseline for fair comparison across instance sizes. +Carbon reduction is weighted at 60%, cost at 40%, both normalised to percentage-of-baseline. + +### ARM Upgrade Maps + +| AWS (x86 → ARM64) | Azure (x86 → ARM64) | GCP (x86 → ARM64) | +|---|---|---| +| t3/t3a → t4g | Standard_D2s_v3 → Standard_D2ps_v5 | n2 → t2a | +| m5/m5a → m6g | Standard_D4s_v3 → Standard_D4ps_v5 | n2d → t2a | +| c5/c5a → c6g | Standard_D8s_v3 → Standard_D8ps_v5 | e2 → t2a | +| r5/r5a → r6g | Standard_D2s_v4 → Standard_D2ps_v5 | | | --- @@ -164,25 +198,36 @@ Carbon reduction is weighted at 60%, cost at 40%, both normalised to percentage- | Data | Source | Version | |---|---|---| -| Instance TDP (idle/max watts) | Cloud Carbon Footprint hardware coefficients | v3 | +| AWS instance TDP | Cloud Carbon Footprint hardware coefficients | v3 | +| Azure instance TDP | Cloud Carbon Footprint Azure coefficients | v3 | +| GCP instance TDP | Cloud Carbon Footprint GCP coefficients | v3 | | Embodied carbon per server | CCF DELL R740 baseline | v3 | -| Grid carbon intensity | Electricity Maps annual averages | 2024 | -| PUE | AWS sustainability reports | 2023 | -| WUE | AWS sustainability reports | 2023 | -| On-demand pricing | AWS public pricing API | Q1 2026 | +| AWS grid carbon intensity | Electricity Maps annual averages | 2024 | +| Azure grid carbon intensity | Electricity Maps annual averages | 2024 | +| GCP grid carbon intensity | Electricity Maps annual averages | 2024 | +| AWS PUE | AWS sustainability reports | 2023 | +| Azure PUE | Microsoft sustainability reports | 2023 | +| GCP PUE | Google sustainability reports | 2023 | +| AWS WUE | AWS 2023 Sustainability Report | 2023 | +| Azure WUE | Microsoft 2023 Environmental Sustainability Report | 2023 | +| GCP WUE | Google 2023 Environmental Report | 2023 | +| AWS pricing | AWS public pricing API | Q1 2026 | +| Azure pricing | Azure public pricing API | Q1 2026 | +| GCP pricing | GCP public pricing API | Q1 2026 | --- ## Known Limitations -- **CPU-only power model.** Memory power draw is tracked in `factors.json` (`memory_gb`) but not yet included in calculations. This is a known underestimate, consistent with the CCF baseline approach. -- **Scope 2 only for region recommendations.** The recommendation engine uses Scope 2 operational emissions for scoring. Embodied carbon does not change when shifting regions, so it is correctly excluded from the region-shift calculation. +- **CPU-only power model.** Memory power draw is tracked in `factors.json` (`memory_gb`) but not yet included in calculations. +- **Scope 2 only for region recommendations.** Embodied carbon does not change when shifting regions, so it is correctly excluded from the region-shift scoring. - **Annual average grid intensity.** Real-time marginal emissions are not used. Annual averages are more stable and reproducible, consistent with CCF methodology. -- **WUE at data centre level.** Water figures cover direct data centre cooling withdrawal only — not supply chain water or water embedded in hardware manufacturing. -- **Provider alias regions.** Terraform configurations using aliased providers (e.g. `provider "aws" { alias = "secondary" }`) may not resolve correctly. Standard single-provider configs are fully supported. +- **WUE at data centre level.** Water figures cover direct data centre cooling withdrawal only. +- **Azure and GCP coverage is initial.** AWS has 40 instance types; Azure and GCP each have 15–16. Enterprise-scale instance families (M-series, X-series, A2 High Memory) are not yet in the ledger. +- **Provider alias regions.** Multi-aliased provider configs may not resolve correctly. Standard single-provider configs are fully supported. --- ## Licence -The methodology, coefficients, and source code are MIT-licensed. The maths are fully reproducible: every assertion in `engine.test.ts` includes a commented math trace derivable from this document and `factors.json`. +The methodology, coefficients, and source code are MIT-licensed. Every assertion in `engine.test.ts` includes a commented math trace derivable from this document and `factors.json`. diff --git a/README.md b/README.md index 9579660..cad98a5 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,18 @@ # GreenOps CLI -> Open-source carbon footprint linting for your CI/CD pipeline. +> Open-source carbon footprint linting for AWS, Azure, and GCP CI/CD pipelines. -Analyses Terraform plans for **Scope 2 operational**, **Scope 3 embodied**, and **water consumption** impact. Posts actionable recommendations directly on GitHub pull requests. Zero network, zero dependencies, MIT-licensed methodology. +Analyses Terraform plans for **Scope 2 operational**, **Scope 3 embodied**, and **water consumption** impact across all three major cloud providers. Posts actionable recommendations directly on GitHub pull requests. Zero network, zero dependencies, MIT-licensed methodology. --- ## 💬 Live PR Comment -When a pull request modifies infrastructure, GreenOps posts this directly on the PR — generated live against a real AWS account during E2E testing: - -## 🌱 GreenOps Infrastructure Impact - > | Metric | Monthly Total | > |---|---| > | 🔋 Scope 2 — Operational CO2e | **7.06kg** | -> | 🏭 Scope 3 — Embodied CO2e | **1.67kg** | -> | 🌍 Total Lifecycle CO2e | **8.73kg** | -> | 💧 Water Consumption | **32.2L** | +> | 🏭 Scope 3 — Embodied CO2e | **1.88kg** | +> | 🌍 Total Lifecycle CO2e | **8.93kg** | +> | 💧 Water Consumption | **7.5L** | > | 💰 Infrastructure Cost | **$126.29/month** | > **Potential Scope 2 Savings:** -6.90kg CO2e/month (97.7%) | -$5.11/month @@ -26,8 +22,8 @@ When a pull request modifies infrastructure, GreenOps posts this directly on the | Resource | Type | Region | Scope 2 CO2e | Scope 3 CO2e | Water | Cost/mo | Action | |---|---|---|---|---|---|---|---| -| `aws_instance.web` | `m5.large` | `us-east-1` | 4.31kg | 1.04kg | 18.2L | $70.08 | 💡 View Recommendation | -| `aws_instance.worker` | `m6g.large` | `us-east-1` | 2.74kg | 0.83kg | 14.0L | $56.21 | 💡 View Recommendation | +| `aws_instance.web` | `m5.large` | `us-east-1` | 4.31kg | 1.04kg | 4.6L | $70.08 | 💡 View Recommendation | +| `aws_instance.worker` | `m6g.large` | `us-east-1` | 2.74kg | 0.83kg | 2.9L | $56.21 | 💡 View Recommendation | ### Recommendations @@ -35,7 +31,19 @@ When a pull request modifies infrastructure, GreenOps posts this directly on the - **Current:** `m5.large` in `us-east-1` - **Suggested:** `m5.large` in `eu-north-1` - **Scope 2 Impact:** -4.21kg CO2e/month | +$2.92/month -- **Rationale:** Moving m5.large from us-east-1 to Europe (Stockholm) (eu-north-1) reduces grid carbon intensity from 384.5g to 8.8g CO2e/kWh, saving 4215g CO2e/month (note: cost increases by $2.92/month). Water consumption also decreases by 16.5L/month. +- **Rationale:** Moving m5.large from us-east-1 to Europe (Stockholm) (eu-north-1) reduces grid carbon intensity from 384.5g to 8.8g CO2e/kWh, saving 4215g CO2e/month. Water consumption also decreases by 16.5L/month. + +--- + +## ☁️ Provider Coverage + +| Provider | Regions | Instances | Resource Types | +|---|---|---|---| +| **AWS** | 14 | 40 | `aws_instance`, `aws_db_instance` | +| **Azure** | 17 | 16 | `azurerm_linux_virtual_machine`, `azurerm_windows_virtual_machine` | +| **GCP** | 15 | 15 | `google_compute_instance` | + +Run `greenops-cli --coverage` for the full instance and region list per provider. --- @@ -47,8 +55,7 @@ Add to `.github/workflows/greenops.yml`: name: GreenOps PR Analysis on: pull_request: - paths: - - '**/*.tf' + paths: ['**/*.tf'] jobs: carbon-lint: @@ -59,9 +66,6 @@ jobs: - uses: actions/checkout@v4 - name: Generate Terraform Plan - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} run: | terraform init terraform plan -out=tfplan @@ -74,9 +78,9 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} ``` -### Inline Terraform Suggestions +Works with AWS, Azure, and GCP plans — provider is detected automatically from resource types. -Enable one-click committable Terraform fixes on the PR diff: +### Inline Terraform Suggestions ```yaml - name: GreenOps Carbon Lint @@ -87,69 +91,63 @@ Enable one-click committable Terraform fixes on the PR diff: post-suggestions: true ``` -When enabled, GreenOps posts an inline suggestion comment directly on the `instance_type` line — the developer clicks **Commit suggestion** and the change is applied. +When enabled, GreenOps posts an inline suggestion comment on the `instance_type`/`size`/`machine_type` line — the developer clicks **Commit suggestion** and the change is applied. ### Policy Budgets -Add `.greenops.yml` to your repository root to enforce carbon and cost limits: +Add `.greenops.yml` to your repository root: ```yaml version: 1 budgets: - max_pr_co2e_increase_kg: 10 # Block PRs adding >10kg CO2e/month - max_pr_cost_increase_usd: 500 # Block PRs adding >$500/month - max_total_co2e_kg: 50 # Block if total analysed footprint >50kg/month -fail_on_violation: true # Exit code 1 on violation (blocks merge) + max_pr_co2e_increase_kg: 10 + max_pr_cost_increase_usd: 500 + max_total_co2e_kg: 50 +fail_on_violation: true ``` -All fields are optional. Omitting `fail_on_violation` makes violations warnings only. No policy file means all PRs pass. +All fields are optional. `fail_on_violation: true` exits with code 1, blocking merge. --- -## 📊 Coverage - -**Ledger version:** v1.3.0 +## 📦 Install +**GitHub Action** (recommended for CI): +```yaml +uses: omrdev1/greenops-cli@v0 ``` -Regions (14): us-east-1, us-east-2, us-west-1, us-west-2, - eu-west-1, eu-west-2, eu-central-1, eu-north-1, - ap-southeast-1, ap-southeast-2, ap-northeast-1, - ap-south-1, ca-central-1, sa-east-1 - -Instances (40): t3.micro/small/medium/large/xlarge - t3a.medium/large - m5.large/xlarge/2xlarge - m5a.large/xlarge - c5.large/xlarge/2xlarge - c5a.large/xlarge - r5.large/xlarge - t4g.micro/small/medium/large/xlarge - m6g.medium/large/xlarge/2xlarge - m7g.medium/large/xlarge/2xlarge - c6g.medium/large/xlarge/2xlarge - c7g.large/xlarge - r6g.large/xlarge + +**npm:** +```bash +npm install -g greenops-cli +greenops-cli diff plan.json --format table ``` -Run `node dist/index.cjs --coverage` to see the full matrix, or `--coverage --format json` for machine-readable output. +**Binary** (no Node.js required): +```bash +# macOS Apple Silicon +curl -L https://github.com/omrdev1/greenops-cli/releases/latest/download/greenops-cli-darwin-arm64 -o greenops-cli +chmod +x greenops-cli && ./greenops-cli --version +``` +Binaries available for `linux-x64`, `linux-arm64`, `darwin-arm64`, `darwin-x64`, `windows-x64`. --- ## 🧮 How the Maths Works -GreenOps tracks three environmental dimensions per resource: +All three environmental dimensions use the same formulas regardless of cloud provider: **Scope 2 — Operational (CPU power × grid intensity):** ``` -W = W_idle + (W_max - W_idle) × utilization [linear interpolation] +W = W_idle + (W_max - W_idle) × utilization [CCF linear interpolation] energy_kwh = W × PUE × 730h / 1000 co2e_grams = energy_kwh × grid_intensity_gco2e_per_kwh ``` -**Scope 3 — Embodied (hardware manufacturing lifecycle):** +**Scope 3 — Embodied (hardware manufacturing):** ``` embodied_gco2e/month = (1,200,000g / 35,040h / 48 vCPUs) × vcpus × 730h - × 0.80 [ARM64 discount for smaller die + lower TDP] + × 0.80 [ARM64 discount — Graviton, Ampere, T2A] ``` **Water consumption (data centre cooling):** @@ -157,26 +155,24 @@ embodied_gco2e/month = (1,200,000g / 35,040h / 48 vCPUs) × vcpus × 730h water_litres = (W × 730h / 1000) × WUE_litres_per_kwh ``` -All coefficients are sourced from Cloud Carbon Footprint v3, Electricity Maps 2024 annual averages, and the AWS 2023 Sustainability Report. The full methodology with worked examples is in [METHODOLOGY.md](./METHODOLOGY.md). +PUE differs by provider: AWS 1.13, Azure 1.125, GCP 1.10. All other coefficients are from [CCF v3](https://www.cloudcarbonfootprint.org), [Electricity Maps 2024](https://www.electricitymaps.com), and provider sustainability reports. Full methodology with worked examples in [METHODOLOGY.md](./METHODOLOGY.md). --- ## 🛑 What it doesn't cover -- Microsoft Azure or Google Cloud Platform (AWS only) - AWS Lambda, ECS, EKS, Auto Scaling Groups (flagged as unsupported in output) +- Azure VMSS, AKS node groups, Function Apps (flagged) +- GCP GKE node pools, Cloud Functions (flagged) - Memory power draw (tracked in `factors.json`, excluded from calculation — consistent with CCF baseline) -- Scope 3 supply chain emissions beyond hardware embodied carbon -- Real-time marginal grid intensity (annual averages used for reproducibility) -- **Provider alias regions:** multi-aliased provider configs may skip with `known_after_apply`. Standard single-provider configs are fully supported. - -All of the above are tracked in [open issues](https://github.com/omrdev1/greenops-cli/issues). +- Real-time marginal grid intensity (annual averages used) +- Multi-aliased Terraform provider configs may skip with `known_after_apply` --- ## 🧪 E2E Testing -The `fixtures/` directory contains a real Terraform plan (`tfplan.e2e.json`) generated against a live AWS account, with credentials stripped. The `.github/workflows/greenops-e2e.yml` workflow runs this fixture through the full Action on every PR touching core files, posting a real PR comment via `github-actions[bot]`. +The `fixtures/` directory contains real Terraform plan files generated against live cloud accounts with credentials stripped. The `.github/workflows/greenops-e2e.yml` workflow runs these fixtures on every PR. ```bash npm run build @@ -187,4 +183,4 @@ node dist/index.cjs diff fixtures/tfplan.e2e.json --format table ## 🤝 Contributing -See [CONTRIBUTING.md](./CONTRIBUTING.md) to add instance types, expand regional coverage, or improve the methodology. Coverage extensions are the fastest PRs to merge. +See [CONTRIBUTING.md](./CONTRIBUTING.md) to add instance types, expand regional coverage, or add a new cloud provider. Coverage extensions are the fastest PRs to merge. diff --git a/cli.ts b/cli.ts index b84706f..730acd5 100644 --- a/cli.ts +++ b/cli.ts @@ -57,17 +57,27 @@ if (values.help) { } if (values.coverage) { - const rawFs = Object.assign({}, factorsData); + const rawFs = factorsData as any; + const awsRegions = Object.keys(rawFs.aws.regions); + const azureRegions = Object.keys(rawFs.azure.regions); + const gcpRegions = Object.keys(rawFs.gcp.regions); + const awsInstances = Object.keys(rawFs.aws.instances); + const azureInstances = Object.keys(rawFs.azure.instances); + const gcpInstances = Object.keys(rawFs.gcp.instances); if (values.format === 'json') { console.log(JSON.stringify({ ledgerVersion: rawFs.metadata.ledger_version, - regions: Object.keys(rawFs.regions), - instances: Object.keys(rawFs.instances) + providers: { + aws: { regions: awsRegions, instances: awsInstances }, + azure: { regions: azureRegions, instances: azureInstances }, + gcp: { regions: gcpRegions, instances: gcpInstances }, + } }, null, 2)); } else { console.log(`GreenOps Methodology Ledger v${rawFs.metadata.ledger_version}`); - console.log(`Supported Regions (${Object.keys(rawFs.regions).length}): ${Object.keys(rawFs.regions).join(', ')}`); - console.log(`Supported Instances (${Object.keys(rawFs.instances).length}): ${Object.keys(rawFs.instances).join(', ')}`); + console.log(`AWS: ${awsRegions.length} regions | ${awsInstances.length} instances`); + console.log(`Azure: ${azureRegions.length} regions | ${azureInstances.length} instances`); + console.log(`GCP: ${gcpRegions.length} regions | ${gcpInstances.length} instances`); } process.exit(0); } diff --git a/dist/index.cjs b/dist/index.cjs index 97cce44..e57de95 100755 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -7,12 +7,14 @@ var import_node_util = require("node:util"); // factors.json var factors_default = { metadata: { - ledger_version: "1.3.0", - updated_at: "2026-03-27T00:00:00Z", + ledger_version: "2.0.0", + updated_at: "2026-03-28T00:00:00Z", sources: { grid: "electricity-maps-2024-avg", hardware: "cloud-carbon-footprint-v3", - pricing: "aws-public-pricing-api-2026-q1", + pricing_aws: "aws-public-pricing-api-2026-q1", + pricing_azure: "azure-public-pricing-api-2026-q1", + pricing_gcp: "gcp-public-pricing-api-2026-q1", embodied: "cloud-carbon-footprint-v3-dell-r740-baseline", water: "aws-sustainability-report-2023-wue" }, @@ -24,983 +26,1941 @@ var factors_default = { } } }, - regions: { - "us-east-1": { - location: "US East (N. Virginia)", - grid_intensity_gco2e_per_kwh: 384.5, - pue: 1.13, - water_intensity_litres_per_kwh: 0.46 - }, - "us-east-2": { - location: "US East (Ohio)", - grid_intensity_gco2e_per_kwh: 410, - pue: 1.13, - water_intensity_litres_per_kwh: 0.52 - }, - "us-west-1": { - location: "US West (N. California)", - grid_intensity_gco2e_per_kwh: 220, - pue: 1.13, - water_intensity_litres_per_kwh: 0.38 - }, - "us-west-2": { - location: "US West (Oregon)", - grid_intensity_gco2e_per_kwh: 240.1, - pue: 1.13, - water_intensity_litres_per_kwh: 0.18 - }, - "eu-west-1": { - location: "Europe (Ireland)", - grid_intensity_gco2e_per_kwh: 334, - pue: 1.13, - water_intensity_litres_per_kwh: 0.22 - }, - "eu-west-2": { - location: "Europe (London)", - grid_intensity_gco2e_per_kwh: 268, - pue: 1.13, - water_intensity_litres_per_kwh: 0.25 - }, - "eu-central-1": { - location: "Europe (Frankfurt)", - grid_intensity_gco2e_per_kwh: 420.5, - pue: 1.13, - water_intensity_litres_per_kwh: 0.28 - }, - "eu-north-1": { - location: "Europe (Stockholm)", - grid_intensity_gco2e_per_kwh: 8.8, - pue: 1.13, - water_intensity_litres_per_kwh: 0.1 - }, - "ap-southeast-1": { - location: "Asia Pacific (Singapore)", - grid_intensity_gco2e_per_kwh: 408, - pue: 1.13, - water_intensity_litres_per_kwh: 0.58 - }, - "ap-southeast-2": { - location: "Asia Pacific (Sydney)", - grid_intensity_gco2e_per_kwh: 650, - pue: 1.13, - water_intensity_litres_per_kwh: 0.45 - }, - "ap-northeast-1": { - location: "Asia Pacific (Tokyo)", - grid_intensity_gco2e_per_kwh: 506, - pue: 1.13, - water_intensity_litres_per_kwh: 0.5 - }, - "ap-south-1": { - location: "Asia Pacific (Mumbai)", - grid_intensity_gco2e_per_kwh: 723, - pue: 1.13, - water_intensity_litres_per_kwh: 0.72 + aws: { + regions: { + "us-east-1": { + location: "US East (N. Virginia)", + grid_intensity_gco2e_per_kwh: 384.5, + pue: 1.13, + water_intensity_litres_per_kwh: 0.46 + }, + "us-east-2": { + location: "US East (Ohio)", + grid_intensity_gco2e_per_kwh: 410, + pue: 1.13, + water_intensity_litres_per_kwh: 0.52 + }, + "us-west-1": { + location: "US West (N. California)", + grid_intensity_gco2e_per_kwh: 220, + pue: 1.13, + water_intensity_litres_per_kwh: 0.38 + }, + "us-west-2": { + location: "US West (Oregon)", + grid_intensity_gco2e_per_kwh: 240.1, + pue: 1.13, + water_intensity_litres_per_kwh: 0.18 + }, + "eu-west-1": { + location: "Europe (Ireland)", + grid_intensity_gco2e_per_kwh: 334, + pue: 1.13, + water_intensity_litres_per_kwh: 0.22 + }, + "eu-west-2": { + location: "Europe (London)", + grid_intensity_gco2e_per_kwh: 268, + pue: 1.13, + water_intensity_litres_per_kwh: 0.25 + }, + "eu-central-1": { + location: "Europe (Frankfurt)", + grid_intensity_gco2e_per_kwh: 420.5, + pue: 1.13, + water_intensity_litres_per_kwh: 0.28 + }, + "eu-north-1": { + location: "Europe (Stockholm)", + grid_intensity_gco2e_per_kwh: 8.8, + pue: 1.13, + water_intensity_litres_per_kwh: 0.1 + }, + "ap-southeast-1": { + location: "Asia Pacific (Singapore)", + grid_intensity_gco2e_per_kwh: 408, + pue: 1.13, + water_intensity_litres_per_kwh: 0.58 + }, + "ap-southeast-2": { + location: "Asia Pacific (Sydney)", + grid_intensity_gco2e_per_kwh: 650, + pue: 1.13, + water_intensity_litres_per_kwh: 0.45 + }, + "ap-northeast-1": { + location: "Asia Pacific (Tokyo)", + grid_intensity_gco2e_per_kwh: 506, + pue: 1.13, + water_intensity_litres_per_kwh: 0.5 + }, + "ap-south-1": { + location: "Asia Pacific (Mumbai)", + grid_intensity_gco2e_per_kwh: 723, + pue: 1.13, + water_intensity_litres_per_kwh: 0.72 + }, + "ca-central-1": { + location: "Canada (Central)", + grid_intensity_gco2e_per_kwh: 130, + pue: 1.13, + water_intensity_litres_per_kwh: 0.2 + }, + "sa-east-1": { + location: "South America (S\xE3o Paulo)", + grid_intensity_gco2e_per_kwh: 74, + pue: 1.13, + water_intensity_litres_per_kwh: 0.35 + } }, - "ca-central-1": { - location: "Canada (Central)", - grid_intensity_gco2e_per_kwh: 130, - pue: 1.13, - water_intensity_litres_per_kwh: 0.2 + instances: { + "t3.micro": { + architecture: "x86_64", + vcpus: 2, + memory_gb: 1, + power_watts: { + idle: 1.4, + max: 5 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + "t3.small": { + architecture: "x86_64", + vcpus: 2, + memory_gb: 2, + power_watts: { + idle: 2, + max: 7 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + "t3.medium": { + architecture: "x86_64", + vcpus: 2, + memory_gb: 4, + power_watts: { + idle: 3.4, + max: 10.2 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + "t3.large": { + architecture: "x86_64", + vcpus: 2, + memory_gb: 8, + power_watts: { + idle: 6.8, + max: 20.4 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + "t3.xlarge": { + architecture: "x86_64", + vcpus: 4, + memory_gb: 16, + power_watts: { + idle: 13.6, + max: 40.8 + }, + embodied_co2e_grams_per_month: 2083.3 + }, + "t3a.medium": { + architecture: "x86_64", + vcpus: 2, + memory_gb: 4, + power_watts: { + idle: 3.2, + max: 9.8 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + "t3a.large": { + architecture: "x86_64", + vcpus: 2, + memory_gb: 8, + power_watts: { + idle: 6.4, + max: 19.6 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + "m5.large": { + architecture: "x86_64", + vcpus: 2, + memory_gb: 8, + power_watts: { + idle: 6.8, + max: 20.4 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + "m5.xlarge": { + architecture: "x86_64", + vcpus: 4, + memory_gb: 16, + power_watts: { + idle: 13.6, + max: 40.8 + }, + embodied_co2e_grams_per_month: 2083.3 + }, + "m5.2xlarge": { + architecture: "x86_64", + vcpus: 8, + memory_gb: 32, + power_watts: { + idle: 27.2, + max: 81.6 + }, + embodied_co2e_grams_per_month: 4166.7 + }, + "m5a.large": { + architecture: "x86_64", + vcpus: 2, + memory_gb: 8, + power_watts: { + idle: 6.5, + max: 19.5 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + "m5a.xlarge": { + architecture: "x86_64", + vcpus: 4, + memory_gb: 16, + power_watts: { + idle: 13, + max: 39 + }, + embodied_co2e_grams_per_month: 2083.3 + }, + "c5.large": { + architecture: "x86_64", + vcpus: 2, + memory_gb: 4, + power_watts: { + idle: 6.5, + max: 22 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + "c5.xlarge": { + architecture: "x86_64", + vcpus: 4, + memory_gb: 8, + power_watts: { + idle: 13, + max: 44 + }, + embodied_co2e_grams_per_month: 2083.3 + }, + "c5.2xlarge": { + architecture: "x86_64", + vcpus: 8, + memory_gb: 16, + power_watts: { + idle: 26, + max: 88 + }, + embodied_co2e_grams_per_month: 4166.7 + }, + "c5a.large": { + architecture: "x86_64", + vcpus: 2, + memory_gb: 4, + power_watts: { + idle: 6.2, + max: 21 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + "c5a.xlarge": { + architecture: "x86_64", + vcpus: 4, + memory_gb: 8, + power_watts: { + idle: 12.4, + max: 42 + }, + embodied_co2e_grams_per_month: 2083.3 + }, + "r5.large": { + architecture: "x86_64", + vcpus: 2, + memory_gb: 16, + power_watts: { + idle: 8, + max: 24 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + "r5.xlarge": { + architecture: "x86_64", + vcpus: 4, + memory_gb: 32, + power_watts: { + idle: 16, + max: 48 + }, + embodied_co2e_grams_per_month: 2083.3 + }, + "t4g.micro": { + architecture: "arm64", + vcpus: 2, + memory_gb: 1, + power_watts: { + idle: 0.9, + max: 3.2 + }, + embodied_co2e_grams_per_month: 833.3 + }, + "t4g.small": { + architecture: "arm64", + vcpus: 2, + memory_gb: 2, + power_watts: { + idle: 1.4, + max: 4.5 + }, + embodied_co2e_grams_per_month: 833.3 + }, + "t4g.medium": { + architecture: "arm64", + vcpus: 2, + memory_gb: 4, + power_watts: { + idle: 2.2, + max: 6.8 + }, + embodied_co2e_grams_per_month: 833.3 + }, + "t4g.large": { + architecture: "arm64", + vcpus: 2, + memory_gb: 8, + power_watts: { + idle: 4.4, + max: 13.6 + }, + embodied_co2e_grams_per_month: 833.3 + }, + "t4g.xlarge": { + architecture: "arm64", + vcpus: 4, + memory_gb: 16, + power_watts: { + idle: 8.8, + max: 27.2 + }, + embodied_co2e_grams_per_month: 1666.7 + }, + "m6g.medium": { + architecture: "arm64", + vcpus: 1, + memory_gb: 4, + power_watts: { + idle: 2.1, + max: 6.6 + }, + embodied_co2e_grams_per_month: 416.7 + }, + "m6g.large": { + architecture: "arm64", + vcpus: 2, + memory_gb: 8, + power_watts: { + idle: 4.1, + max: 13.2 + }, + embodied_co2e_grams_per_month: 833.3 + }, + "m6g.xlarge": { + architecture: "arm64", + vcpus: 4, + memory_gb: 16, + power_watts: { + idle: 8.2, + max: 26.4 + }, + embodied_co2e_grams_per_month: 1666.7 + }, + "m6g.2xlarge": { + architecture: "arm64", + vcpus: 8, + memory_gb: 32, + power_watts: { + idle: 16.4, + max: 52.8 + }, + embodied_co2e_grams_per_month: 3333.3 + }, + "m7g.medium": { + architecture: "arm64", + vcpus: 1, + memory_gb: 4, + power_watts: { + idle: 1.8, + max: 5.8 + }, + embodied_co2e_grams_per_month: 416.7 + }, + "m7g.large": { + architecture: "arm64", + vcpus: 2, + memory_gb: 8, + power_watts: { + idle: 3.6, + max: 11.6 + }, + embodied_co2e_grams_per_month: 833.3 + }, + "m7g.xlarge": { + architecture: "arm64", + vcpus: 4, + memory_gb: 16, + power_watts: { + idle: 7.2, + max: 23.2 + }, + embodied_co2e_grams_per_month: 1666.7 + }, + "m7g.2xlarge": { + architecture: "arm64", + vcpus: 8, + memory_gb: 32, + power_watts: { + idle: 14.4, + max: 46.4 + }, + embodied_co2e_grams_per_month: 3333.3 + }, + "c6g.medium": { + architecture: "arm64", + vcpus: 1, + memory_gb: 2, + power_watts: { + idle: 2, + max: 7.3 + }, + embodied_co2e_grams_per_month: 416.7 + }, + "c6g.large": { + architecture: "arm64", + vcpus: 2, + memory_gb: 4, + power_watts: { + idle: 3.9, + max: 14.5 + }, + embodied_co2e_grams_per_month: 833.3 + }, + "c6g.xlarge": { + architecture: "arm64", + vcpus: 4, + memory_gb: 8, + power_watts: { + idle: 7.8, + max: 29 + }, + embodied_co2e_grams_per_month: 1666.7 + }, + "c6g.2xlarge": { + architecture: "arm64", + vcpus: 8, + memory_gb: 16, + power_watts: { + idle: 15.6, + max: 58 + }, + embodied_co2e_grams_per_month: 3333.3 + }, + "c7g.large": { + architecture: "arm64", + vcpus: 2, + memory_gb: 4, + power_watts: { + idle: 3.5, + max: 13 + }, + embodied_co2e_grams_per_month: 833.3 + }, + "c7g.xlarge": { + architecture: "arm64", + vcpus: 4, + memory_gb: 8, + power_watts: { + idle: 7, + max: 26 + }, + embodied_co2e_grams_per_month: 1666.7 + }, + "r6g.large": { + architecture: "arm64", + vcpus: 2, + memory_gb: 16, + power_watts: { + idle: 4.8, + max: 15 + }, + embodied_co2e_grams_per_month: 833.3 + }, + "r6g.xlarge": { + architecture: "arm64", + vcpus: 4, + memory_gb: 32, + power_watts: { + idle: 9.6, + max: 30 + }, + embodied_co2e_grams_per_month: 1666.7 + } }, - "sa-east-1": { - location: "South America (S\xE3o Paulo)", - grid_intensity_gco2e_per_kwh: 74, - pue: 1.13, - water_intensity_litres_per_kwh: 0.35 + pricing_usd_per_hour: { + "us-east-1": { + "t3.micro": 0.0104, + "t3.small": 0.0208, + "t3.medium": 0.0416, + "t3.large": 0.0832, + "t3.xlarge": 0.1664, + "t3a.medium": 0.0376, + "t3a.large": 0.0752, + "m5.large": 0.096, + "m5.xlarge": 0.192, + "m5.2xlarge": 0.384, + "m5a.large": 0.086, + "m5a.xlarge": 0.172, + "c5.large": 0.085, + "c5.xlarge": 0.17, + "c5.2xlarge": 0.34, + "c5a.large": 0.077, + "c5a.xlarge": 0.154, + "r5.large": 0.126, + "r5.xlarge": 0.252, + "t4g.micro": 84e-4, + "t4g.small": 0.0168, + "t4g.medium": 0.0336, + "t4g.large": 0.0672, + "t4g.xlarge": 0.1344, + "m6g.medium": 0.0385, + "m6g.large": 0.077, + "m6g.xlarge": 0.154, + "m6g.2xlarge": 0.308, + "m7g.medium": 0.0408, + "m7g.large": 0.0816, + "m7g.xlarge": 0.1632, + "m7g.2xlarge": 0.3264, + "c6g.medium": 0.034, + "c6g.large": 0.068, + "c6g.xlarge": 0.136, + "c6g.2xlarge": 0.272, + "c7g.large": 0.0725, + "c7g.xlarge": 0.145, + "r6g.large": 0.1008, + "r6g.xlarge": 0.2016 + }, + "us-east-2": { + "t3.micro": 0.0104, + "t3.small": 0.0208, + "t3.medium": 0.0416, + "t3.large": 0.0832, + "t3.xlarge": 0.1664, + "t3a.medium": 0.0376, + "t3a.large": 0.0752, + "m5.large": 0.096, + "m5.xlarge": 0.192, + "m5.2xlarge": 0.384, + "m5a.large": 0.086, + "m5a.xlarge": 0.172, + "c5.large": 0.085, + "c5.xlarge": 0.17, + "c5.2xlarge": 0.34, + "c5a.large": 0.077, + "c5a.xlarge": 0.154, + "r5.large": 0.126, + "r5.xlarge": 0.252, + "t4g.micro": 84e-4, + "t4g.small": 0.0168, + "t4g.medium": 0.0336, + "t4g.large": 0.0672, + "t4g.xlarge": 0.1344, + "m6g.medium": 0.0385, + "m6g.large": 0.077, + "m6g.xlarge": 0.154, + "m6g.2xlarge": 0.308, + "m7g.medium": 0.0408, + "m7g.large": 0.0816, + "m7g.xlarge": 0.1632, + "m7g.2xlarge": 0.3264, + "c6g.medium": 0.034, + "c6g.large": 0.068, + "c6g.xlarge": 0.136, + "c6g.2xlarge": 0.272, + "c7g.large": 0.0725, + "c7g.xlarge": 0.145, + "r6g.large": 0.1008, + "r6g.xlarge": 0.2016 + }, + "us-west-1": { + "t3.micro": 0.0116, + "t3.small": 0.0232, + "t3.medium": 0.0464, + "t3.large": 0.0928, + "t3.xlarge": 0.1856, + "m5.large": 0.107, + "m5.xlarge": 0.214, + "m5.2xlarge": 0.428, + "c5.large": 0.096, + "c5.xlarge": 0.192, + "c5.2xlarge": 0.384, + "t4g.medium": 0.0376, + "t4g.large": 0.0752, + "t4g.xlarge": 0.1504, + "m6g.large": 0.086, + "m6g.xlarge": 0.172, + "m6g.2xlarge": 0.344, + "m7g.large": 0.0912, + "m7g.xlarge": 0.1824, + "c6g.large": 0.076, + "c6g.xlarge": 0.152, + "r6g.large": 0.1127, + "r6g.xlarge": 0.2254 + }, + "us-west-2": { + "t3.micro": 0.0104, + "t3.small": 0.0208, + "t3.medium": 0.0416, + "t3.large": 0.0832, + "t3.xlarge": 0.1664, + "t3a.medium": 0.0376, + "t3a.large": 0.0752, + "m5.large": 0.096, + "m5.xlarge": 0.192, + "m5.2xlarge": 0.384, + "m5a.large": 0.086, + "m5a.xlarge": 0.172, + "c5.large": 0.085, + "c5.xlarge": 0.17, + "c5.2xlarge": 0.34, + "c5a.large": 0.077, + "c5a.xlarge": 0.154, + "r5.large": 0.126, + "r5.xlarge": 0.252, + "t4g.micro": 84e-4, + "t4g.small": 0.0168, + "t4g.medium": 0.0336, + "t4g.large": 0.0672, + "t4g.xlarge": 0.1344, + "m6g.medium": 0.0385, + "m6g.large": 0.077, + "m6g.xlarge": 0.154, + "m6g.2xlarge": 0.308, + "m7g.medium": 0.0408, + "m7g.large": 0.0816, + "m7g.xlarge": 0.1632, + "m7g.2xlarge": 0.3264, + "c6g.medium": 0.034, + "c6g.large": 0.068, + "c6g.xlarge": 0.136, + "c6g.2xlarge": 0.272, + "c7g.large": 0.0725, + "c7g.xlarge": 0.145, + "r6g.large": 0.1008, + "r6g.xlarge": 0.2016 + }, + "eu-west-1": { + "t3.micro": 0.0116, + "t3.small": 0.0232, + "t3.medium": 0.0456, + "t3.large": 0.0912, + "t3.xlarge": 0.1824, + "t3a.medium": 0.0416, + "t3a.large": 0.0832, + "m5.large": 0.107, + "m5.xlarge": 0.214, + "m5.2xlarge": 0.428, + "m5a.large": 0.096, + "m5a.xlarge": 0.192, + "c5.large": 0.096, + "c5.xlarge": 0.192, + "c5.2xlarge": 0.384, + "c5a.large": 0.087, + "c5a.xlarge": 0.174, + "r5.large": 0.141, + "r5.xlarge": 0.282, + "t4g.micro": 94e-4, + "t4g.small": 0.0188, + "t4g.medium": 0.0376, + "t4g.large": 0.0752, + "t4g.xlarge": 0.1504, + "m6g.medium": 0.043, + "m6g.large": 0.086, + "m6g.xlarge": 0.172, + "m6g.2xlarge": 0.344, + "m7g.medium": 0.0456, + "m7g.large": 0.0912, + "m7g.xlarge": 0.1824, + "m7g.2xlarge": 0.3648, + "c6g.medium": 0.038, + "c6g.large": 0.076, + "c6g.xlarge": 0.152, + "c6g.2xlarge": 0.304, + "c7g.large": 0.0812, + "c7g.xlarge": 0.1624, + "r6g.large": 0.1127, + "r6g.xlarge": 0.2254 + }, + "eu-west-2": { + "t3.micro": 0.0126, + "t3.small": 0.0252, + "t3.medium": 0.0504, + "t3.large": 0.1008, + "t3.xlarge": 0.2016, + "m5.large": 0.1178, + "m5.xlarge": 0.2356, + "m5.2xlarge": 0.4712, + "c5.large": 0.1054, + "c5.xlarge": 0.2108, + "c5.2xlarge": 0.4216, + "t4g.medium": 0.0414, + "t4g.large": 0.0828, + "t4g.xlarge": 0.1656, + "m6g.large": 0.0945, + "m6g.xlarge": 0.189, + "m6g.2xlarge": 0.378, + "m7g.large": 0.1001, + "m7g.xlarge": 0.2002, + "c6g.large": 0.0836, + "c6g.xlarge": 0.1672, + "r6g.large": 0.124, + "r6g.xlarge": 0.248 + }, + "eu-central-1": { + "t3.micro": 0.012, + "t3.small": 0.024, + "t3.medium": 0.0496, + "t3.large": 0.0992, + "t3.xlarge": 0.1984, + "t3a.medium": 0.0448, + "t3a.large": 0.0896, + "m5.large": 0.115, + "m5.xlarge": 0.23, + "m5.2xlarge": 0.46, + "m5a.large": 0.103, + "m5a.xlarge": 0.206, + "c5.large": 0.102, + "c5.xlarge": 0.204, + "c5.2xlarge": 0.408, + "r5.large": 0.151, + "r5.xlarge": 0.302, + "t4g.micro": 0.01, + "t4g.small": 0.02, + "t4g.medium": 0.0416, + "t4g.large": 0.0832, + "t4g.xlarge": 0.1664, + "m6g.medium": 0.046, + "m6g.large": 0.092, + "m6g.xlarge": 0.184, + "m6g.2xlarge": 0.368, + "m7g.medium": 0.0488, + "m7g.large": 0.0976, + "m7g.xlarge": 0.1952, + "m7g.2xlarge": 0.3904, + "c6g.medium": 0.041, + "c6g.large": 0.082, + "c6g.xlarge": 0.164, + "c6g.2xlarge": 0.328, + "c7g.large": 0.0875, + "c7g.xlarge": 0.175, + "r6g.large": 0.121, + "r6g.xlarge": 0.242 + }, + "eu-north-1": { + "t3.micro": 0.0108, + "t3.small": 0.0216, + "t3.medium": 0.0432, + "t3.large": 0.0864, + "t3.xlarge": 0.1728, + "m5.large": 0.1, + "m5.xlarge": 0.2, + "m5.2xlarge": 0.4, + "c5.large": 0.089, + "c5.xlarge": 0.178, + "c5.2xlarge": 0.356, + "t4g.medium": 0.0362, + "t4g.large": 0.0724, + "t4g.xlarge": 0.1448, + "m6g.large": 0.08, + "m6g.xlarge": 0.16, + "m6g.2xlarge": 0.32, + "m7g.large": 0.0848, + "m7g.xlarge": 0.1696, + "c6g.large": 0.0712, + "c6g.xlarge": 0.1424, + "r6g.large": 0.1054, + "r6g.xlarge": 0.2108 + }, + "ap-southeast-1": { + "t3.micro": 0.0132, + "t3.small": 0.0264, + "t3.medium": 0.0528, + "t3.large": 0.1056, + "t3.xlarge": 0.2112, + "m5.large": 0.124, + "m5.xlarge": 0.248, + "m5.2xlarge": 0.496, + "c5.large": 0.107, + "c5.xlarge": 0.214, + "c5.2xlarge": 0.428, + "t4g.medium": 0.0438, + "t4g.large": 0.0876, + "t4g.xlarge": 0.1752, + "m6g.large": 0.0992, + "m6g.xlarge": 0.1984, + "m6g.2xlarge": 0.3968, + "m7g.large": 0.1051, + "m7g.xlarge": 0.2102, + "c6g.large": 0.086, + "c6g.xlarge": 0.172, + "r6g.large": 0.1307, + "r6g.xlarge": 0.2614 + }, + "ap-southeast-2": { + "t3.micro": 0.0136, + "t3.small": 0.0272, + "t3.medium": 0.0544, + "t3.large": 0.1088, + "t3.xlarge": 0.2176, + "t3a.medium": 0.0492, + "t3a.large": 0.0984, + "m5.large": 0.134, + "m5.xlarge": 0.268, + "m5.2xlarge": 0.536, + "m5a.large": 0.12, + "m5a.xlarge": 0.24, + "c5.large": 0.118, + "c5.xlarge": 0.236, + "c5.2xlarge": 0.472, + "r5.large": 0.176, + "r5.xlarge": 0.352, + "t4g.micro": 0.0113, + "t4g.small": 0.0226, + "t4g.medium": 0.0452, + "t4g.large": 0.0904, + "t4g.xlarge": 0.1808, + "m6g.medium": 0.0535, + "m6g.large": 0.107, + "m6g.xlarge": 0.214, + "m6g.2xlarge": 0.428, + "m7g.medium": 0.0567, + "m7g.large": 0.1134, + "m7g.xlarge": 0.2268, + "m7g.2xlarge": 0.4536, + "c6g.medium": 0.047, + "c6g.large": 0.094, + "c6g.xlarge": 0.188, + "c6g.2xlarge": 0.376, + "c7g.large": 0.1002, + "c7g.xlarge": 0.2004, + "r6g.large": 0.1411, + "r6g.xlarge": 0.2822 + }, + "ap-northeast-1": { + "t3.micro": 0.014, + "t3.small": 0.028, + "t3.medium": 0.056, + "t3.large": 0.112, + "t3.xlarge": 0.224, + "t3a.medium": 0.0504, + "t3a.large": 0.1008, + "m5.large": 0.128, + "m5.xlarge": 0.256, + "m5.2xlarge": 0.512, + "m5a.large": 0.115, + "m5a.xlarge": 0.23, + "c5.large": 0.114, + "c5.xlarge": 0.228, + "c5.2xlarge": 0.456, + "r5.large": 0.169, + "r5.xlarge": 0.338, + "t4g.micro": 0.0116, + "t4g.small": 0.0232, + "t4g.medium": 0.0464, + "t4g.large": 0.0928, + "t4g.xlarge": 0.1856, + "m6g.medium": 0.0549, + "m6g.large": 0.1098, + "m6g.xlarge": 0.2196, + "m6g.2xlarge": 0.4392, + "m7g.medium": 0.0582, + "m7g.large": 0.1164, + "m7g.xlarge": 0.2328, + "m7g.2xlarge": 0.4656, + "c6g.medium": 0.0482, + "c6g.large": 0.0964, + "c6g.xlarge": 0.1928, + "c6g.2xlarge": 0.3856, + "c7g.large": 0.1028, + "c7g.xlarge": 0.2056, + "r6g.large": 0.1448, + "r6g.xlarge": 0.2896 + }, + "ap-south-1": { + "t3.micro": 0.0114, + "t3.small": 0.0228, + "t3.medium": 0.0456, + "t3.large": 0.0912, + "t3.xlarge": 0.1824, + "t3a.medium": 0.041, + "t3a.large": 0.082, + "m5.large": 0.106, + "m5.xlarge": 0.212, + "m5.2xlarge": 0.424, + "c5.large": 0.094, + "c5.xlarge": 0.188, + "c5.2xlarge": 0.376, + "r5.large": 0.1396, + "r5.xlarge": 0.2792, + "t4g.micro": 95e-4, + "t4g.small": 0.019, + "t4g.medium": 0.038, + "t4g.large": 0.076, + "t4g.xlarge": 0.152, + "m6g.medium": 0.0454, + "m6g.large": 0.0908, + "m6g.xlarge": 0.1816, + "m6g.2xlarge": 0.3632, + "m7g.medium": 0.0481, + "m7g.large": 0.0962, + "m7g.xlarge": 0.1924, + "m7g.2xlarge": 0.3848, + "c6g.medium": 0.0399, + "c6g.large": 0.0798, + "c6g.xlarge": 0.1596, + "c6g.2xlarge": 0.3192, + "r6g.large": 0.1197, + "r6g.xlarge": 0.2394 + }, + "ca-central-1": { + "t3.micro": 0.0116, + "t3.small": 0.0232, + "t3.medium": 0.0464, + "t3.large": 0.0928, + "t3.xlarge": 0.1856, + "t3a.medium": 0.0418, + "t3a.large": 0.0836, + "m5.large": 0.107, + "m5.xlarge": 0.214, + "m5.2xlarge": 0.428, + "m5a.large": 0.096, + "m5a.xlarge": 0.192, + "c5.large": 0.095, + "c5.xlarge": 0.19, + "c5.2xlarge": 0.38, + "r5.large": 0.141, + "r5.xlarge": 0.282, + "t4g.micro": 96e-4, + "t4g.small": 0.0192, + "t4g.medium": 0.0386, + "t4g.large": 0.0772, + "t4g.xlarge": 0.1544, + "m6g.medium": 0.0462, + "m6g.large": 0.0924, + "m6g.xlarge": 0.1848, + "m6g.2xlarge": 0.3696, + "m7g.medium": 0.049, + "m7g.large": 0.098, + "m7g.xlarge": 0.196, + "m7g.2xlarge": 0.392, + "c6g.medium": 0.0408, + "c6g.large": 0.0816, + "c6g.xlarge": 0.1632, + "c6g.2xlarge": 0.3264, + "c7g.large": 0.087, + "c7g.xlarge": 0.174, + "r6g.large": 0.1218, + "r6g.xlarge": 0.2436 + }, + "sa-east-1": { + "t3.micro": 0.0168, + "t3.small": 0.0336, + "t3.medium": 0.0672, + "t3.large": 0.1344, + "t3.xlarge": 0.2688, + "m5.large": 0.162, + "m5.xlarge": 0.324, + "m5.2xlarge": 0.648, + "c5.large": 0.144, + "c5.xlarge": 0.288, + "c5.2xlarge": 0.576, + "t4g.medium": 0.056, + "t4g.large": 0.112, + "t4g.xlarge": 0.224, + "m6g.large": 0.1296, + "m6g.xlarge": 0.2592, + "m6g.2xlarge": 0.5184, + "m7g.large": 0.1374, + "m7g.xlarge": 0.2748, + "c6g.large": 0.1152, + "c6g.xlarge": 0.2304, + "r6g.large": 0.1706, + "r6g.xlarge": 0.3412 + } } }, - instances: { - "t3.micro": { - architecture: "x86_64", - vcpus: 2, - memory_gb: 1, - power_watts: { - idle: 1.4, - max: 5 - }, - embodied_co2e_grams_per_month: 1041.7 - }, - "t3.small": { - architecture: "x86_64", - vcpus: 2, - memory_gb: 2, - power_watts: { - idle: 2, - max: 7 - }, - embodied_co2e_grams_per_month: 1041.7 - }, - "t3.medium": { - architecture: "x86_64", - vcpus: 2, - memory_gb: 4, - power_watts: { - idle: 3.4, - max: 10.2 - }, - embodied_co2e_grams_per_month: 1041.7 - }, - "t3.large": { - architecture: "x86_64", - vcpus: 2, - memory_gb: 8, - power_watts: { - idle: 6.8, - max: 20.4 - }, - embodied_co2e_grams_per_month: 1041.7 - }, - "t3.xlarge": { - architecture: "x86_64", - vcpus: 4, - memory_gb: 16, - power_watts: { - idle: 13.6, - max: 40.8 - }, - embodied_co2e_grams_per_month: 2083.3 - }, - "t3a.medium": { - architecture: "x86_64", - vcpus: 2, - memory_gb: 4, - power_watts: { - idle: 3.2, - max: 9.8 - }, - embodied_co2e_grams_per_month: 1041.7 - }, - "t3a.large": { - architecture: "x86_64", - vcpus: 2, - memory_gb: 8, - power_watts: { - idle: 6.4, - max: 19.6 - }, - embodied_co2e_grams_per_month: 1041.7 - }, - "m5.large": { - architecture: "x86_64", - vcpus: 2, - memory_gb: 8, - power_watts: { - idle: 6.8, - max: 20.4 - }, - embodied_co2e_grams_per_month: 1041.7 - }, - "m5.xlarge": { - architecture: "x86_64", - vcpus: 4, - memory_gb: 16, - power_watts: { - idle: 13.6, - max: 40.8 - }, - embodied_co2e_grams_per_month: 2083.3 - }, - "m5.2xlarge": { - architecture: "x86_64", - vcpus: 8, - memory_gb: 32, - power_watts: { - idle: 27.2, - max: 81.6 - }, - embodied_co2e_grams_per_month: 4166.7 - }, - "m5a.large": { - architecture: "x86_64", - vcpus: 2, - memory_gb: 8, - power_watts: { - idle: 6.5, - max: 19.5 - }, - embodied_co2e_grams_per_month: 1041.7 - }, - "m5a.xlarge": { - architecture: "x86_64", - vcpus: 4, - memory_gb: 16, - power_watts: { - idle: 13, - max: 39 - }, - embodied_co2e_grams_per_month: 2083.3 - }, - "c5.large": { - architecture: "x86_64", - vcpus: 2, - memory_gb: 4, - power_watts: { - idle: 6.5, - max: 22 - }, - embodied_co2e_grams_per_month: 1041.7 - }, - "c5.xlarge": { - architecture: "x86_64", - vcpus: 4, - memory_gb: 8, - power_watts: { - idle: 13, - max: 44 - }, - embodied_co2e_grams_per_month: 2083.3 - }, - "c5.2xlarge": { - architecture: "x86_64", - vcpus: 8, - memory_gb: 16, - power_watts: { - idle: 26, - max: 88 - }, - embodied_co2e_grams_per_month: 4166.7 - }, - "c5a.large": { - architecture: "x86_64", - vcpus: 2, - memory_gb: 4, - power_watts: { - idle: 6.2, - max: 21 - }, - embodied_co2e_grams_per_month: 1041.7 - }, - "c5a.xlarge": { - architecture: "x86_64", - vcpus: 4, - memory_gb: 8, - power_watts: { - idle: 12.4, - max: 42 - }, - embodied_co2e_grams_per_month: 2083.3 - }, - "r5.large": { - architecture: "x86_64", - vcpus: 2, - memory_gb: 16, - power_watts: { - idle: 8, - max: 24 - }, - embodied_co2e_grams_per_month: 1041.7 - }, - "r5.xlarge": { - architecture: "x86_64", - vcpus: 4, - memory_gb: 32, - power_watts: { - idle: 16, - max: 48 - }, - embodied_co2e_grams_per_month: 2083.3 - }, - "t4g.micro": { - architecture: "arm64", - vcpus: 2, - memory_gb: 1, - power_watts: { - idle: 0.9, - max: 3.2 - }, - embodied_co2e_grams_per_month: 833.3 - }, - "t4g.small": { - architecture: "arm64", - vcpus: 2, - memory_gb: 2, - power_watts: { - idle: 1.4, - max: 4.5 - }, - embodied_co2e_grams_per_month: 833.3 - }, - "t4g.medium": { - architecture: "arm64", - vcpus: 2, - memory_gb: 4, - power_watts: { - idle: 2.2, - max: 6.8 - }, - embodied_co2e_grams_per_month: 833.3 - }, - "t4g.large": { - architecture: "arm64", - vcpus: 2, - memory_gb: 8, - power_watts: { - idle: 4.4, - max: 13.6 - }, - embodied_co2e_grams_per_month: 833.3 - }, - "t4g.xlarge": { - architecture: "arm64", - vcpus: 4, - memory_gb: 16, - power_watts: { - idle: 8.8, - max: 27.2 - }, - embodied_co2e_grams_per_month: 1666.7 - }, - "m6g.medium": { - architecture: "arm64", - vcpus: 1, - memory_gb: 4, - power_watts: { - idle: 2.1, - max: 6.6 - }, - embodied_co2e_grams_per_month: 416.7 - }, - "m6g.large": { - architecture: "arm64", - vcpus: 2, - memory_gb: 8, - power_watts: { - idle: 4.1, - max: 13.2 - }, - embodied_co2e_grams_per_month: 833.3 - }, - "m6g.xlarge": { - architecture: "arm64", - vcpus: 4, - memory_gb: 16, - power_watts: { - idle: 8.2, - max: 26.4 - }, - embodied_co2e_grams_per_month: 1666.7 - }, - "m6g.2xlarge": { - architecture: "arm64", - vcpus: 8, - memory_gb: 32, - power_watts: { - idle: 16.4, - max: 52.8 - }, - embodied_co2e_grams_per_month: 3333.3 - }, - "m7g.medium": { - architecture: "arm64", - vcpus: 1, - memory_gb: 4, - power_watts: { - idle: 1.8, - max: 5.8 - }, - embodied_co2e_grams_per_month: 416.7 - }, - "m7g.large": { - architecture: "arm64", - vcpus: 2, - memory_gb: 8, - power_watts: { - idle: 3.6, - max: 11.6 - }, - embodied_co2e_grams_per_month: 833.3 - }, - "m7g.xlarge": { - architecture: "arm64", - vcpus: 4, - memory_gb: 16, - power_watts: { - idle: 7.2, - max: 23.2 - }, - embodied_co2e_grams_per_month: 1666.7 - }, - "m7g.2xlarge": { - architecture: "arm64", - vcpus: 8, - memory_gb: 32, - power_watts: { - idle: 14.4, - max: 46.4 - }, - embodied_co2e_grams_per_month: 3333.3 - }, - "c6g.medium": { - architecture: "arm64", - vcpus: 1, - memory_gb: 2, - power_watts: { - idle: 2, - max: 7.3 - }, - embodied_co2e_grams_per_month: 416.7 - }, - "c6g.large": { - architecture: "arm64", - vcpus: 2, - memory_gb: 4, - power_watts: { - idle: 3.9, - max: 14.5 - }, - embodied_co2e_grams_per_month: 833.3 - }, - "c6g.xlarge": { - architecture: "arm64", - vcpus: 4, - memory_gb: 8, - power_watts: { - idle: 7.8, - max: 29 - }, - embodied_co2e_grams_per_month: 1666.7 - }, - "c6g.2xlarge": { - architecture: "arm64", - vcpus: 8, - memory_gb: 16, - power_watts: { - idle: 15.6, - max: 58 - }, - embodied_co2e_grams_per_month: 3333.3 - }, - "c7g.large": { - architecture: "arm64", - vcpus: 2, - memory_gb: 4, - power_watts: { - idle: 3.5, - max: 13 - }, - embodied_co2e_grams_per_month: 833.3 - }, - "c7g.xlarge": { - architecture: "arm64", - vcpus: 4, - memory_gb: 8, - power_watts: { - idle: 7, - max: 26 - }, - embodied_co2e_grams_per_month: 1666.7 + azure: { + regions: { + eastus: { + location: "East US (Virginia)", + grid_intensity_gco2e_per_kwh: 380, + pue: 1.125, + water_intensity_litres_per_kwh: 0.43 + }, + eastus2: { + location: "East US 2 (Virginia)", + grid_intensity_gco2e_per_kwh: 380, + pue: 1.125, + water_intensity_litres_per_kwh: 0.43 + }, + westus: { + location: "West US (California)", + grid_intensity_gco2e_per_kwh: 200, + pue: 1.125, + water_intensity_litres_per_kwh: 0.35 + }, + westus2: { + location: "West US 2 (Washington)", + grid_intensity_gco2e_per_kwh: 235, + pue: 1.125, + water_intensity_litres_per_kwh: 0.18 + }, + westus3: { + location: "West US 3 (Arizona)", + grid_intensity_gco2e_per_kwh: 350, + pue: 1.125, + water_intensity_litres_per_kwh: 0.55 + }, + northeurope: { + location: "North Europe (Ireland)", + grid_intensity_gco2e_per_kwh: 334, + pue: 1.125, + water_intensity_litres_per_kwh: 0.22 + }, + westeurope: { + location: "West Europe (Netherlands)", + grid_intensity_gco2e_per_kwh: 270, + pue: 1.125, + water_intensity_litres_per_kwh: 0.2 + }, + uksouth: { + location: "UK South (London)", + grid_intensity_gco2e_per_kwh: 268, + pue: 1.125, + water_intensity_litres_per_kwh: 0.25 + }, + ukwest: { + location: "UK West (Cardiff)", + grid_intensity_gco2e_per_kwh: 268, + pue: 1.125, + water_intensity_litres_per_kwh: 0.25 + }, + swedencentral: { + location: "Sweden Central", + grid_intensity_gco2e_per_kwh: 8.8, + pue: 1.125, + water_intensity_litres_per_kwh: 0.1 + }, + germanywestcentral: { + location: "Germany West Central", + grid_intensity_gco2e_per_kwh: 420.5, + pue: 1.125, + water_intensity_litres_per_kwh: 0.28 + }, + southeastasia: { + location: "Southeast Asia (Singapore)", + grid_intensity_gco2e_per_kwh: 408, + pue: 1.125, + water_intensity_litres_per_kwh: 0.58 + }, + australiaeast: { + location: "Australia East (NSW)", + grid_intensity_gco2e_per_kwh: 650, + pue: 1.125, + water_intensity_litres_per_kwh: 0.45 + }, + japaneast: { + location: "Japan East (Tokyo)", + grid_intensity_gco2e_per_kwh: 506, + pue: 1.125, + water_intensity_litres_per_kwh: 0.5 + }, + centralindia: { + location: "Central India (Pune)", + grid_intensity_gco2e_per_kwh: 723, + pue: 1.125, + water_intensity_litres_per_kwh: 0.72 + }, + canadacentral: { + location: "Canada Central (Toronto)", + grid_intensity_gco2e_per_kwh: 130, + pue: 1.125, + water_intensity_litres_per_kwh: 0.2 + }, + brazilsouth: { + location: "Brazil South (Sao Paulo)", + grid_intensity_gco2e_per_kwh: 74, + pue: 1.125, + water_intensity_litres_per_kwh: 0.35 + } }, - "r6g.large": { - architecture: "arm64", - vcpus: 2, - memory_gb: 16, - power_watts: { - idle: 4.8, - max: 15 - }, - embodied_co2e_grams_per_month: 833.3 + instances: { + Standard_B2s: { + architecture: "x86_64", + vcpus: 2, + memory_gb: 4, + power_watts: { + idle: 1.5, + max: 5.5 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + Standard_B2ms: { + architecture: "x86_64", + vcpus: 2, + memory_gb: 8, + power_watts: { + idle: 3, + max: 11 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + Standard_B4ms: { + architecture: "x86_64", + vcpus: 4, + memory_gb: 16, + power_watts: { + idle: 6, + max: 22 + }, + embodied_co2e_grams_per_month: 2083.3 + }, + Standard_D2s_v3: { + architecture: "x86_64", + vcpus: 2, + memory_gb: 8, + power_watts: { + idle: 6.8, + max: 20.4 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + Standard_D4s_v3: { + architecture: "x86_64", + vcpus: 4, + memory_gb: 16, + power_watts: { + idle: 13.6, + max: 40.8 + }, + embodied_co2e_grams_per_month: 2083.3 + }, + Standard_D8s_v3: { + architecture: "x86_64", + vcpus: 8, + memory_gb: 32, + power_watts: { + idle: 27.2, + max: 81.6 + }, + embodied_co2e_grams_per_month: 4166.6 + }, + Standard_D2s_v4: { + architecture: "x86_64", + vcpus: 2, + memory_gb: 8, + power_watts: { + idle: 6.5, + max: 19.5 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + Standard_D4s_v4: { + architecture: "x86_64", + vcpus: 4, + memory_gb: 16, + power_watts: { + idle: 13, + max: 39 + }, + embodied_co2e_grams_per_month: 2083.3 + }, + Standard_D2ps_v5: { + architecture: "arm64", + vcpus: 2, + memory_gb: 8, + power_watts: { + idle: 4.1, + max: 13.2 + }, + embodied_co2e_grams_per_month: 833.3 + }, + Standard_D4ps_v5: { + architecture: "arm64", + vcpus: 4, + memory_gb: 16, + power_watts: { + idle: 8.2, + max: 26.4 + }, + embodied_co2e_grams_per_month: 1666.7 + }, + Standard_D8ps_v5: { + architecture: "arm64", + vcpus: 8, + memory_gb: 32, + power_watts: { + idle: 16.4, + max: 52.8 + }, + embodied_co2e_grams_per_month: 3333.3 + }, + Standard_F2s_v2: { + architecture: "x86_64", + vcpus: 2, + memory_gb: 4, + power_watts: { + idle: 6.5, + max: 22 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + Standard_F4s_v2: { + architecture: "x86_64", + vcpus: 4, + memory_gb: 8, + power_watts: { + idle: 13, + max: 44 + }, + embodied_co2e_grams_per_month: 2083.3 + }, + Standard_F8s_v2: { + architecture: "x86_64", + vcpus: 8, + memory_gb: 16, + power_watts: { + idle: 26, + max: 88 + }, + embodied_co2e_grams_per_month: 4166.6 + }, + Standard_E2s_v3: { + architecture: "x86_64", + vcpus: 2, + memory_gb: 16, + power_watts: { + idle: 8, + max: 24 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + Standard_E4s_v3: { + architecture: "x86_64", + vcpus: 4, + memory_gb: 32, + power_watts: { + idle: 16, + max: 48 + }, + embodied_co2e_grams_per_month: 2083.3 + } }, - "r6g.xlarge": { - architecture: "arm64", - vcpus: 4, - memory_gb: 32, - power_watts: { - idle: 9.6, - max: 30 - }, - embodied_co2e_grams_per_month: 1666.7 + pricing_usd_per_hour: { + eastus: { + Standard_B2s: 0.041, + Standard_B2ms: 0.081, + Standard_B4ms: 0.162, + Standard_D2s_v3: 0.096, + Standard_D4s_v3: 0.192, + Standard_D8s_v3: 0.384, + Standard_D2s_v4: 0.091, + Standard_D4s_v4: 0.182, + Standard_D2ps_v5: 0.077, + Standard_D4ps_v5: 0.154, + Standard_D8ps_v5: 0.308, + Standard_F2s_v2: 0.085, + Standard_F4s_v2: 0.17, + Standard_F8s_v2: 0.34, + Standard_E2s_v3: 0.126, + Standard_E4s_v3: 0.252 + }, + eastus2: { + Standard_B2s: 0.041, + Standard_B2ms: 0.081, + Standard_B4ms: 0.162, + Standard_D2s_v3: 0.096, + Standard_D4s_v3: 0.192, + Standard_D8s_v3: 0.384, + Standard_D2s_v4: 0.091, + Standard_D4s_v4: 0.182, + Standard_D2ps_v5: 0.077, + Standard_D4ps_v5: 0.154, + Standard_D8ps_v5: 0.308, + Standard_F2s_v2: 0.085, + Standard_F4s_v2: 0.17, + Standard_F8s_v2: 0.34, + Standard_E2s_v3: 0.126, + Standard_E4s_v3: 0.252 + }, + westus2: { + Standard_B2s: 0.041, + Standard_B2ms: 0.081, + Standard_B4ms: 0.162, + Standard_D2s_v3: 0.096, + Standard_D4s_v3: 0.192, + Standard_D8s_v3: 0.384, + Standard_D2s_v4: 0.091, + Standard_D4s_v4: 0.182, + Standard_D2ps_v5: 0.077, + Standard_D4ps_v5: 0.154, + Standard_D8ps_v5: 0.308, + Standard_F2s_v2: 0.085, + Standard_F4s_v2: 0.17, + Standard_F8s_v2: 0.34, + Standard_E2s_v3: 0.126, + Standard_E4s_v3: 0.252 + }, + northeurope: { + Standard_B2s: 0.044, + Standard_B2ms: 0.087, + Standard_B4ms: 0.174, + Standard_D2s_v3: 0.104, + Standard_D4s_v3: 0.208, + Standard_D8s_v3: 0.416, + Standard_D2s_v4: 0.098, + Standard_D4s_v4: 0.196, + Standard_D2ps_v5: 0.083, + Standard_D4ps_v5: 0.166, + Standard_D8ps_v5: 0.332, + Standard_F2s_v2: 0.091, + Standard_F4s_v2: 0.183, + Standard_F8s_v2: 0.366, + Standard_E2s_v3: 0.136, + Standard_E4s_v3: 0.271 + }, + westeurope: { + Standard_B2s: 0.044, + Standard_B2ms: 0.087, + Standard_B4ms: 0.174, + Standard_D2s_v3: 0.104, + Standard_D4s_v3: 0.208, + Standard_D8s_v3: 0.416, + Standard_D2s_v4: 0.098, + Standard_D4s_v4: 0.196, + Standard_D2ps_v5: 0.083, + Standard_D4ps_v5: 0.166, + Standard_D8ps_v5: 0.332, + Standard_F2s_v2: 0.091, + Standard_F4s_v2: 0.183, + Standard_F8s_v2: 0.366, + Standard_E2s_v3: 0.136, + Standard_E4s_v3: 0.271 + }, + uksouth: { + Standard_B2s: 0.046, + Standard_B2ms: 0.091, + Standard_B4ms: 0.183, + Standard_D2s_v3: 0.109, + Standard_D4s_v3: 0.218, + Standard_D8s_v3: 0.436, + Standard_D2s_v4: 0.103, + Standard_D4s_v4: 0.206, + Standard_D2ps_v5: 0.087, + Standard_D4ps_v5: 0.174, + Standard_D8ps_v5: 0.348, + Standard_F2s_v2: 0.095, + Standard_F4s_v2: 0.191, + Standard_F8s_v2: 0.382, + Standard_E2s_v3: 0.143, + Standard_E4s_v3: 0.285 + }, + swedencentral: { + Standard_B2s: 0.042, + Standard_B2ms: 0.083, + Standard_B4ms: 0.166, + Standard_D2s_v3: 0.099, + Standard_D4s_v3: 0.198, + Standard_D8s_v3: 0.397, + Standard_D2s_v4: 0.094, + Standard_D4s_v4: 0.187, + Standard_D2ps_v5: 0.079, + Standard_D4ps_v5: 0.159, + Standard_D8ps_v5: 0.317, + Standard_F2s_v2: 0.087, + Standard_F4s_v2: 0.174, + Standard_F8s_v2: 0.348, + Standard_E2s_v3: 0.13, + Standard_E4s_v3: 0.259 + }, + southeastasia: { + Standard_B2s: 0.048, + Standard_B2ms: 0.096, + Standard_B4ms: 0.192, + Standard_D2s_v3: 0.114, + Standard_D4s_v3: 0.229, + Standard_D8s_v3: 0.458, + Standard_D2s_v4: 0.108, + Standard_D4s_v4: 0.217, + Standard_D2ps_v5: 0.092, + Standard_D4ps_v5: 0.184, + Standard_D8ps_v5: 0.368, + Standard_F2s_v2: 0.1, + Standard_F4s_v2: 0.2, + Standard_F8s_v2: 0.4, + Standard_E2s_v3: 0.15, + Standard_E4s_v3: 0.301 + }, + australiaeast: { + Standard_B2s: 0.052, + Standard_B2ms: 0.104, + Standard_B4ms: 0.208, + Standard_D2s_v3: 0.124, + Standard_D4s_v3: 0.248, + Standard_D8s_v3: 0.496, + Standard_D2s_v4: 0.117, + Standard_D4s_v4: 0.234, + Standard_D2ps_v5: 0.099, + Standard_D4ps_v5: 0.199, + Standard_D8ps_v5: 0.397, + Standard_F2s_v2: 0.108, + Standard_F4s_v2: 0.217, + Standard_F8s_v2: 0.433, + Standard_E2s_v3: 0.162, + Standard_E4s_v3: 0.325 + }, + japaneast: { + Standard_B2s: 0.051, + Standard_B2ms: 0.102, + Standard_B4ms: 0.204, + Standard_D2s_v3: 0.121, + Standard_D4s_v3: 0.242, + Standard_D8s_v3: 0.484, + Standard_D2s_v4: 0.115, + Standard_D4s_v4: 0.229, + Standard_D2ps_v5: 0.097, + Standard_D4ps_v5: 0.194, + Standard_D8ps_v5: 0.388, + Standard_F2s_v2: 0.105, + Standard_F4s_v2: 0.211, + Standard_F8s_v2: 0.422, + Standard_E2s_v3: 0.158, + Standard_E4s_v3: 0.317 + }, + centralindia: { + Standard_B2s: 0.043, + Standard_B2ms: 0.086, + Standard_B4ms: 0.171, + Standard_D2s_v3: 0.102, + Standard_D4s_v3: 0.203, + Standard_D8s_v3: 0.406, + Standard_D2s_v4: 0.096, + Standard_D4s_v4: 0.192, + Standard_D2ps_v5: 0.081, + Standard_D4ps_v5: 0.162, + Standard_D8ps_v5: 0.325, + Standard_F2s_v2: 0.089, + Standard_F4s_v2: 0.178, + Standard_F8s_v2: 0.356, + Standard_E2s_v3: 0.133, + Standard_E4s_v3: 0.266 + }, + canadacentral: { + Standard_B2s: 0.043, + Standard_B2ms: 0.086, + Standard_B4ms: 0.171, + Standard_D2s_v3: 0.101, + Standard_D4s_v3: 0.202, + Standard_D8s_v3: 0.404, + Standard_D2s_v4: 0.096, + Standard_D4s_v4: 0.191, + Standard_D2ps_v5: 0.081, + Standard_D4ps_v5: 0.162, + Standard_D8ps_v5: 0.323, + Standard_F2s_v2: 0.088, + Standard_F4s_v2: 0.177, + Standard_F8s_v2: 0.354, + Standard_E2s_v3: 0.132, + Standard_E4s_v3: 0.264 + }, + brazilsouth: { + Standard_B2s: 0.059, + Standard_B2ms: 0.118, + Standard_B4ms: 0.237, + Standard_D2s_v3: 0.141, + Standard_D4s_v3: 0.281, + Standard_D8s_v3: 0.562, + Standard_D2s_v4: 0.133, + Standard_D4s_v4: 0.266, + Standard_D2ps_v5: 0.113, + Standard_D4ps_v5: 0.225, + Standard_D8ps_v5: 0.45, + Standard_F2s_v2: 0.123, + Standard_F4s_v2: 0.246, + Standard_F8s_v2: 0.492, + Standard_E2s_v3: 0.184, + Standard_E4s_v3: 0.368 + } } }, - pricing_usd_per_hour: { - "us-east-1": { - "t3.micro": 0.0104, - "t3.small": 0.0208, - "t3.medium": 0.0416, - "t3.large": 0.0832, - "t3.xlarge": 0.1664, - "t3a.medium": 0.0376, - "t3a.large": 0.0752, - "m5.large": 0.096, - "m5.xlarge": 0.192, - "m5.2xlarge": 0.384, - "m5a.large": 0.086, - "m5a.xlarge": 0.172, - "c5.large": 0.085, - "c5.xlarge": 0.17, - "c5.2xlarge": 0.34, - "c5a.large": 0.077, - "c5a.xlarge": 0.154, - "r5.large": 0.126, - "r5.xlarge": 0.252, - "t4g.micro": 84e-4, - "t4g.small": 0.0168, - "t4g.medium": 0.0336, - "t4g.large": 0.0672, - "t4g.xlarge": 0.1344, - "m6g.medium": 0.0385, - "m6g.large": 0.077, - "m6g.xlarge": 0.154, - "m6g.2xlarge": 0.308, - "m7g.medium": 0.0408, - "m7g.large": 0.0816, - "m7g.xlarge": 0.1632, - "m7g.2xlarge": 0.3264, - "c6g.medium": 0.034, - "c6g.large": 0.068, - "c6g.xlarge": 0.136, - "c6g.2xlarge": 0.272, - "c7g.large": 0.0725, - "c7g.xlarge": 0.145, - "r6g.large": 0.1008, - "r6g.xlarge": 0.2016 - }, - "us-east-2": { - "t3.micro": 0.0104, - "t3.small": 0.0208, - "t3.medium": 0.0416, - "t3.large": 0.0832, - "t3.xlarge": 0.1664, - "t3a.medium": 0.0376, - "t3a.large": 0.0752, - "m5.large": 0.096, - "m5.xlarge": 0.192, - "m5.2xlarge": 0.384, - "m5a.large": 0.086, - "m5a.xlarge": 0.172, - "c5.large": 0.085, - "c5.xlarge": 0.17, - "c5.2xlarge": 0.34, - "c5a.large": 0.077, - "c5a.xlarge": 0.154, - "r5.large": 0.126, - "r5.xlarge": 0.252, - "t4g.micro": 84e-4, - "t4g.small": 0.0168, - "t4g.medium": 0.0336, - "t4g.large": 0.0672, - "t4g.xlarge": 0.1344, - "m6g.medium": 0.0385, - "m6g.large": 0.077, - "m6g.xlarge": 0.154, - "m6g.2xlarge": 0.308, - "m7g.medium": 0.0408, - "m7g.large": 0.0816, - "m7g.xlarge": 0.1632, - "m7g.2xlarge": 0.3264, - "c6g.medium": 0.034, - "c6g.large": 0.068, - "c6g.xlarge": 0.136, - "c6g.2xlarge": 0.272, - "c7g.large": 0.0725, - "c7g.xlarge": 0.145, - "r6g.large": 0.1008, - "r6g.xlarge": 0.2016 - }, - "us-west-1": { - "t3.micro": 0.0116, - "t3.small": 0.0232, - "t3.medium": 0.0464, - "t3.large": 0.0928, - "t3.xlarge": 0.1856, - "m5.large": 0.107, - "m5.xlarge": 0.214, - "m5.2xlarge": 0.428, - "c5.large": 0.096, - "c5.xlarge": 0.192, - "c5.2xlarge": 0.384, - "t4g.medium": 0.0376, - "t4g.large": 0.0752, - "t4g.xlarge": 0.1504, - "m6g.large": 0.086, - "m6g.xlarge": 0.172, - "m6g.2xlarge": 0.344, - "m7g.large": 0.0912, - "m7g.xlarge": 0.1824, - "c6g.large": 0.076, - "c6g.xlarge": 0.152, - "r6g.large": 0.1127, - "r6g.xlarge": 0.2254 - }, - "us-west-2": { - "t3.micro": 0.0104, - "t3.small": 0.0208, - "t3.medium": 0.0416, - "t3.large": 0.0832, - "t3.xlarge": 0.1664, - "t3a.medium": 0.0376, - "t3a.large": 0.0752, - "m5.large": 0.096, - "m5.xlarge": 0.192, - "m5.2xlarge": 0.384, - "m5a.large": 0.086, - "m5a.xlarge": 0.172, - "c5.large": 0.085, - "c5.xlarge": 0.17, - "c5.2xlarge": 0.34, - "c5a.large": 0.077, - "c5a.xlarge": 0.154, - "r5.large": 0.126, - "r5.xlarge": 0.252, - "t4g.micro": 84e-4, - "t4g.small": 0.0168, - "t4g.medium": 0.0336, - "t4g.large": 0.0672, - "t4g.xlarge": 0.1344, - "m6g.medium": 0.0385, - "m6g.large": 0.077, - "m6g.xlarge": 0.154, - "m6g.2xlarge": 0.308, - "m7g.medium": 0.0408, - "m7g.large": 0.0816, - "m7g.xlarge": 0.1632, - "m7g.2xlarge": 0.3264, - "c6g.medium": 0.034, - "c6g.large": 0.068, - "c6g.xlarge": 0.136, - "c6g.2xlarge": 0.272, - "c7g.large": 0.0725, - "c7g.xlarge": 0.145, - "r6g.large": 0.1008, - "r6g.xlarge": 0.2016 - }, - "eu-west-1": { - "t3.micro": 0.0116, - "t3.small": 0.0232, - "t3.medium": 0.0456, - "t3.large": 0.0912, - "t3.xlarge": 0.1824, - "t3a.medium": 0.0416, - "t3a.large": 0.0832, - "m5.large": 0.107, - "m5.xlarge": 0.214, - "m5.2xlarge": 0.428, - "m5a.large": 0.096, - "m5a.xlarge": 0.192, - "c5.large": 0.096, - "c5.xlarge": 0.192, - "c5.2xlarge": 0.384, - "c5a.large": 0.087, - "c5a.xlarge": 0.174, - "r5.large": 0.141, - "r5.xlarge": 0.282, - "t4g.micro": 94e-4, - "t4g.small": 0.0188, - "t4g.medium": 0.0376, - "t4g.large": 0.0752, - "t4g.xlarge": 0.1504, - "m6g.medium": 0.043, - "m6g.large": 0.086, - "m6g.xlarge": 0.172, - "m6g.2xlarge": 0.344, - "m7g.medium": 0.0456, - "m7g.large": 0.0912, - "m7g.xlarge": 0.1824, - "m7g.2xlarge": 0.3648, - "c6g.medium": 0.038, - "c6g.large": 0.076, - "c6g.xlarge": 0.152, - "c6g.2xlarge": 0.304, - "c7g.large": 0.0812, - "c7g.xlarge": 0.1624, - "r6g.large": 0.1127, - "r6g.xlarge": 0.2254 - }, - "eu-west-2": { - "t3.micro": 0.0126, - "t3.small": 0.0252, - "t3.medium": 0.0504, - "t3.large": 0.1008, - "t3.xlarge": 0.2016, - "m5.large": 0.1178, - "m5.xlarge": 0.2356, - "m5.2xlarge": 0.4712, - "c5.large": 0.1054, - "c5.xlarge": 0.2108, - "c5.2xlarge": 0.4216, - "t4g.medium": 0.0414, - "t4g.large": 0.0828, - "t4g.xlarge": 0.1656, - "m6g.large": 0.0945, - "m6g.xlarge": 0.189, - "m6g.2xlarge": 0.378, - "m7g.large": 0.1001, - "m7g.xlarge": 0.2002, - "c6g.large": 0.0836, - "c6g.xlarge": 0.1672, - "r6g.large": 0.124, - "r6g.xlarge": 0.248 - }, - "eu-central-1": { - "t3.micro": 0.012, - "t3.small": 0.024, - "t3.medium": 0.0496, - "t3.large": 0.0992, - "t3.xlarge": 0.1984, - "t3a.medium": 0.0448, - "t3a.large": 0.0896, - "m5.large": 0.115, - "m5.xlarge": 0.23, - "m5.2xlarge": 0.46, - "m5a.large": 0.103, - "m5a.xlarge": 0.206, - "c5.large": 0.102, - "c5.xlarge": 0.204, - "c5.2xlarge": 0.408, - "r5.large": 0.151, - "r5.xlarge": 0.302, - "t4g.micro": 0.01, - "t4g.small": 0.02, - "t4g.medium": 0.0416, - "t4g.large": 0.0832, - "t4g.xlarge": 0.1664, - "m6g.medium": 0.046, - "m6g.large": 0.092, - "m6g.xlarge": 0.184, - "m6g.2xlarge": 0.368, - "m7g.medium": 0.0488, - "m7g.large": 0.0976, - "m7g.xlarge": 0.1952, - "m7g.2xlarge": 0.3904, - "c6g.medium": 0.041, - "c6g.large": 0.082, - "c6g.xlarge": 0.164, - "c6g.2xlarge": 0.328, - "c7g.large": 0.0875, - "c7g.xlarge": 0.175, - "r6g.large": 0.121, - "r6g.xlarge": 0.242 - }, - "eu-north-1": { - "t3.micro": 0.0108, - "t3.small": 0.0216, - "t3.medium": 0.0432, - "t3.large": 0.0864, - "t3.xlarge": 0.1728, - "m5.large": 0.1, - "m5.xlarge": 0.2, - "m5.2xlarge": 0.4, - "c5.large": 0.089, - "c5.xlarge": 0.178, - "c5.2xlarge": 0.356, - "t4g.medium": 0.0362, - "t4g.large": 0.0724, - "t4g.xlarge": 0.1448, - "m6g.large": 0.08, - "m6g.xlarge": 0.16, - "m6g.2xlarge": 0.32, - "m7g.large": 0.0848, - "m7g.xlarge": 0.1696, - "c6g.large": 0.0712, - "c6g.xlarge": 0.1424, - "r6g.large": 0.1054, - "r6g.xlarge": 0.2108 - }, - "ap-southeast-1": { - "t3.micro": 0.0132, - "t3.small": 0.0264, - "t3.medium": 0.0528, - "t3.large": 0.1056, - "t3.xlarge": 0.2112, - "m5.large": 0.124, - "m5.xlarge": 0.248, - "m5.2xlarge": 0.496, - "c5.large": 0.107, - "c5.xlarge": 0.214, - "c5.2xlarge": 0.428, - "t4g.medium": 0.0438, - "t4g.large": 0.0876, - "t4g.xlarge": 0.1752, - "m6g.large": 0.0992, - "m6g.xlarge": 0.1984, - "m6g.2xlarge": 0.3968, - "m7g.large": 0.1051, - "m7g.xlarge": 0.2102, - "c6g.large": 0.086, - "c6g.xlarge": 0.172, - "r6g.large": 0.1307, - "r6g.xlarge": 0.2614 - }, - "ap-southeast-2": { - "t3.micro": 0.0136, - "t3.small": 0.0272, - "t3.medium": 0.0544, - "t3.large": 0.1088, - "t3.xlarge": 0.2176, - "t3a.medium": 0.0492, - "t3a.large": 0.0984, - "m5.large": 0.134, - "m5.xlarge": 0.268, - "m5.2xlarge": 0.536, - "m5a.large": 0.12, - "m5a.xlarge": 0.24, - "c5.large": 0.118, - "c5.xlarge": 0.236, - "c5.2xlarge": 0.472, - "r5.large": 0.176, - "r5.xlarge": 0.352, - "t4g.micro": 0.0113, - "t4g.small": 0.0226, - "t4g.medium": 0.0452, - "t4g.large": 0.0904, - "t4g.xlarge": 0.1808, - "m6g.medium": 0.0535, - "m6g.large": 0.107, - "m6g.xlarge": 0.214, - "m6g.2xlarge": 0.428, - "m7g.medium": 0.0567, - "m7g.large": 0.1134, - "m7g.xlarge": 0.2268, - "m7g.2xlarge": 0.4536, - "c6g.medium": 0.047, - "c6g.large": 0.094, - "c6g.xlarge": 0.188, - "c6g.2xlarge": 0.376, - "c7g.large": 0.1002, - "c7g.xlarge": 0.2004, - "r6g.large": 0.1411, - "r6g.xlarge": 0.2822 - }, - "ap-northeast-1": { - "t3.micro": 0.014, - "t3.small": 0.028, - "t3.medium": 0.056, - "t3.large": 0.112, - "t3.xlarge": 0.224, - "t3a.medium": 0.0504, - "t3a.large": 0.1008, - "m5.large": 0.128, - "m5.xlarge": 0.256, - "m5.2xlarge": 0.512, - "m5a.large": 0.115, - "m5a.xlarge": 0.23, - "c5.large": 0.114, - "c5.xlarge": 0.228, - "c5.2xlarge": 0.456, - "r5.large": 0.169, - "r5.xlarge": 0.338, - "t4g.micro": 0.0116, - "t4g.small": 0.0232, - "t4g.medium": 0.0464, - "t4g.large": 0.0928, - "t4g.xlarge": 0.1856, - "m6g.medium": 0.0549, - "m6g.large": 0.1098, - "m6g.xlarge": 0.2196, - "m6g.2xlarge": 0.4392, - "m7g.medium": 0.0582, - "m7g.large": 0.1164, - "m7g.xlarge": 0.2328, - "m7g.2xlarge": 0.4656, - "c6g.medium": 0.0482, - "c6g.large": 0.0964, - "c6g.xlarge": 0.1928, - "c6g.2xlarge": 0.3856, - "c7g.large": 0.1028, - "c7g.xlarge": 0.2056, - "r6g.large": 0.1448, - "r6g.xlarge": 0.2896 - }, - "ap-south-1": { - "t3.micro": 0.0114, - "t3.small": 0.0228, - "t3.medium": 0.0456, - "t3.large": 0.0912, - "t3.xlarge": 0.1824, - "t3a.medium": 0.041, - "t3a.large": 0.082, - "m5.large": 0.106, - "m5.xlarge": 0.212, - "m5.2xlarge": 0.424, - "c5.large": 0.094, - "c5.xlarge": 0.188, - "c5.2xlarge": 0.376, - "r5.large": 0.1396, - "r5.xlarge": 0.2792, - "t4g.micro": 95e-4, - "t4g.small": 0.019, - "t4g.medium": 0.038, - "t4g.large": 0.076, - "t4g.xlarge": 0.152, - "m6g.medium": 0.0454, - "m6g.large": 0.0908, - "m6g.xlarge": 0.1816, - "m6g.2xlarge": 0.3632, - "m7g.medium": 0.0481, - "m7g.large": 0.0962, - "m7g.xlarge": 0.1924, - "m7g.2xlarge": 0.3848, - "c6g.medium": 0.0399, - "c6g.large": 0.0798, - "c6g.xlarge": 0.1596, - "c6g.2xlarge": 0.3192, - "r6g.large": 0.1197, - "r6g.xlarge": 0.2394 + gcp: { + regions: { + "us-central1": { + location: "Iowa", + grid_intensity_gco2e_per_kwh: 340, + pue: 1.1, + water_intensity_litres_per_kwh: 0.4 + }, + "us-east1": { + location: "South Carolina", + grid_intensity_gco2e_per_kwh: 380, + pue: 1.1, + water_intensity_litres_per_kwh: 0.43 + }, + "us-east4": { + location: "Virginia", + grid_intensity_gco2e_per_kwh: 380, + pue: 1.1, + water_intensity_litres_per_kwh: 0.43 + }, + "us-west1": { + location: "Oregon", + grid_intensity_gco2e_per_kwh: 200, + pue: 1.1, + water_intensity_litres_per_kwh: 0.18 + }, + "us-west2": { + location: "Los Angeles", + grid_intensity_gco2e_per_kwh: 220, + pue: 1.1, + water_intensity_litres_per_kwh: 0.35 + }, + "europe-west1": { + location: "Belgium", + grid_intensity_gco2e_per_kwh: 144, + pue: 1.1, + water_intensity_litres_per_kwh: 0.2 + }, + "europe-west2": { + location: "London", + grid_intensity_gco2e_per_kwh: 268, + pue: 1.1, + water_intensity_litres_per_kwh: 0.25 + }, + "europe-west4": { + location: "Netherlands", + grid_intensity_gco2e_per_kwh: 270, + pue: 1.1, + water_intensity_litres_per_kwh: 0.2 + }, + "europe-north1": { + location: "Finland", + grid_intensity_gco2e_per_kwh: 18, + pue: 1.1, + water_intensity_litres_per_kwh: 0.12 + }, + "asia-southeast1": { + location: "Singapore", + grid_intensity_gco2e_per_kwh: 408, + pue: 1.1, + water_intensity_litres_per_kwh: 0.58 + }, + "asia-east1": { + location: "Taiwan", + grid_intensity_gco2e_per_kwh: 486, + pue: 1.1, + water_intensity_litres_per_kwh: 0.45 + }, + "asia-northeast1": { + location: "Tokyo", + grid_intensity_gco2e_per_kwh: 506, + pue: 1.1, + water_intensity_litres_per_kwh: 0.5 + }, + "asia-south1": { + location: "Mumbai", + grid_intensity_gco2e_per_kwh: 723, + pue: 1.1, + water_intensity_litres_per_kwh: 0.72 + }, + "northamerica-northeast1": { + location: "Montreal", + grid_intensity_gco2e_per_kwh: 28, + pue: 1.1, + water_intensity_litres_per_kwh: 0.2 + }, + "southamerica-east1": { + location: "Sao Paulo", + grid_intensity_gco2e_per_kwh: 74, + pue: 1.1, + water_intensity_litres_per_kwh: 0.35 + } }, - "ca-central-1": { - "t3.micro": 0.0116, - "t3.small": 0.0232, - "t3.medium": 0.0464, - "t3.large": 0.0928, - "t3.xlarge": 0.1856, - "t3a.medium": 0.0418, - "t3a.large": 0.0836, - "m5.large": 0.107, - "m5.xlarge": 0.214, - "m5.2xlarge": 0.428, - "m5a.large": 0.096, - "m5a.xlarge": 0.192, - "c5.large": 0.095, - "c5.xlarge": 0.19, - "c5.2xlarge": 0.38, - "r5.large": 0.141, - "r5.xlarge": 0.282, - "t4g.micro": 96e-4, - "t4g.small": 0.0192, - "t4g.medium": 0.0386, - "t4g.large": 0.0772, - "t4g.xlarge": 0.1544, - "m6g.medium": 0.0462, - "m6g.large": 0.0924, - "m6g.xlarge": 0.1848, - "m6g.2xlarge": 0.3696, - "m7g.medium": 0.049, - "m7g.large": 0.098, - "m7g.xlarge": 0.196, - "m7g.2xlarge": 0.392, - "c6g.medium": 0.0408, - "c6g.large": 0.0816, - "c6g.xlarge": 0.1632, - "c6g.2xlarge": 0.3264, - "c7g.large": 0.087, - "c7g.xlarge": 0.174, - "r6g.large": 0.1218, - "r6g.xlarge": 0.2436 + instances: { + "n2-standard-2": { + architecture: "x86_64", + vcpus: 2, + memory_gb: 8, + power_watts: { + idle: 6.8, + max: 20.4 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + "n2-standard-4": { + architecture: "x86_64", + vcpus: 4, + memory_gb: 16, + power_watts: { + idle: 13.6, + max: 40.8 + }, + embodied_co2e_grams_per_month: 2083.3 + }, + "n2-standard-8": { + architecture: "x86_64", + vcpus: 8, + memory_gb: 32, + power_watts: { + idle: 27.2, + max: 81.6 + }, + embodied_co2e_grams_per_month: 4166.7 + }, + "n2d-standard-2": { + architecture: "x86_64", + vcpus: 2, + memory_gb: 8, + power_watts: { + idle: 6.5, + max: 19.5 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + "n2d-standard-4": { + architecture: "x86_64", + vcpus: 4, + memory_gb: 16, + power_watts: { + idle: 13, + max: 39 + }, + embodied_co2e_grams_per_month: 2083.3 + }, + "t2a-standard-1": { + architecture: "arm64", + vcpus: 1, + memory_gb: 4, + power_watts: { + idle: 2.1, + max: 6.6 + }, + embodied_co2e_grams_per_month: 416.7 + }, + "t2a-standard-2": { + architecture: "arm64", + vcpus: 2, + memory_gb: 8, + power_watts: { + idle: 4.1, + max: 13.2 + }, + embodied_co2e_grams_per_month: 833.3 + }, + "t2a-standard-4": { + architecture: "arm64", + vcpus: 4, + memory_gb: 16, + power_watts: { + idle: 8.2, + max: 26.4 + }, + embodied_co2e_grams_per_month: 1666.7 + }, + "t2a-standard-8": { + architecture: "arm64", + vcpus: 8, + memory_gb: 32, + power_watts: { + idle: 16.4, + max: 52.8 + }, + embodied_co2e_grams_per_month: 3333.3 + }, + "c2-standard-4": { + architecture: "x86_64", + vcpus: 4, + memory_gb: 16, + power_watts: { + idle: 13, + max: 44 + }, + embodied_co2e_grams_per_month: 2083.3 + }, + "c2-standard-8": { + architecture: "x86_64", + vcpus: 8, + memory_gb: 32, + power_watts: { + idle: 26, + max: 88 + }, + embodied_co2e_grams_per_month: 4166.7 + }, + "e2-standard-2": { + architecture: "x86_64", + vcpus: 2, + memory_gb: 8, + power_watts: { + idle: 3.4, + max: 10.2 + }, + embodied_co2e_grams_per_month: 1041.7 + }, + "e2-standard-4": { + architecture: "x86_64", + vcpus: 4, + memory_gb: 16, + power_watts: { + idle: 6.8, + max: 20.4 + }, + embodied_co2e_grams_per_month: 2083.3 + }, + "t2d-standard-1": { + architecture: "x86_64", + vcpus: 1, + memory_gb: 4, + power_watts: { + idle: 1.7, + max: 5.1 + }, + embodied_co2e_grams_per_month: 520.8 + }, + "t2d-standard-2": { + architecture: "x86_64", + vcpus: 2, + memory_gb: 8, + power_watts: { + idle: 3.4, + max: 10.2 + }, + embodied_co2e_grams_per_month: 1041.7 + } }, - "sa-east-1": { - "t3.micro": 0.0168, - "t3.small": 0.0336, - "t3.medium": 0.0672, - "t3.large": 0.1344, - "t3.xlarge": 0.2688, - "m5.large": 0.162, - "m5.xlarge": 0.324, - "m5.2xlarge": 0.648, - "c5.large": 0.144, - "c5.xlarge": 0.288, - "c5.2xlarge": 0.576, - "t4g.medium": 0.056, - "t4g.large": 0.112, - "t4g.xlarge": 0.224, - "m6g.large": 0.1296, - "m6g.xlarge": 0.2592, - "m6g.2xlarge": 0.5184, - "m7g.large": 0.1374, - "m7g.xlarge": 0.2748, - "c6g.large": 0.1152, - "c6g.xlarge": 0.2304, - "r6g.large": 0.1706, - "r6g.xlarge": 0.3412 + pricing_usd_per_hour: { + "us-central1": { + "n2-standard-2": 0.097, + "n2-standard-4": 0.194, + "n2-standard-8": 0.388, + "n2d-standard-2": 0.086, + "n2d-standard-4": 0.172, + "t2a-standard-1": 0.038, + "t2a-standard-2": 0.076, + "t2a-standard-4": 0.152, + "t2a-standard-8": 0.304, + "c2-standard-4": 0.2, + "c2-standard-8": 0.4, + "e2-standard-2": 0.067, + "e2-standard-4": 0.134, + "t2d-standard-1": 0.037, + "t2d-standard-2": 0.074 + }, + "us-east1": { + "n2-standard-2": 0.097, + "n2-standard-4": 0.194, + "n2-standard-8": 0.388, + "n2d-standard-2": 0.086, + "n2d-standard-4": 0.172, + "t2a-standard-1": 0.038, + "t2a-standard-2": 0.076, + "t2a-standard-4": 0.152, + "t2a-standard-8": 0.304, + "c2-standard-4": 0.2, + "c2-standard-8": 0.4, + "e2-standard-2": 0.067, + "e2-standard-4": 0.134, + "t2d-standard-1": 0.037, + "t2d-standard-2": 0.074 + }, + "us-east4": { + "n2-standard-2": 0.104, + "n2-standard-4": 0.208, + "n2-standard-8": 0.416, + "n2d-standard-2": 0.092, + "n2d-standard-4": 0.185, + "t2a-standard-1": 0.041, + "t2a-standard-2": 0.082, + "t2a-standard-4": 0.163, + "t2a-standard-8": 0.326, + "c2-standard-4": 0.214, + "c2-standard-8": 0.428, + "e2-standard-2": 0.072, + "e2-standard-4": 0.144, + "t2d-standard-1": 0.04, + "t2d-standard-2": 0.079 + }, + "us-west1": { + "n2-standard-2": 0.097, + "n2-standard-4": 0.194, + "n2-standard-8": 0.388, + "n2d-standard-2": 0.086, + "n2d-standard-4": 0.172, + "t2a-standard-1": 0.038, + "t2a-standard-2": 0.076, + "t2a-standard-4": 0.152, + "t2a-standard-8": 0.304, + "c2-standard-4": 0.2, + "c2-standard-8": 0.4, + "e2-standard-2": 0.067, + "e2-standard-4": 0.134, + "t2d-standard-1": 0.037, + "t2d-standard-2": 0.074 + }, + "europe-west1": { + "n2-standard-2": 0.108, + "n2-standard-4": 0.216, + "n2-standard-8": 0.432, + "n2d-standard-2": 0.096, + "n2d-standard-4": 0.192, + "t2a-standard-1": 0.042, + "t2a-standard-2": 0.085, + "t2a-standard-4": 0.169, + "t2a-standard-8": 0.339, + "c2-standard-4": 0.222, + "c2-standard-8": 0.445, + "e2-standard-2": 0.075, + "e2-standard-4": 0.15, + "t2d-standard-1": 0.044, + "t2d-standard-2": 0.087 + }, + "europe-west2": { + "n2-standard-2": 0.116, + "n2-standard-4": 0.231, + "n2-standard-8": 0.462, + "n2d-standard-2": 0.103, + "n2d-standard-4": 0.205, + "t2a-standard-1": 0.045, + "t2a-standard-2": 0.091, + "t2a-standard-4": 0.181, + "t2a-standard-8": 0.362, + "c2-standard-4": 0.238, + "c2-standard-8": 0.475, + "e2-standard-2": 0.08, + "e2-standard-4": 0.16, + "t2d-standard-1": 0.047, + "t2d-standard-2": 0.093 + }, + "europe-north1": { + "n2-standard-2": 0.108, + "n2-standard-4": 0.216, + "n2-standard-8": 0.432, + "n2d-standard-2": 0.096, + "n2d-standard-4": 0.192, + "t2a-standard-1": 0.042, + "t2a-standard-2": 0.085, + "t2a-standard-4": 0.169, + "t2a-standard-8": 0.339, + "c2-standard-4": 0.222, + "c2-standard-8": 0.445, + "e2-standard-2": 0.075, + "e2-standard-4": 0.15, + "t2d-standard-1": 0.044, + "t2d-standard-2": 0.087 + }, + "asia-southeast1": { + "n2-standard-2": 0.117, + "n2-standard-4": 0.234, + "n2-standard-8": 0.469, + "n2d-standard-2": 0.104, + "n2d-standard-4": 0.208, + "t2a-standard-1": 0.046, + "t2a-standard-2": 0.092, + "t2a-standard-4": 0.184, + "t2a-standard-8": 0.368, + "c2-standard-4": 0.241, + "c2-standard-8": 0.482, + "e2-standard-2": 0.081, + "e2-standard-4": 0.162, + "t2d-standard-1": 0.048, + "t2d-standard-2": 0.095 + }, + "asia-northeast1": { + "n2-standard-2": 0.126, + "n2-standard-4": 0.252, + "n2-standard-8": 0.504, + "n2d-standard-2": 0.112, + "n2d-standard-4": 0.223, + "t2a-standard-1": 0.049, + "t2a-standard-2": 0.099, + "t2a-standard-4": 0.197, + "t2a-standard-8": 0.395, + "c2-standard-4": 0.259, + "c2-standard-8": 0.517, + "e2-standard-2": 0.087, + "e2-standard-4": 0.174, + "t2d-standard-1": 0.051, + "t2d-standard-2": 0.102 + }, + "asia-south1": { + "n2-standard-2": 0.102, + "n2-standard-4": 0.204, + "n2-standard-8": 0.407, + "n2d-standard-2": 0.091, + "n2d-standard-4": 0.181, + "t2a-standard-1": 0.04, + "t2a-standard-2": 0.08, + "t2a-standard-4": 0.159, + "t2a-standard-8": 0.319, + "c2-standard-4": 0.209, + "c2-standard-8": 0.419, + "e2-standard-2": 0.07, + "e2-standard-4": 0.14, + "t2d-standard-1": 0.039, + "t2d-standard-2": 0.078 + }, + "northamerica-northeast1": { + "n2-standard-2": 0.104, + "n2-standard-4": 0.208, + "n2-standard-8": 0.416, + "n2d-standard-2": 0.092, + "n2d-standard-4": 0.185, + "t2a-standard-1": 0.041, + "t2a-standard-2": 0.082, + "t2a-standard-4": 0.163, + "t2a-standard-8": 0.326, + "c2-standard-4": 0.214, + "c2-standard-8": 0.428, + "e2-standard-2": 0.072, + "e2-standard-4": 0.144, + "t2d-standard-1": 0.04, + "t2d-standard-2": 0.079 + }, + "southamerica-east1": { + "n2-standard-2": 0.138, + "n2-standard-4": 0.276, + "n2-standard-8": 0.552, + "n2d-standard-2": 0.123, + "n2d-standard-4": 0.245, + "t2a-standard-1": 0.054, + "t2a-standard-2": 0.108, + "t2a-standard-4": 0.216, + "t2a-standard-8": 0.431, + "c2-standard-4": 0.283, + "c2-standard-8": 0.566, + "e2-standard-2": 0.095, + "e2-standard-4": 0.19, + "t2d-standard-1": 0.053, + "t2d-standard-2": 0.105 + } } } }; @@ -1008,8 +1968,8 @@ var factors_default = { // package.json var package_default = { name: "greenops-cli", - version: "0.4.0", - description: "Carbon footprint linting for Terraform plans. Analyses infrastructure changes for CO2e impact and cost, posts recommendations directly on GitHub PRs.", + version: "0.5.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: { "greenops-cli": "dist/index.cjs" @@ -1031,6 +1991,9 @@ var package_default = { "greenops", "cloud", "aws", + "azure", + "gcp", + "multi-cloud", "sustainability", "devops", "ci", @@ -1061,39 +2024,70 @@ var package_default = { // extractor.ts var import_node_fs = require("node:fs"); var import_node_path = require("node:path"); -function isKnownAfterApply(change, fieldPath) { - if (!change) - return true; - if (change.after_unknown?.[fieldPath] === true) - return true; - if (change.after?.[fieldPath] === null || change.after?.[fieldPath] === void 0) - return true; - return false; +var SUPPORTED_TYPES = { + aws: ["aws_instance", "aws_db_instance"], + azure: ["azurerm_linux_virtual_machine", "azurerm_windows_virtual_machine", "azurerm_virtual_machine"], + gcp: ["google_compute_instance"] +}; +var COMPUTE_RELEVANT_TYPES = [ + "aws_launch_template", + "aws_autoscaling_group", + "aws_ecs_service", + "aws_eks_node_group", + "aws_lambda_function", + "azurerm_virtual_machine_scale_set", + "azurerm_kubernetes_cluster", + "azurerm_function_app", + "google_compute_instance_template", + "google_container_cluster", + "google_cloudfunctions_function" +]; +function detectProvider(resourceType) { + if (resourceType.startsWith("aws_")) + return "aws"; + if (resourceType.startsWith("azurerm_")) + return "azure"; + if (resourceType.startsWith("google_")) + return "gcp"; + return null; } -function extractProviderRegion(plan) { +function extractProviderRegions(plan) { + const result2 = { aws: null, azure: null, gcp: null }; const providerConfig = plan.configuration?.provider_config; if (!providerConfig) - return null; + return result2; for (const [key, provider] of Object.entries(providerConfig)) { if (key === "aws" || key.startsWith("aws.")) { const alias = provider.expressions?.alias?.constant_value; if (alias && key !== "aws") continue; const region = provider.expressions?.region?.constant_value; - if (region && typeof region === "string") - return region; + if (region && !result2.aws) + result2.aws = region; } - } - for (const [key, provider] of Object.entries(providerConfig)) { - if (key === "aws" || key.startsWith("aws.")) { + if (key === "azurerm" || key.startsWith("azurerm.")) { + const location = provider.expressions?.location?.constant_value; + if (location && !result2.azure) + result2.azure = location; + } + if (key === "google" || key.startsWith("google.")) { const region = provider.expressions?.region?.constant_value; - if (region && typeof region === "string") - return region; + if (region && !result2.gcp) + result2.gcp = region; } } - return null; + return result2; +} +function isKnownAfterApply(change, fieldPath) { + if (!change) + return true; + if (change.after_unknown?.[fieldPath] === true) + return true; + if (change.after?.[fieldPath] === null || change.after?.[fieldPath] === void 0) + return true; + return false; } -function resolveRegion(change, providerRegion) { +function resolveAwsRegion(change, providerRegion) { if (change?.after?.arn && typeof change.after.arn === "string") { const parts = change.after.arn.split(":"); if (parts.length >= 4 && parts[3]) @@ -1104,16 +2098,67 @@ function resolveRegion(change, providerRegion) { if (azMatch) return azMatch[1]; } - if (change?.after?.region && typeof change.after.region === "string") { + if (change?.after?.region && typeof change.after.region === "string") return change.after.region; - } - if (change?.before?.region && typeof change.before.region === "string") { + if (change?.before?.region && typeof change.before.region === "string") return change.before.region; + if (providerRegion) + return providerRegion; + return null; +} +function resolveAzureRegion(change, providerRegion) { + const raw = change?.after?.location ?? change?.before?.location ?? providerRegion; + if (!raw || typeof raw !== "string") + return null; + return raw.toLowerCase().replace(/\s+/g, ""); +} +function resolveGcpRegion(change, providerRegion) { + if (change?.after?.region && typeof change.after.region === "string") + return change.after.region; + if (change?.after?.zone && typeof change.after.zone === "string") { + const zoneMatch = change.after.zone.match(/^([a-z]+-[a-z]+\d+)/); + if (zoneMatch) + return zoneMatch[1]; } + if (change?.before?.region && typeof change.before.region === "string") + return change.before.region; if (providerRegion) return providerRegion; return null; } +function extractAwsInstanceType(res, plannedValuesMap) { + const isDb = res.type === "aws_db_instance"; + const typeField = isDb ? "instance_class" : "instance_type"; + if (isKnownAfterApply(res.change, typeField)) { + const plannedType = plannedValuesMap.get(res.address)?.[typeField]; + if (typeof plannedType !== "string") + return { instanceType: null, skipReason: "known_after_apply" }; + if (!res.change.after) + res.change.after = {}; + res.change.after[typeField] = plannedType; + } + let instanceType = res.change?.after?.[typeField]; + if (typeof instanceType !== "string") + return { instanceType: null, skipReason: "known_after_apply" }; + if (isDb && instanceType.startsWith("db.")) { + instanceType = instanceType.replace(/^db\./, ""); + if (!instanceType.includes(".")) + return { instanceType: null, skipReason: "unsupported_instance" }; + } + return { instanceType }; +} +function extractAzureInstanceType(res) { + const size = res.change?.after?.size ?? res.change?.before?.size; + if (!size || typeof size !== "string") + return { instanceType: null, skipReason: "known_after_apply" }; + return { instanceType: size }; +} +function extractGcpInstanceType(res) { + const machineType = res.change?.after?.machine_type ?? res.change?.before?.machine_type; + if (!machineType || typeof machineType !== "string") + return { instanceType: null, skipReason: "known_after_apply" }; + return { instanceType: machineType }; +} function extractResourceInputs(planFilePath) { const result2 = { resources: [], skipped: [], unsupportedTypes: [] }; const resolvedPath = (0, import_node_path.isAbsolute)(planFilePath) ? planFilePath : (0, import_node_path.resolve)(process.cwd(), planFilePath); @@ -1136,68 +2181,66 @@ function extractResourceInputs(planFilePath) { return result2; } const typedPlan = plan; - const providerRegion = extractProviderRegion(typedPlan); + const providerRegions = extractProviderRegions(typedPlan); const plannedValuesMap = /* @__PURE__ */ new Map(); for (const r of typedPlan.planned_values?.root_module?.resources ?? []) { if (r.address && r.values) plannedValuesMap.set(r.address, r.values); } + const allSupportedTypes = Object.values(SUPPORTED_TYPES).flat(); for (const rawRes of typedPlan.resource_changes) { const res = rawRes; const actions = res.change?.actions; if (!Array.isArray(actions) || !actions.includes("create") && !actions.includes("update")) { continue; } - const SUPPORTED_TYPES = ["aws_instance", "aws_db_instance"]; - const COMPUTE_RELEVANT_TYPES = ["aws_launch_template", "aws_autoscaling_group", "aws_ecs_service", "aws_eks_node_group", "aws_lambda_function"]; - if (!SUPPORTED_TYPES.includes(res.type)) { + const provider = detectProvider(res.type); + if (!allSupportedTypes.includes(res.type)) { if (COMPUTE_RELEVANT_TYPES.includes(res.type) && !result2.unsupportedTypes.includes(res.type)) { result2.unsupportedTypes.push(res.type); } continue; } - const isDb = res.type === "aws_db_instance"; - const typeField = isDb ? "instance_class" : "instance_type"; - if (isKnownAfterApply(res.change, typeField)) { - const plannedType = plannedValuesMap.get(res.address)?.[typeField]; - if (typeof plannedType !== "string") { - result2.skipped.push({ resourceId: res.address, reason: "known_after_apply" }); - continue; - } - if (!res.change.after) - res.change.after = {}; - res.change.after[typeField] = plannedType; - } - let instanceType = res.change.after[typeField]; - if (typeof res.change.after[typeField] !== "string") { - result2.skipped.push({ resourceId: res.address, reason: "known_after_apply" }); + if (!provider) continue; + let instanceType = null; + let skipReason; + let region = null; + if (provider === "aws") { + const extracted2 = extractAwsInstanceType(res, plannedValuesMap); + instanceType = extracted2.instanceType; + skipReason = extracted2.skipReason; + if (instanceType) + region = resolveAwsRegion(res.change, providerRegions.aws); + } else if (provider === "azure") { + const extracted2 = extractAzureInstanceType(res); + instanceType = extracted2.instanceType; + skipReason = extracted2.skipReason; + if (instanceType) + region = resolveAzureRegion(res.change, providerRegions.azure); + } else if (provider === "gcp") { + const extracted2 = extractGcpInstanceType(res); + instanceType = extracted2.instanceType; + skipReason = extracted2.skipReason; + if (instanceType) + region = resolveGcpRegion(res.change, providerRegions.gcp); } - if (isDb && instanceType.startsWith("db.")) { - instanceType = instanceType.replace(/^db\./, ""); - if (!instanceType.includes(".")) { - result2.skipped.push({ resourceId: res.address, reason: "unsupported_instance" }); - continue; - } + if (!instanceType || skipReason) { + result2.skipped.push({ resourceId: res.address, reason: skipReason ?? "known_after_apply" }); + continue; } - const region = resolveRegion(res.change, providerRegion); if (!region) { result2.skipped.push({ resourceId: res.address, reason: "known_after_apply" }); continue; } - result2.resources.push({ - resourceId: res.address, - // Correctly applies nested addresses as the ID (e.g. module.compute.aws_instance.api) - instanceType, - region - }); + result2.resources.push({ resourceId: res.address, instanceType, region, provider }); } return result2; } // engine.ts var HOURS_PER_MONTH = 730; -var GRAMS_PER_KWH_TO_KWH_FACTOR = 1e3; +var GRAMS_PER_KWH = 1e3; 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}`); @@ -1210,50 +2253,70 @@ function resolveUtilization(input, ledger) { function linearInterpolationWatts(idle, max, utilization) { return idle + (max - idle) * utilization; } -function wattsToScope2Carbon(watts, hours, pue, gridIntensityGco2ePerKwh) { - const energyKwh = watts * pue * hours / GRAMS_PER_KWH_TO_KWH_FACTOR; - return energyKwh * gridIntensityGco2ePerKwh; +function wattsToScope2Carbon(watts, hours, pue, gridIntensity) { + return watts * pue * hours / GRAMS_PER_KWH * gridIntensity; } -function wattsToWater(watts, hours, waterIntensityLitresPerKwh) { - const energyKwh = watts * hours / GRAMS_PER_KWH_TO_KWH_FACTOR; - return energyKwh * waterIntensityLitresPerKwh; +function wattsToWater(watts, hours, wue) { + return watts * hours / GRAMS_PER_KWH * wue; } var ARM_UPGRADE_MAP = { - // x86 → ARM64 upgrade targets (same vCPU/RAM class, lower power + embodied) - t3: "t4g", - t3a: "t4g", - m5: "m6g", - m5a: "m6g", - c5: "c6g", - c5a: "c6g", - r5: "r6g", - r5a: "r6g" + aws: { + t3: "t4g", + t3a: "t4g", + m5: "m6g", + m5a: "m6g", + c5: "c6g", + c5a: "c6g", + r5: "r6g", + r5a: "r6g" + }, + azure: { + "Standard_D2s_v3": "Standard_D2ps_v5", + "Standard_D4s_v3": "Standard_D4ps_v5", + "Standard_D8s_v3": "Standard_D8ps_v5", + "Standard_D2s_v4": "Standard_D2ps_v5", + "Standard_D4s_v4": "Standard_D4ps_v5" + }, + gcp: { + n2: "t2a", + n2d: "t2a", + e2: "t2a" + } }; -function getArmAlternative(instanceType, ledger) { +function getArmAlternative(instanceType, provider, ledger) { + const providerLedger = ledger[provider]; + const map = ARM_UPGRADE_MAP[provider]; + if (provider === "azure") { + const candidate2 = map[instanceType]; + return candidate2 && providerLedger.instances[candidate2] ? candidate2 : null; + } const [family, size] = instanceType.split("."); if (!family || !size) return null; - const armFamily = ARM_UPGRADE_MAP[family]; + const armFamily = map[family]; if (!armFamily) return null; const candidate = `${armFamily}.${size}`; - return ledger.instances[candidate] ? candidate : null; + return providerLedger.instances[candidate] ? candidate : null; } -function getCleanerRegion(currentRegion, instanceType, ledger) { - const regions = Object.entries(ledger.regions).filter(([regionId]) => { +function getCleanerRegion(currentRegion, instanceType, provider, ledger) { + const providerLedger = ledger[provider]; + const regions = Object.entries(providerLedger.regions).filter(([regionId]) => { if (regionId === currentRegion) return false; - return !!ledger.pricing_usd_per_hour[regionId]?.[instanceType]; + return !!providerLedger.pricing_usd_per_hour[regionId]?.[instanceType]; }).sort(([, a], [, b]) => a.grid_intensity_gco2e_per_kwh - b.grid_intensity_gco2e_per_kwh); if (regions.length === 0) return null; const [cleanestRegionId, cleanestRegion] = regions[0]; - const currentIntensity = ledger.regions[currentRegion]?.grid_intensity_gco2e_per_kwh ?? Infinity; + const currentIntensity = providerLedger.regions[currentRegion]?.grid_intensity_gco2e_per_kwh ?? Infinity; if (cleanestRegion.grid_intensity_gco2e_per_kwh >= currentIntensity * 0.9) return null; return cleanestRegionId; } function calculateBaseline(input, ledger = factors_default) { + const provider = input.provider ?? "aws"; + const providerLedger = ledger[provider]; const hours = input.hoursPerMonth ?? HOURS_PER_MONTH; const utilization = resolveUtilization(input, ledger); const zeroResult = (unsupportedReason, gridIntensity = 0, embodied = 0, waterIntensity = 0) => ({ @@ -1273,23 +2336,23 @@ function calculateBaseline(input, ledger = factors_default) { waterIntensityLitresPerKwhApplied: waterIntensity } }); - const regionData = ledger.regions[input.region]; + const regionData = providerLedger.regions[input.region]; if (!regionData) { - return zeroResult(`Region "${input.region}" is not present in the Open GreenOps Methodology Ledger v${ledger.metadata.ledger_version}.`); + return zeroResult(`Region "${input.region}" is not present in the ${provider.toUpperCase()} section of the Open GreenOps Methodology Ledger v${ledger.metadata.ledger_version}.`); } - const instanceData = ledger.instances[input.instanceType]; + const instanceData = providerLedger.instances[input.instanceType]; if (!instanceData) { return zeroResult( - `Instance type "${input.instanceType}" is not present in the Open GreenOps Methodology Ledger v${ledger.metadata.ledger_version}.`, + `Instance type "${input.instanceType}" is not present in the ${provider.toUpperCase()} section of the Open GreenOps Methodology Ledger v${ledger.metadata.ledger_version}.`, regionData.grid_intensity_gco2e_per_kwh, 0, regionData.water_intensity_litres_per_kwh ); } - const pricePerHour = ledger.pricing_usd_per_hour[input.region]?.[input.instanceType]; + const pricePerHour = providerLedger.pricing_usd_per_hour[input.region]?.[input.instanceType]; if (pricePerHour === void 0) { return zeroResult( - `No pricing data for "${input.instanceType}" in "${input.region}" in the Open GreenOps Methodology Ledger v${ledger.metadata.ledger_version}.`, + `No pricing data for "${input.instanceType}" in "${input.region}" (${provider.toUpperCase()}).`, regionData.grid_intensity_gco2e_per_kwh, instanceData.embodied_co2e_grams_per_month, regionData.water_intensity_litres_per_kwh @@ -1308,11 +2371,7 @@ function calculateBaseline(input, ledger = factors_default) { regionData.grid_intensity_gco2e_per_kwh ); const embodiedCo2eGramsPerMonth = instanceData.embodied_co2e_grams_per_month * (hours / HOURS_PER_MONTH); - const waterLitresPerMonth = wattsToWater( - effectiveWatts, - hours, - regionData.water_intensity_litres_per_kwh - ); + const waterLitresPerMonth = wattsToWater(effectiveWatts, hours, regionData.water_intensity_litres_per_kwh); const totalLifecycleCo2eGramsPerMonth = totalCo2eGramsPerMonth + embodiedCo2eGramsPerMonth; const totalCostUsdPerMonth = pricePerHour * hours; const confidence = input.avgUtilization !== void 0 ? "MEDIUM" : "HIGH"; @@ -1336,8 +2395,10 @@ function calculateBaseline(input, ledger = factors_default) { function generateRecommendation(input, baseline, ledger = factors_default) { if (baseline.confidence === "LOW_ASSUMED_DEFAULT") return null; + const provider = input.provider ?? "aws"; + const providerLedger = ledger[provider]; const candidates = []; - const armAlternative = getArmAlternative(input.instanceType, ledger); + const armAlternative = getArmAlternative(input.instanceType, provider, ledger); if (armAlternative) { const armEstimate = calculateBaseline({ ...input, instanceType: armAlternative }, ledger); if (armEstimate.confidence !== "LOW_ASSUMED_DEFAULT") { @@ -1345,17 +2406,17 @@ function generateRecommendation(input, baseline, ledger = factors_default) { const costDelta = armEstimate.totalCostUsdPerMonth - baseline.totalCostUsdPerMonth; const embodiedDelta = armEstimate.embodiedCo2eGramsPerMonth - baseline.embodiedCo2eGramsPerMonth; if (co2Delta < 0 && costDelta < 0) { - const embodiedNote = embodiedDelta < 0 ? ` ARM64 also reduces embodied (Scope 3) carbon by ${Math.abs(Math.round(embodiedDelta))}g CO2e/month.` : ""; + const embodiedNote = embodiedDelta < 0 ? ` ARM also reduces embodied (Scope 3) carbon by ${Math.abs(Math.round(embodiedDelta))}g CO2e/month.` : ""; candidates.push({ suggestedInstanceType: armAlternative, co2eDeltaGramsPerMonth: co2Delta, costDeltaUsdPerMonth: costDelta, - rationale: `Switching from ${input.instanceType} (x86_64) to ${armAlternative} (ARM64) provides identical vCPU and memory at lower power draw, reducing Scope 2 carbon by ${Math.abs(Math.round(co2Delta))}g CO2e/month and cost by $${Math.abs(costDelta).toFixed(2)}/month.${embodiedNote}` + rationale: `Switching from ${input.instanceType} to ${armAlternative} (ARM) provides identical vCPU and memory at lower power draw, saving ${Math.abs(Math.round(co2Delta))}g CO2e/month and $${Math.abs(costDelta).toFixed(2)}/month.${embodiedNote}` }); } } } - const cleanerRegion = getCleanerRegion(input.region, input.instanceType, ledger); + const cleanerRegion = getCleanerRegion(input.region, input.instanceType, provider, ledger); if (cleanerRegion) { const regionEstimate = calculateBaseline({ ...input, region: cleanerRegion }, ledger); if (regionEstimate.confidence !== "LOW_ASSUMED_DEFAULT") { @@ -1363,7 +2424,7 @@ function generateRecommendation(input, baseline, ledger = factors_default) { const costDelta = regionEstimate.totalCostUsdPerMonth - baseline.totalCostUsdPerMonth; const co2ReductionPct = baseline.totalCo2eGramsPerMonth > 0 ? Math.abs(co2Delta) / baseline.totalCo2eGramsPerMonth : 0; if (co2Delta < 0 && co2ReductionPct > 0.15) { - const regionName = ledger.regions[cleanerRegion]?.location ?? cleanerRegion; + const regionName = providerLedger.regions[cleanerRegion]?.location ?? cleanerRegion; const costNote = costDelta > 0 ? ` (note: cost increases by $${costDelta.toFixed(2)}/month)` : ` saving $${Math.abs(costDelta).toFixed(2)}/month`; const waterDelta = regionEstimate.waterLitresPerMonth - baseline.waterLitresPerMonth; const waterNote = waterDelta < -0.1 ? ` Water consumption also decreases by ${Math.abs(waterDelta).toFixed(1)}L/month.` : ""; @@ -1371,7 +2432,7 @@ function generateRecommendation(input, baseline, ledger = factors_default) { suggestedRegion: cleanerRegion, co2eDeltaGramsPerMonth: co2Delta, costDeltaUsdPerMonth: costDelta, - rationale: `Moving ${input.instanceType} from ${input.region} to ${regionName} (${cleanerRegion}) reduces Scope 2 grid carbon intensity from ${ledger.regions[input.region]?.grid_intensity_gco2e_per_kwh}g to ${ledger.regions[cleanerRegion]?.grid_intensity_gco2e_per_kwh}g CO2e/kWh, saving ${Math.abs(Math.round(co2Delta))}g CO2e/month${costNote}.${waterNote}` + rationale: `Moving ${input.instanceType} from ${input.region} to ${regionName} (${cleanerRegion}) reduces grid carbon intensity from ${providerLedger.regions[input.region]?.grid_intensity_gco2e_per_kwh}g to ${providerLedger.regions[cleanerRegion]?.grid_intensity_gco2e_per_kwh}g CO2e/kWh, saving ${Math.abs(Math.round(co2Delta))}g CO2e/month${costNote}.${waterNote}` }); } } @@ -1415,10 +2476,12 @@ function analysePlan(resources, skipped, planFile2, ledger = factors_default, un potentialCostSavingUsdPerMonth: 0 } ); + const providers = [...new Set(resources.map((r) => r.provider ?? "aws"))]; return { analysedAt: (/* @__PURE__ */ new Date()).toISOString(), ledgerVersion: ledger.metadata.ledger_version, planFile: planFile2, + providers: providers.length > 0 ? providers : ["aws"], resources: analysedResources, skipped, unsupportedTypes, @@ -2007,17 +3070,27 @@ if (values.help) { process.exit(0); } if (values.coverage) { - const rawFs = Object.assign({}, factors_default); + const rawFs = factors_default; + const awsRegions = Object.keys(rawFs.aws.regions); + const azureRegions = Object.keys(rawFs.azure.regions); + const gcpRegions = Object.keys(rawFs.gcp.regions); + const awsInstances = Object.keys(rawFs.aws.instances); + const azureInstances = Object.keys(rawFs.azure.instances); + const gcpInstances = Object.keys(rawFs.gcp.instances); if (values.format === "json") { console.log(JSON.stringify({ ledgerVersion: rawFs.metadata.ledger_version, - regions: Object.keys(rawFs.regions), - instances: Object.keys(rawFs.instances) + providers: { + aws: { regions: awsRegions, instances: awsInstances }, + azure: { regions: azureRegions, instances: azureInstances }, + gcp: { regions: gcpRegions, instances: gcpInstances } + } }, null, 2)); } else { console.log(`GreenOps Methodology Ledger v${rawFs.metadata.ledger_version}`); - console.log(`Supported Regions (${Object.keys(rawFs.regions).length}): ${Object.keys(rawFs.regions).join(", ")}`); - console.log(`Supported Instances (${Object.keys(rawFs.instances).length}): ${Object.keys(rawFs.instances).join(", ")}`); + console.log(`AWS: ${awsRegions.length} regions | ${awsInstances.length} instances`); + console.log(`Azure: ${azureRegions.length} regions | ${azureInstances.length} instances`); + console.log(`GCP: ${gcpRegions.length} regions | ${gcpInstances.length} instances`); } process.exit(0); } diff --git a/engine.test.ts b/engine.test.ts index 83336e7..04ed4e0 100644 --- a/engine.test.ts +++ b/engine.test.ts @@ -173,4 +173,142 @@ describe('calculateBaseline', () => { }); assert.equal(result.scope, 'SCOPE_2_AND_3'); }); + + // --------------------------------------------------------------------------- + // Azure engine tests + // --------------------------------------------------------------------------- + + it('Azure: calculates correct Scope 2 CO2e for Standard_D2s_v3 in eastus', () => { + // Audit trace: + // Instance: Standard_D2s_v3 (x86_64, 2 vCPU, 8GB) + // Power: idle=6.8W, max=20.4W → effective at 50% = 13.6W + // Region: eastus — grid=380.0 gCO2e/kWh, PUE=1.125, WUE=0.43 L/kWh + // Scope 2: 13.6 × 1.125 × 730 / 1000 × 380.0 = 4244.22 gCO2e/month + // Scope 3: 1041.7 gCO2e/month (2 vCPU × 520.83 g/vCPU/month, x86) + // Water: 13.6 × 730 / 1000 × 0.43 = 4.26904 L/month + // Cost: $0.096 × 730 = $70.08/month + const result = calculateBaseline({ + resourceId: 'azurerm_linux_virtual_machine.api', + instanceType: 'Standard_D2s_v3', + region: 'eastus', + provider: 'azure', + }); + + assert.equal(result.confidence, 'HIGH'); + assert.equal(result.scope, 'SCOPE_2_AND_3'); + assert.ok(Math.abs(result.totalCo2eGramsPerMonth - 4244.22) < 0.01, `Scope 2 expected ~4244.22, got ${result.totalCo2eGramsPerMonth}`); + assert.ok(Math.abs(result.embodiedCo2eGramsPerMonth - 1041.7) < 0.01, 'Scope 3 should be 1041.7g'); + assert.ok(Math.abs(result.waterLitresPerMonth - 4.26904) < 0.001, `Water expected ~4.27L, got ${result.waterLitresPerMonth}`); + assert.ok(Math.abs(result.totalCostUsdPerMonth - 70.08) < 0.001, `Cost expected ~$70.08, got ${result.totalCostUsdPerMonth}`); + }); + + it('Azure: ARM upgrade recommendation (Standard_D2s_v3 → Standard_D2ps_v5) produces savings', () => { + // Standard_D2s_v3 (x86) → Standard_D2ps_v5 (ARM64/Ampere) + // ARM Scope 2: 2699.45 gCO2e/month — saves 1544.77g CO2e/month + // ARM Scope 3: 833.3g (2 vCPU ARM, 20% discount) — saves 208.4g embodied + // ARM Cost: $0.077 × 730 = $56.21 — saves $13.87/month + const baseline = calculateBaseline({ + resourceId: 'test', instanceType: 'Standard_D2s_v3', region: 'eastus', provider: 'azure', + }); + const arm = calculateBaseline({ + resourceId: 'test', instanceType: 'Standard_D2ps_v5', region: 'eastus', provider: 'azure', + }); + + assert.ok(arm.totalCo2eGramsPerMonth < baseline.totalCo2eGramsPerMonth, 'ARM should have lower Scope 2'); + assert.ok(arm.embodiedCo2eGramsPerMonth < baseline.embodiedCo2eGramsPerMonth, 'ARM should have lower embodied carbon'); + assert.ok(arm.totalCostUsdPerMonth < baseline.totalCostUsdPerMonth, 'ARM should be cheaper'); + assert.ok(Math.abs(arm.embodiedCo2eGramsPerMonth - 833.3) < 0.01, 'ARM Scope 3 should be 833.3g'); + }); + + it('Azure: returns LOW_ASSUMED_DEFAULT for unsupported instance', () => { + const result = calculateBaseline({ + resourceId: 'test', instanceType: 'Standard_M96ms_v3', region: 'eastus', provider: 'azure', + }); + assert.equal(result.confidence, 'LOW_ASSUMED_DEFAULT'); + assert.equal(result.totalCo2eGramsPerMonth, 0); + assert.ok(result.unsupportedReason?.includes('Standard_M96ms_v3')); + }); + + it('Azure: returns LOW_ASSUMED_DEFAULT for unsupported region', () => { + const result = calculateBaseline({ + resourceId: 'test', instanceType: 'Standard_D2s_v3', region: 'newzealandnorth', provider: 'azure', + }); + assert.equal(result.confidence, 'LOW_ASSUMED_DEFAULT'); + }); + + // --------------------------------------------------------------------------- + // GCP engine tests + // --------------------------------------------------------------------------- + + it('GCP: calculates correct Scope 2 CO2e for n2-standard-2 in us-central1', () => { + // Audit trace: + // Instance: n2-standard-2 (x86_64, 2 vCPU, 8GB) + // Power: idle=6.8W, max=20.4W → effective at 50% = 13.6W + // Region: us-central1 (Iowa) — grid=340.0 gCO2e/kWh, PUE=1.10, WUE=0.40 L/kWh + // Scope 2: 13.6 × 1.10 × 730 / 1000 × 340.0 = 3713.072 gCO2e/month + // Scope 3: 1041.7 gCO2e/month (2 vCPU x86) + // Water: 13.6 × 730 / 1000 × 0.40 = 3.9712 L/month + // Cost: $0.097 × 730 = $70.81/month + const result = calculateBaseline({ + resourceId: 'google_compute_instance.web', + instanceType: 'n2-standard-2', + region: 'us-central1', + provider: 'gcp', + }); + + assert.equal(result.confidence, 'HIGH'); + assert.equal(result.scope, 'SCOPE_2_AND_3'); + assert.ok(Math.abs(result.totalCo2eGramsPerMonth - 3713.072) < 0.01, `Scope 2 expected ~3713.07, got ${result.totalCo2eGramsPerMonth}`); + assert.ok(Math.abs(result.embodiedCo2eGramsPerMonth - 1041.7) < 0.01, 'Scope 3 should be 1041.7g'); + assert.ok(Math.abs(result.waterLitresPerMonth - 3.9712) < 0.001, `Water expected ~3.97L, got ${result.waterLitresPerMonth}`); + assert.ok(Math.abs(result.totalCostUsdPerMonth - 70.81) < 0.001, `Cost expected ~$70.81, got ${result.totalCostUsdPerMonth}`); + }); + + it('GCP: ARM upgrade recommendation (n2-standard-2 → t2a-standard-2) produces savings', () => { + // n2-standard-2 (x86) → t2a-standard-2 (ARM64/Ampere T2A) + // ARM Scope 2: 2361.62 gCO2e/month — saves ~1351g CO2e/month + // ARM Scope 3: 833.3g (20% discount) — saves 208.4g embodied + // ARM Cost: $0.076 × 730 = $55.48 — saves ~$15.33/month + const baseline = calculateBaseline({ + resourceId: 'test', instanceType: 'n2-standard-2', region: 'us-central1', provider: 'gcp', + }); + const arm = calculateBaseline({ + resourceId: 'test', instanceType: 't2a-standard-2', region: 'us-central1', provider: 'gcp', + }); + + assert.ok(arm.totalCo2eGramsPerMonth < baseline.totalCo2eGramsPerMonth, 'ARM should have lower Scope 2'); + assert.ok(arm.embodiedCo2eGramsPerMonth < baseline.embodiedCo2eGramsPerMonth, 'ARM should have lower embodied carbon'); + assert.ok(arm.totalCostUsdPerMonth < baseline.totalCostUsdPerMonth, 'ARM should be cheaper'); + assert.ok(Math.abs(arm.embodiedCo2eGramsPerMonth - 833.3) < 0.01, 'ARM Scope 3 should be 833.3g'); + }); + + it('GCP: GCP region has lower PUE than AWS (1.10 vs 1.13) — produces lower carbon than equivalent AWS', () => { + // Same instance power draw, same grid intensity (both ~380g), but GCP PUE=1.10 vs AWS PUE=1.13 + // AWS us-east-1: grid=384.5, PUE=1.13 + // GCP us-east1: grid=380.0, PUE=1.10 + // GCP should produce slightly less carbon per watt-hour + const aws = calculateBaseline({ + resourceId: 'test', instanceType: 'm5.large', region: 'us-east-1', provider: 'aws', + }); + const gcp = calculateBaseline({ + resourceId: 'test', instanceType: 'n2-standard-2', region: 'us-east1', provider: 'gcp', + }); + // Both are 2 vCPU x86 — similar power draw, similar grid. GCP PUE advantage should show. + assert.ok(gcp.totalCo2eGramsPerMonth < aws.totalCo2eGramsPerMonth, 'GCP us-east1 should have lower Scope 2 than AWS us-east-1 due to lower PUE'); + }); + + it('GCP: returns LOW_ASSUMED_DEFAULT for unsupported instance', () => { + const result = calculateBaseline({ + resourceId: 'test', instanceType: 'n1-standard-2', region: 'us-central1', provider: 'gcp', + }); + assert.equal(result.confidence, 'LOW_ASSUMED_DEFAULT'); + assert.ok(result.unsupportedReason?.includes('n1-standard-2')); + }); + + it('GCP: returns LOW_ASSUMED_DEFAULT for unsupported region', () => { + const result = calculateBaseline({ + resourceId: 'test', instanceType: 'n2-standard-2', region: 'me-west1', provider: 'gcp', + }); + assert.equal(result.confidence, 'LOW_ASSUMED_DEFAULT'); + }); }); diff --git a/engine.ts b/engine.ts index cfb76e6..cd3cfde 100644 --- a/engine.ts +++ b/engine.ts @@ -6,10 +6,11 @@ import type { PlanAnalysisResult, ConfidenceLevel, PowerModel, + CloudProvider, } from './types'; // --------------------------------------------------------------------------- -// Internal types for ledger shape (mirrors factors.json v1.3.0) +// Internal types for ledger shape (mirrors factors.json v2.0.0) // --------------------------------------------------------------------------- interface LedgerInstance { @@ -17,9 +18,6 @@ interface LedgerInstance { vcpus: number; memory_gb: number; power_watts: { idle: number; max: number }; - /** Prorated Scope 3 embodied carbon from manufacturing lifecycle (gCO2e/month). - * Source: CCF DELL R740 baseline (1,200 kgCO2e/server, 4yr lifespan, 48 vCPUs). - * ARM (Graviton) applies 20% discount reflecting smaller die + lower TDP. */ embodied_co2e_grams_per_month: number; } @@ -27,21 +25,23 @@ interface LedgerRegion { location: string; grid_intensity_gco2e_per_kwh: number; pue: number; - /** AWS WUE (Water Usage Effectiveness) in litres per kWh of IT load. - * Source: AWS 2023 Sustainability Report. */ water_intensity_litres_per_kwh: number; } +interface ProviderLedger { + regions: Record; + instances: Record; + pricing_usd_per_hour: Record>; +} + interface Ledger { metadata: { ledger_version: string; - assumptions: { - default_utilization: { value: number }; - }; + assumptions: { default_utilization: { value: number } }; }; - regions: Record; - instances: Record; - pricing_usd_per_hour: Record>; + aws: ProviderLedger; + azure: ProviderLedger; + gcp: ProviderLedger; } // --------------------------------------------------------------------------- @@ -49,7 +49,7 @@ interface Ledger { // --------------------------------------------------------------------------- const HOURS_PER_MONTH = 730; -const GRAMS_PER_KWH_TO_KWH_FACTOR = 1000; +const GRAMS_PER_KWH = 1000; // --------------------------------------------------------------------------- // Helpers @@ -65,78 +65,72 @@ function resolveUtilization(input: ResourceInput, ledger: Ledger): number { return input.avgUtilization ?? ledger.metadata.assumptions.default_utilization.value; } -/** - * Linear interpolation power model (standard CCF methodology): - * W = W_idle + (W_max - W_idle) × utilization - * - * CPU-only. Memory power draw tracked via memory_gb in factors.json - * but not yet included in the calculation (reserved for v1.4.0). - */ function linearInterpolationWatts(idle: number, max: number, utilization: number): number { return idle + (max - idle) * utilization; } -/** - * Converts effective CPU watts to monthly Scope 2 CO2e grams. - * energy_kwh = watts × pue × hours / 1000 - * carbon_g = energy_kwh × grid_intensity_gco2e_per_kwh - */ -function wattsToScope2Carbon( - watts: number, - hours: number, - pue: number, - gridIntensityGco2ePerKwh: number -): number { - const energyKwh = (watts * pue * hours) / GRAMS_PER_KWH_TO_KWH_FACTOR; - return energyKwh * gridIntensityGco2ePerKwh; +function wattsToScope2Carbon(watts: number, hours: number, pue: number, gridIntensity: number): number { + return (watts * pue * hours / GRAMS_PER_KWH) * gridIntensity; } -/** - * Calculates monthly water consumption from operational energy. - * energy_kwh (IT load, before PUE) × WUE litres/kWh - */ -function wattsToWater(watts: number, hours: number, waterIntensityLitresPerKwh: number): number { - const energyKwh = (watts * hours) / GRAMS_PER_KWH_TO_KWH_FACTOR; - return energyKwh * waterIntensityLitresPerKwh; +function wattsToWater(watts: number, hours: number, wue: number): number { + return (watts * hours / GRAMS_PER_KWH) * wue; } // --------------------------------------------------------------------------- -// ARM recommendation map +// ARM upgrade maps — per provider // --------------------------------------------------------------------------- -const ARM_UPGRADE_MAP: Record = { - // x86 → ARM64 upgrade targets (same vCPU/RAM class, lower power + embodied) - t3: 't4g', - t3a: 't4g', - m5: 'm6g', - m5a: 'm6g', - c5: 'c6g', - c5a: 'c6g', - r5: 'r6g', - r5a: 'r6g', +const ARM_UPGRADE_MAP: Record> = { + aws: { + t3: 't4g', t3a: 't4g', + m5: 'm6g', m5a: 'm6g', + c5: 'c6g', c5a: 'c6g', + r5: 'r6g', r5a: 'r6g', + }, + azure: { + 'Standard_D2s_v3': 'Standard_D2ps_v5', + 'Standard_D4s_v3': 'Standard_D4ps_v5', + 'Standard_D8s_v3': 'Standard_D8ps_v5', + 'Standard_D2s_v4': 'Standard_D2ps_v5', + 'Standard_D4s_v4': 'Standard_D4ps_v5', + }, + gcp: { + n2: 't2a', + n2d: 't2a', + e2: 't2a', + }, }; -function getArmAlternative(instanceType: string, ledger: Ledger): string | null { +function getArmAlternative(instanceType: string, provider: CloudProvider, ledger: Ledger): string | null { + const providerLedger = ledger[provider]; + const map = ARM_UPGRADE_MAP[provider]; + + if (provider === 'azure') { + const candidate = map[instanceType]; + return candidate && providerLedger.instances[candidate] ? candidate : null; + } + const [family, size] = instanceType.split('.'); if (!family || !size) return null; - const armFamily = ARM_UPGRADE_MAP[family]; + const armFamily = map[family]; if (!armFamily) return null; const candidate = `${armFamily}.${size}`; - return ledger.instances[candidate] ? candidate : null; + return providerLedger.instances[candidate] ? candidate : null; } -function getCleanerRegion(currentRegion: string, instanceType: string, ledger: Ledger): string | null { - const regions = Object.entries(ledger.regions) +function getCleanerRegion(currentRegion: string, instanceType: string, provider: CloudProvider, ledger: Ledger): string | null { + const providerLedger = ledger[provider]; + const regions = Object.entries(providerLedger.regions) .filter(([regionId]) => { if (regionId === currentRegion) return false; - return !!ledger.pricing_usd_per_hour[regionId]?.[instanceType]; + return !!providerLedger.pricing_usd_per_hour[regionId]?.[instanceType]; }) .sort(([, a], [, b]) => a.grid_intensity_gco2e_per_kwh - b.grid_intensity_gco2e_per_kwh); if (regions.length === 0) return null; - const [cleanestRegionId, cleanestRegion] = regions[0]; - const currentIntensity = ledger.regions[currentRegion]?.grid_intensity_gco2e_per_kwh ?? Infinity; + const currentIntensity = providerLedger.regions[currentRegion]?.grid_intensity_gco2e_per_kwh ?? Infinity; if (cleanestRegion.grid_intensity_gco2e_per_kwh >= currentIntensity * 0.9) return null; return cleanestRegionId; } @@ -145,20 +139,12 @@ function getCleanerRegion(currentRegion: string, instanceType: string, ledger: L // Core Engine // --------------------------------------------------------------------------- -/** - * Calculates the full environmental and cost baseline for a single resource. - * - * Returns three emission dimensions: - * - Scope 2 operational (CPU power × grid carbon intensity × PUE) - * - Scope 3 embodied (prorated hardware manufacturing lifecycle) - * - Water consumption (operational energy × regional AWS WUE) - * - * Every assumption applied is recorded in assumptionsApplied for audit transparency. - */ export function calculateBaseline( input: ResourceInput, ledger: Ledger = factorsData as Ledger ): EmissionAndCostEstimate { + const provider: CloudProvider = input.provider ?? 'aws'; + const providerLedger = ledger[provider]; const hours = input.hoursPerMonth ?? HOURS_PER_MONTH; const utilization = resolveUtilization(input, ledger); @@ -180,23 +166,23 @@ export function calculateBaseline( }, }); - const regionData = ledger.regions[input.region]; + const regionData = providerLedger.regions[input.region]; if (!regionData) { - return zeroResult(`Region "${input.region}" is not present in the Open GreenOps Methodology Ledger v${ledger.metadata.ledger_version}.`); + return zeroResult(`Region "${input.region}" is not present in the ${provider.toUpperCase()} section of the Open GreenOps Methodology Ledger v${ledger.metadata.ledger_version}.`); } - const instanceData = ledger.instances[input.instanceType]; + const instanceData = providerLedger.instances[input.instanceType]; if (!instanceData) { return zeroResult( - `Instance type "${input.instanceType}" is not present in the Open GreenOps Methodology Ledger v${ledger.metadata.ledger_version}.`, + `Instance type "${input.instanceType}" is not present in the ${provider.toUpperCase()} section of the Open GreenOps Methodology Ledger v${ledger.metadata.ledger_version}.`, regionData.grid_intensity_gco2e_per_kwh, 0, regionData.water_intensity_litres_per_kwh ); } - const pricePerHour = ledger.pricing_usd_per_hour[input.region]?.[input.instanceType]; + const pricePerHour = providerLedger.pricing_usd_per_hour[input.region]?.[input.instanceType]; if (pricePerHour === undefined) { return zeroResult( - `No pricing data for "${input.instanceType}" in "${input.region}" in the Open GreenOps Methodology Ledger v${ledger.metadata.ledger_version}.`, + `No pricing data for "${input.instanceType}" in "${input.region}" (${provider.toUpperCase()}).`, regionData.grid_intensity_gco2e_per_kwh, instanceData.embodied_co2e_grams_per_month, regionData.water_intensity_litres_per_kwh @@ -205,25 +191,14 @@ export function calculateBaseline( const powerModel: PowerModel = 'LINEAR_INTERPOLATION'; const effectiveWatts = linearInterpolationWatts( - instanceData.power_watts.idle, - instanceData.power_watts.max, - utilization + instanceData.power_watts.idle, instanceData.power_watts.max, utilization ); - // Scope 2: operational emissions const totalCo2eGramsPerMonth = wattsToScope2Carbon( effectiveWatts, hours, regionData.pue, regionData.grid_intensity_gco2e_per_kwh ); - - // Scope 3: embodied emissions — prorated by hours if partial month - const embodiedCo2eGramsPerMonth = - instanceData.embodied_co2e_grams_per_month * (hours / HOURS_PER_MONTH); - - // Water consumption - const waterLitresPerMonth = wattsToWater( - effectiveWatts, hours, regionData.water_intensity_litres_per_kwh - ); - + const embodiedCo2eGramsPerMonth = instanceData.embodied_co2e_grams_per_month * (hours / HOURS_PER_MONTH); + const waterLitresPerMonth = wattsToWater(effectiveWatts, hours, regionData.water_intensity_litres_per_kwh); const totalLifecycleCo2eGramsPerMonth = totalCo2eGramsPerMonth + embodiedCo2eGramsPerMonth; const totalCostUsdPerMonth = pricePerHour * hours; const confidence: ConfidenceLevel = input.avgUtilization !== undefined ? 'MEDIUM' : 'HIGH'; @@ -246,24 +221,17 @@ export function calculateBaseline( }; } -/** - * Generates the single best recommendation for reducing environmental impact. - * - * Scoring: 60% weight on CO2 reduction, 40% on cost reduction. - * Both dimensions normalised to percentage-of-baseline. - * ARM upgrades surface embodied carbon benefit in the rationale. - */ export function generateRecommendation( input: ResourceInput, baseline: EmissionAndCostEstimate, ledger: Ledger = factorsData as Ledger ): UpgradeRecommendation | null { if (baseline.confidence === 'LOW_ASSUMED_DEFAULT') return null; - + const provider: CloudProvider = input.provider ?? 'aws'; + const providerLedger = ledger[provider]; const candidates: UpgradeRecommendation[] = []; - // Strategy 1: ARM upgrade - const armAlternative = getArmAlternative(input.instanceType, ledger); + const armAlternative = getArmAlternative(input.instanceType, provider, ledger); if (armAlternative) { const armEstimate = calculateBaseline({ ...input, instanceType: armAlternative }, ledger); if (armEstimate.confidence !== 'LOW_ASSUMED_DEFAULT') { @@ -272,20 +240,18 @@ export function generateRecommendation( const embodiedDelta = armEstimate.embodiedCo2eGramsPerMonth - baseline.embodiedCo2eGramsPerMonth; if (co2Delta < 0 && costDelta < 0) { const embodiedNote = embodiedDelta < 0 - ? ` ARM64 also reduces embodied (Scope 3) carbon by ${Math.abs(Math.round(embodiedDelta))}g CO2e/month.` - : ''; + ? ` ARM also reduces embodied (Scope 3) carbon by ${Math.abs(Math.round(embodiedDelta))}g CO2e/month.` : ''; candidates.push({ suggestedInstanceType: armAlternative, co2eDeltaGramsPerMonth: co2Delta, costDeltaUsdPerMonth: costDelta, - rationale: `Switching from ${input.instanceType} (x86_64) to ${armAlternative} (ARM64) provides identical vCPU and memory at lower power draw, reducing Scope 2 carbon by ${Math.abs(Math.round(co2Delta))}g CO2e/month and cost by $${Math.abs(costDelta).toFixed(2)}/month.${embodiedNote}`, + rationale: `Switching from ${input.instanceType} to ${armAlternative} (ARM) provides identical vCPU and memory at lower power draw, saving ${Math.abs(Math.round(co2Delta))}g CO2e/month and $${Math.abs(costDelta).toFixed(2)}/month.${embodiedNote}`, }); } } } - // Strategy 2: Region shift - const cleanerRegion = getCleanerRegion(input.region, input.instanceType, ledger); + const cleanerRegion = getCleanerRegion(input.region, input.instanceType, provider, ledger); if (cleanerRegion) { const regionEstimate = calculateBaseline({ ...input, region: cleanerRegion }, ledger); if (regionEstimate.confidence !== 'LOW_ASSUMED_DEFAULT') { @@ -294,7 +260,7 @@ export function generateRecommendation( const co2ReductionPct = baseline.totalCo2eGramsPerMonth > 0 ? Math.abs(co2Delta) / baseline.totalCo2eGramsPerMonth : 0; if (co2Delta < 0 && co2ReductionPct > 0.15) { - const regionName = ledger.regions[cleanerRegion]?.location ?? cleanerRegion; + const regionName = providerLedger.regions[cleanerRegion]?.location ?? cleanerRegion; const costNote = costDelta > 0 ? ` (note: cost increases by $${costDelta.toFixed(2)}/month)` : ` saving $${Math.abs(costDelta).toFixed(2)}/month`; @@ -305,14 +271,13 @@ export function generateRecommendation( suggestedRegion: cleanerRegion, co2eDeltaGramsPerMonth: co2Delta, costDeltaUsdPerMonth: costDelta, - rationale: `Moving ${input.instanceType} from ${input.region} to ${regionName} (${cleanerRegion}) reduces Scope 2 grid carbon intensity from ${ledger.regions[input.region]?.grid_intensity_gco2e_per_kwh}g to ${ledger.regions[cleanerRegion]?.grid_intensity_gco2e_per_kwh}g CO2e/kWh, saving ${Math.abs(Math.round(co2Delta))}g CO2e/month${costNote}.${waterNote}`, + rationale: `Moving ${input.instanceType} from ${input.region} to ${regionName} (${cleanerRegion}) reduces grid carbon intensity from ${providerLedger.regions[input.region]?.grid_intensity_gco2e_per_kwh}g to ${providerLedger.regions[cleanerRegion]?.grid_intensity_gco2e_per_kwh}g CO2e/kWh, saving ${Math.abs(Math.round(co2Delta))}g CO2e/month${costNote}.${waterNote}`, }); } } } if (candidates.length === 0) return null; - const scored = candidates.map((rec) => { const co2Pct = baseline.totalCo2eGramsPerMonth > 0 ? Math.abs(rec.co2eDeltaGramsPerMonth) / baseline.totalCo2eGramsPerMonth : 0; @@ -324,10 +289,6 @@ export function generateRecommendation( return scored[0].rec; } -// --------------------------------------------------------------------------- -// Plan-level aggregator -// --------------------------------------------------------------------------- - export function analysePlan( resources: ResourceInput[], skipped: PlanAnalysisResult['skipped'], @@ -365,10 +326,13 @@ export function analysePlan( } ); + const providers = [...new Set(resources.map(r => r.provider ?? 'aws'))] as CloudProvider[]; + return { analysedAt: new Date().toISOString(), ledgerVersion: ledger.metadata.ledger_version, planFile, + providers: providers.length > 0 ? providers : ['aws'], resources: analysedResources, skipped, unsupportedTypes, diff --git a/extractor.test.ts b/extractor.test.ts index e8950de..5831d4f 100644 --- a/extractor.test.ts +++ b/extractor.test.ts @@ -1,14 +1,13 @@ import { test, describe } from 'node:test'; import * as assert from 'node:assert/strict'; import { dirname, resolve } from 'node:path'; -import { readFileSync, writeFileSync, unlinkSync } from 'node:fs'; +import { writeFileSync, unlinkSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { extractResourceInputs } from './extractor'; -// A mock fixture harness that prevents reliance on a direct CLI binary -function runFixtureTest(fixture: any) { +function runFixtureTest(fixture: unknown) { const tmpFile = resolve( - typeof __dirname !== 'undefined' ? __dirname : dirname(fileURLToPath(import.meta.url)), + typeof __dirname !== 'undefined' ? __dirname : dirname(fileURLToPath(import.meta.url)), `test-fixture-${Date.now()}-${Math.random()}.json` ); writeFileSync(tmpFile, JSON.stringify(fixture)); @@ -19,6 +18,10 @@ function runFixtureTest(fixture: any) { } } +// --------------------------------------------------------------------------- +// AWS tests (unchanged from pre-multi-cloud) +// --------------------------------------------------------------------------- + describe('Terraform Plan Extractor', () => { test('clean plan with two supported resources', () => { const fixture = { @@ -52,10 +55,12 @@ describe('Terraform Plan Extractor', () => { assert.equal(result.resources[0].resourceId, 'aws_instance.web'); assert.equal(result.resources[0].instanceType, 'm5.large'); assert.equal(result.resources[0].region, 'us-east-1'); + assert.equal(result.resources[0].provider, 'aws'); assert.equal(result.resources[1].resourceId, 'aws_db_instance.db'); - assert.equal(result.resources[1].instanceType, 'm5.xlarge'); // Normalized + assert.equal(result.resources[1].instanceType, 'm5.xlarge'); assert.equal(result.resources[1].region, 'us-west-2'); + assert.equal(result.resources[1].provider, 'aws'); }); test('plan with known_after_apply instance type goes to skipped', () => { @@ -75,7 +80,6 @@ describe('Terraform Plan Extractor', () => { const result = runFixtureTest(fixture); assert.equal(result.resources.length, 0); assert.equal(result.skipped.length, 1); - assert.equal(result.skipped[0].resourceId, 'aws_instance.unknown'); assert.equal(result.skipped[0].reason, 'known_after_apply'); }); @@ -104,24 +108,19 @@ describe('Terraform Plan Extractor', () => { { address: 'aws_s3_bucket.static', type: 'aws_s3_bucket', - change: { - actions: ['create'], - after: { bucket: 'my-bucket' } - } + change: { actions: ['create'], after: { bucket: 'my-bucket' } } } ] }; const result = runFixtureTest(fixture); assert.equal(result.resources.length, 0); - assert.equal(result.skipped.length, 0); // Not skipped, completely ignored + assert.equal(result.skipped.length, 0); }); test('malformed input file returns structured error', () => { - // Passing a path to a non-existent file triggers ENOENT mapping const result = extractResourceInputs('does_not_exist.json'); assert.ok(result.error !== undefined); assert.ok(result.error.includes('Failed to read')); - assert.equal(result.resources.length, 0); }); test('delete action is correctly ignored (not extracted, not skipped)', () => { @@ -226,4 +225,245 @@ describe('Terraform Plan Extractor', () => { assert.ok(result.unsupportedTypes.includes('aws_launch_template')); assert.ok(result.unsupportedTypes.includes('aws_ecs_service')); }); + + // --------------------------------------------------------------------------- + // Azure tests + // --------------------------------------------------------------------------- + + test('Azure: extracts azurerm_linux_virtual_machine with size and location', () => { + const fixture = { + resource_changes: [ + { + address: 'azurerm_linux_virtual_machine.api', + type: 'azurerm_linux_virtual_machine', + change: { + actions: ['create'], + after: { size: 'Standard_D2s_v3', location: 'eastus' } + } + } + ] + }; + const result = runFixtureTest(fixture); + assert.equal(result.error, undefined); + assert.equal(result.resources.length, 1); + assert.equal(result.resources[0].resourceId, 'azurerm_linux_virtual_machine.api'); + assert.equal(result.resources[0].instanceType, 'Standard_D2s_v3'); + assert.equal(result.resources[0].region, 'eastus'); + assert.equal(result.resources[0].provider, 'azure'); + }); + + test('Azure: normalises "East US" location string to "eastus"', () => { + // Azure location values in Terraform plans can appear as "East US" (display format) + // or "eastus" (API format). The extractor normalises both to lowercase no-spaces. + const fixture = { + resource_changes: [ + { + address: 'azurerm_linux_virtual_machine.web', + type: 'azurerm_linux_virtual_machine', + change: { + actions: ['create'], + after: { size: 'Standard_D4s_v3', location: 'East US' } + } + } + ] + }; + const result = runFixtureTest(fixture); + assert.equal(result.resources.length, 1); + assert.equal(result.resources[0].region, 'eastus', 'Should normalise "East US" to "eastus"'); + }); + + test('Azure: extracts azurerm_windows_virtual_machine', () => { + const fixture = { + resource_changes: [ + { + address: 'azurerm_windows_virtual_machine.worker', + type: 'azurerm_windows_virtual_machine', + change: { + actions: ['create'], + after: { size: 'Standard_F4s_v2', location: 'uksouth' } + } + } + ] + }; + const result = runFixtureTest(fixture); + assert.equal(result.resources.length, 1); + assert.equal(result.resources[0].instanceType, 'Standard_F4s_v2'); + assert.equal(result.resources[0].region, 'uksouth'); + assert.equal(result.resources[0].provider, 'azure'); + }); + + test('Azure: skips VM with unknown size (known_after_apply)', () => { + const fixture = { + resource_changes: [ + { + address: 'azurerm_linux_virtual_machine.dynamic', + type: 'azurerm_linux_virtual_machine', + change: { + actions: ['create'], + after: { location: 'eastus' } + // size is absent — will be resolved at apply time + } + } + ] + }; + const result = runFixtureTest(fixture); + assert.equal(result.resources.length, 0); + assert.equal(result.skipped.length, 1); + assert.equal(result.skipped[0].reason, 'known_after_apply'); + }); + + test('Azure: unsupported compute-relevant types are tracked', () => { + const fixture = { + resource_changes: [ + { + address: 'azurerm_virtual_machine_scale_set.pool', + type: 'azurerm_virtual_machine_scale_set', + change: { actions: ['create'], after: {} } + } + ] + }; + const result = runFixtureTest(fixture); + assert.equal(result.resources.length, 0); + assert.ok(result.unsupportedTypes.includes('azurerm_virtual_machine_scale_set')); + }); + + // --------------------------------------------------------------------------- + // GCP tests + // --------------------------------------------------------------------------- + + test('GCP: extracts google_compute_instance with machine_type and zone', () => { + // GCP plans typically include zone not region; the extractor strips the AZ suffix + const fixture = { + resource_changes: [ + { + address: 'google_compute_instance.web', + type: 'google_compute_instance', + change: { + actions: ['create'], + after: { machine_type: 'n2-standard-2', zone: 'us-central1-a' } + } + } + ] + }; + const result = runFixtureTest(fixture); + assert.equal(result.error, undefined); + assert.equal(result.resources.length, 1); + assert.equal(result.resources[0].resourceId, 'google_compute_instance.web'); + assert.equal(result.resources[0].instanceType, 'n2-standard-2'); + assert.equal(result.resources[0].region, 'us-central1', 'Should strip zone suffix from us-central1-a'); + assert.equal(result.resources[0].provider, 'gcp'); + }); + + test('GCP: extracts region when provided directly (no zone)', () => { + const fixture = { + resource_changes: [ + { + address: 'google_compute_instance.api', + type: 'google_compute_instance', + change: { + actions: ['create'], + after: { machine_type: 't2a-standard-2', region: 'europe-west1' } + } + } + ] + }; + const result = runFixtureTest(fixture); + assert.equal(result.resources.length, 1); + assert.equal(result.resources[0].region, 'europe-west1'); + assert.equal(result.resources[0].provider, 'gcp'); + }); + + test('GCP: strips zone suffix correctly (europe-west1-b → europe-west1)', () => { + const fixture = { + resource_changes: [ + { + address: 'google_compute_instance.db', + type: 'google_compute_instance', + change: { + actions: ['create'], + after: { machine_type: 'e2-standard-2', zone: 'europe-west1-b' } + } + } + ] + }; + const result = runFixtureTest(fixture); + assert.equal(result.resources.length, 1); + assert.equal(result.resources[0].region, 'europe-west1'); + }); + + test('GCP: skips instance with unknown machine_type', () => { + const fixture = { + resource_changes: [ + { + address: 'google_compute_instance.dynamic', + type: 'google_compute_instance', + change: { + actions: ['create'], + after: { zone: 'us-central1-a' } + // machine_type absent + } + } + ] + }; + const result = runFixtureTest(fixture); + assert.equal(result.resources.length, 0); + assert.equal(result.skipped.length, 1); + assert.equal(result.skipped[0].reason, 'known_after_apply'); + }); + + test('GCP: unsupported compute-relevant types are tracked', () => { + const fixture = { + resource_changes: [ + { + address: 'google_compute_instance_template.pool', + type: 'google_compute_instance_template', + change: { actions: ['create'], after: {} } + } + ] + }; + const result = runFixtureTest(fixture); + assert.equal(result.resources.length, 0); + assert.ok(result.unsupportedTypes.includes('google_compute_instance_template')); + }); + + // --------------------------------------------------------------------------- + // Multi-cloud plan test + // --------------------------------------------------------------------------- + + test('Mixed AWS + Azure + GCP plan extracts all three providers correctly', () => { + const fixture = { + resource_changes: [ + { + address: 'aws_instance.web', + type: 'aws_instance', + change: { actions: ['create'], after: { instance_type: 'm5.large', region: 'us-east-1' } } + }, + { + address: 'azurerm_linux_virtual_machine.api', + type: 'azurerm_linux_virtual_machine', + change: { actions: ['create'], after: { size: 'Standard_D2s_v3', location: 'eastus' } } + }, + { + address: 'google_compute_instance.worker', + type: 'google_compute_instance', + change: { actions: ['create'], after: { machine_type: 'n2-standard-2', zone: 'us-central1-a' } } + } + ] + }; + const result = runFixtureTest(fixture); + assert.equal(result.error, undefined); + assert.equal(result.resources.length, 3); + + const aws = result.resources.find(r => r.provider === 'aws'); + const azure = result.resources.find(r => r.provider === 'azure'); + const gcp = result.resources.find(r => r.provider === 'gcp'); + + assert.ok(aws, 'Should have AWS resource'); + assert.ok(azure, 'Should have Azure resource'); + assert.ok(gcp, 'Should have GCP resource'); + + assert.equal(aws!.instanceType, 'm5.large'); + assert.equal(azure!.instanceType, 'Standard_D2s_v3'); + assert.equal(gcp!.instanceType, 'n2-standard-2'); + }); }); diff --git a/extractor.ts b/extractor.ts index fb41e3a..141cd90 100644 --- a/extractor.ts +++ b/extractor.ts @@ -1,6 +1,6 @@ import { readFileSync } from 'node:fs'; import { resolve, isAbsolute } from 'node:path'; -import type { ResourceInput, PlanAnalysisResult } from './types.js'; +import type { ResourceInput, PlanAnalysisResult, CloudProvider } from './types.js'; // --------------------------------------------------------------------------- // Internal types for Terraform plan shape @@ -21,8 +21,10 @@ interface TerraformPlan { resource_changes: unknown[]; configuration?: { provider_config?: Record; @@ -40,101 +42,138 @@ interface TerraformPlan { export interface ExtractorResult { resources: ResourceInput[]; skipped: PlanAnalysisResult['skipped']; - /** Resource types present in the plan but not supported for carbon analysis */ unsupportedTypes: string[]; error?: string; } -/** - * Checks if a specific attribute on a Terraform resource change is 'known after apply' - * or completely absent (which means unresolvable before apply). - */ -function isKnownAfterApply(change: TerraformResourceChange['change'], fieldPath: string): boolean { - if (!change) return true; - // If explicitly flagged as unknown by Terraform - if (change.after_unknown?.[fieldPath] === true) return true; - // If null or undefined in the known values mapping - if (change.after?.[fieldPath] === null || change.after?.[fieldPath] === undefined) return true; - return false; +// --------------------------------------------------------------------------- +// Supported resource types per provider +// --------------------------------------------------------------------------- + +const SUPPORTED_TYPES: Record = { + aws: ['aws_instance', 'aws_db_instance'], + azure: ['azurerm_linux_virtual_machine', 'azurerm_windows_virtual_machine', 'azurerm_virtual_machine'], + gcp: ['google_compute_instance'], +}; + +const COMPUTE_RELEVANT_TYPES = [ + 'aws_launch_template', 'aws_autoscaling_group', 'aws_ecs_service', + 'aws_eks_node_group', 'aws_lambda_function', + 'azurerm_virtual_machine_scale_set', 'azurerm_kubernetes_cluster', + 'azurerm_function_app', + 'google_compute_instance_template', 'google_container_cluster', + 'google_cloudfunctions_function', +]; + +function detectProvider(resourceType: string): CloudProvider | null { + if (resourceType.startsWith('aws_')) return 'aws'; + if (resourceType.startsWith('azurerm_')) return 'azure'; + if (resourceType.startsWith('google_')) return 'gcp'; + return null; } -/** - * Extracts the default AWS region from the plan's provider configuration block. - * Handles multi-provider configs by preferring the un-aliased "aws" provider. - * Returns null if not statically resolvable (e.g. region set via variable). - */ -function extractProviderRegion(plan: TerraformPlan): string | null { +function extractProviderRegions(plan: TerraformPlan): Record { + const result: Record = { aws: null, azure: null, gcp: null }; const providerConfig = plan.configuration?.provider_config; - if (!providerConfig) return null; + if (!providerConfig) return result; - // First pass: prefer the default (un-aliased) aws provider for (const [key, provider] of Object.entries(providerConfig)) { if (key === 'aws' || key.startsWith('aws.')) { const alias = provider.expressions?.alias?.constant_value; if (alias && key !== 'aws') continue; const region = provider.expressions?.region?.constant_value; - if (region && typeof region === 'string') return region; + if (region && !result.aws) result.aws = region; } - } - - // Second pass: accept any aws provider if no un-aliased one found - for (const [key, provider] of Object.entries(providerConfig)) { - if (key === 'aws' || key.startsWith('aws.')) { + if (key === 'azurerm' || key.startsWith('azurerm.')) { + const location = provider.expressions?.location?.constant_value; + if (location && !result.azure) result.azure = location; + } + if (key === 'google' || key.startsWith('google.')) { const region = provider.expressions?.region?.constant_value; - if (region && typeof region === 'string') return region; + if (region && !result.gcp) result.gcp = region; } } + return result; +} - return null; +function isKnownAfterApply(change: TerraformResourceChange['change'], fieldPath: string): boolean { + if (!change) return true; + if (change.after_unknown?.[fieldPath] === true) return true; + if (change.after?.[fieldPath] === null || change.after?.[fieldPath] === undefined) return true; + return false; } -/** - * Attempts to resolve the AWS region for a resource in the plan. - * Lookup chain: - * 1. `change.after.arn` (e.g. arn:aws:ec2:us-east-1:...) - * 2. `change.after.availability_zone` (strips trailing AZ letter) - * 3. `change.after.region` (explicit resource-level region attribute) - * 4. `change.before.region` (for update actions where region is unchanged) - * 5. `providerRegion` (from configuration.provider_config — handles real-world plans - * where region is set on the provider block, not on individual resources) - * - * If all fail, returns null → resource will be skipped as known_after_apply. - */ -function resolveRegion(change: TerraformResourceChange['change'], providerRegion: string | null): string | null { +function resolveAwsRegion(change: TerraformResourceChange['change'], providerRegion: string | null): string | null { if (change?.after?.arn && typeof change.after.arn === 'string') { - const parts = change.after.arn.split(':'); + const parts = (change.after.arn as string).split(':'); if (parts.length >= 4 && parts[3]) return parts[3]; } - if (change?.after?.availability_zone && typeof change.after.availability_zone === 'string') { - // Handles Local Zones (e.g. us-east-1-bos-1a) and standard AZs (e.g. us-east-1a) const azMatch = (change.after.availability_zone as string).match(/^([a-z]{2}-[a-z]+-\d+)/); if (azMatch) return azMatch[1]; } - - if (change?.after?.region && typeof change.after.region === 'string') { - return change.after.region as string; - } + if (change?.after?.region && typeof change.after.region === 'string') return change.after.region as string; + if (change?.before?.region && typeof change.before.region === 'string') return change.before.region as string; + if (providerRegion) return providerRegion; + return null; +} - // For update actions where region is stable and lives in before state - if (change?.before?.region && typeof change.before.region === 'string') { - return change.before.region as string; - } +function resolveAzureRegion(change: TerraformResourceChange['change'], providerRegion: string | null): string | null { + const raw = change?.after?.location ?? change?.before?.location ?? providerRegion; + if (!raw || typeof raw !== 'string') return null; + return raw.toLowerCase().replace(/\s+/g, ''); +} - // Real-world AWS plans: region lives on the provider block, not the resource. - // This is the most common case in practice. +function resolveGcpRegion(change: TerraformResourceChange['change'], providerRegion: string | null): string | null { + if (change?.after?.region && typeof change.after.region === 'string') return change.after.region as string; + if (change?.after?.zone && typeof change.after.zone === 'string') { + const zoneMatch = (change.after.zone as string).match(/^([a-z]+-[a-z]+\d+)/); + if (zoneMatch) return zoneMatch[1]; + } + if (change?.before?.region && typeof change.before.region === 'string') return change.before.region as string; if (providerRegion) return providerRegion; - return null; } +function extractAwsInstanceType(res: TerraformResourceChange, plannedValuesMap: Map>): { instanceType: string | null; skipReason?: string } { + const isDb = res.type === 'aws_db_instance'; + const typeField = isDb ? 'instance_class' : 'instance_type'; + + if (isKnownAfterApply(res.change, typeField)) { + const plannedType = plannedValuesMap.get(res.address)?.[typeField]; + if (typeof plannedType !== 'string') return { instanceType: null, skipReason: 'known_after_apply' }; + if (!res.change!.after) res.change!.after = {}; + res.change!.after[typeField] = plannedType; + } + + let instanceType = res.change?.after?.[typeField] as string; + if (typeof instanceType !== 'string') return { instanceType: null, skipReason: 'known_after_apply' }; + + if (isDb && instanceType.startsWith('db.')) { + instanceType = instanceType.replace(/^db\./, ''); + if (!instanceType.includes('.')) return { instanceType: null, skipReason: 'unsupported_instance' }; + } + + return { instanceType }; +} + +function extractAzureInstanceType(res: TerraformResourceChange): { instanceType: string | null; skipReason?: string } { + const size = res.change?.after?.size ?? res.change?.before?.size; + if (!size || typeof size !== 'string') return { instanceType: null, skipReason: 'known_after_apply' }; + return { instanceType: size }; +} + +function extractGcpInstanceType(res: TerraformResourceChange): { instanceType: string | null; skipReason?: string } { + const machineType = res.change?.after?.machine_type ?? res.change?.before?.machine_type; + if (!machineType || typeof machineType !== 'string') return { instanceType: null, skipReason: 'known_after_apply' }; + return { instanceType: machineType }; +} + export function extractResourceInputs(planFilePath: string): ExtractorResult { const result: ExtractorResult = { resources: [], skipped: [], unsupportedTypes: [] }; - // Resolve to absolute path so relative traversal sequences (e.g. ../../) are normalised const resolvedPath = isAbsolute(planFilePath) - ? planFilePath - : resolve(process.cwd(), planFilePath); + ? planFilePath : resolve(process.cwd(), planFilePath); let raw: string; try { @@ -158,89 +197,66 @@ export function extractResourceInputs(planFilePath: string): ExtractorResult { } const typedPlan = plan as TerraformPlan; + const providerRegions = extractProviderRegions(typedPlan); - // Extract provider-level region once — used as final fallback in resolveRegion() - const providerRegion = extractProviderRegion(typedPlan); - - // Build a lookup map from planned_values for instance type resolution. - // planned_values resolves attributes that are statically known but may not yet - // be reflected in change.after (e.g. when the provider populates defaults at plan time). const plannedValuesMap = new Map>(); for (const r of typedPlan.planned_values?.root_module?.resources ?? []) { if (r.address && r.values) plannedValuesMap.set(r.address, r.values); } + const allSupportedTypes = Object.values(SUPPORTED_TYPES).flat(); + for (const rawRes of typedPlan.resource_changes) { const res = rawRes as TerraformResourceChange; const actions = res.change?.actions; - - // Only process resources where change.actions includes "create" or "update" - // Deletes represent resources scaling down, so their baseline from today onwards will be zero, - // hence we don't calculate future impact or upgrade recommendations on teardowns. + if (!Array.isArray(actions) || (!actions.includes('create') && !actions.includes('update'))) { - continue; + continue; } - // Track compute-relevant resource types that we can't yet analyse. - // This lets formatters surface a coverage disclaimer. - const SUPPORTED_TYPES = ['aws_instance', 'aws_db_instance']; - const COMPUTE_RELEVANT_TYPES = ['aws_launch_template', 'aws_autoscaling_group', 'aws_ecs_service', 'aws_eks_node_group', 'aws_lambda_function']; + const provider = detectProvider(res.type); - if (!SUPPORTED_TYPES.includes(res.type)) { + if (!allSupportedTypes.includes(res.type)) { if (COMPUTE_RELEVANT_TYPES.includes(res.type) && !result.unsupportedTypes.includes(res.type)) { result.unsupportedTypes.push(res.type); } - continue; + continue; } - const isDb = res.type === 'aws_db_instance'; - const typeField = isDb ? 'instance_class' : 'instance_type'; - - // Verify type isn't unknown_after_apply. - // If change.after doesn't have it, check planned_values before giving up — - // some providers populate planned_values even when change.after is incomplete. - if (isKnownAfterApply(res.change, typeField)) { - const plannedType = plannedValuesMap.get(res.address)?.[typeField]; - if (typeof plannedType !== 'string') { - result.skipped.push({ resourceId: res.address, reason: 'known_after_apply' }); - continue; - } - // Inject into change.after so downstream logic stays consistent - if (!res.change!.after) res.change!.after = {}; - res.change!.after[typeField] = plannedType; - } + if (!provider) continue; - let instanceType: string = res.change!.after![typeField] as string; - if (typeof res.change!.after![typeField] !== 'string') { - result.skipped.push({ resourceId: res.address, reason: 'known_after_apply' }); - continue; + let instanceType: string | null = null; + let skipReason: string | undefined; + let region: string | null = null; + + if (provider === 'aws') { + const extracted = extractAwsInstanceType(res, plannedValuesMap); + instanceType = extracted.instanceType; + skipReason = extracted.skipReason; + if (instanceType) region = resolveAwsRegion(res.change, providerRegions.aws); + } else if (provider === 'azure') { + const extracted = extractAzureInstanceType(res); + instanceType = extracted.instanceType; + skipReason = extracted.skipReason; + if (instanceType) region = resolveAzureRegion(res.change, providerRegions.azure); + } else if (provider === 'gcp') { + const extracted = extractGcpInstanceType(res); + instanceType = extracted.instanceType; + skipReason = extracted.skipReason; + if (instanceType) region = resolveGcpRegion(res.change, providerRegions.gcp); } - // Normalisation step: "db.m5.large" -> "m5.large". - // It's vastly superior to isolate this DB normalisation logic entirely out of the Engine, - // so the mathematical formulas handle consistently flat datasets mapped identically to factors.json. - if (isDb && instanceType.startsWith('db.')) { - instanceType = instanceType.replace(/^db\./, ''); - // Guard: db.serverless (Aurora Serverless) produces invalid types after stripping - if (!instanceType.includes('.')) { - result.skipped.push({ resourceId: res.address, reason: 'unsupported_instance' }); - continue; - } + if (!instanceType || skipReason) { + result.skipped.push({ resourceId: res.address, reason: skipReason ?? 'known_after_apply' }); + continue; } - const region = resolveRegion(res.change, providerRegion); if (!region) { - // If we completely exhausted our lookup heuristics and failed to find a region, - // it means we either need it applied dynamically, or the TF configuration leverages entirely external provider abstractions result.skipped.push({ resourceId: res.address, reason: 'known_after_apply' }); continue; } - result.resources.push({ - resourceId: res.address, // Correctly applies nested addresses as the ID (e.g. module.compute.aws_instance.api) - instanceType, - region - }); + result.resources.push({ resourceId: res.address, instanceType, region, provider }); } return result; diff --git a/factors.json b/factors.json index ec5b8d4..27d87a7 100644 --- a/factors.json +++ b/factors.json @@ -1,11 +1,13 @@ { "metadata": { - "ledger_version": "1.3.0", - "updated_at": "2026-03-27T00:00:00Z", + "ledger_version": "2.0.0", + "updated_at": "2026-03-28T00:00:00Z", "sources": { "grid": "electricity-maps-2024-avg", "hardware": "cloud-carbon-footprint-v3", - "pricing": "aws-public-pricing-api-2026-q1", + "pricing_aws": "aws-public-pricing-api-2026-q1", + "pricing_azure": "azure-public-pricing-api-2026-q1", + "pricing_gcp": "gcp-public-pricing-api-2026-q1", "embodied": "cloud-carbon-footprint-v3-dell-r740-baseline", "water": "aws-sustainability-report-2023-wue" }, @@ -17,983 +19,1941 @@ } } }, - "regions": { - "us-east-1": { - "location": "US East (N. Virginia)", - "grid_intensity_gco2e_per_kwh": 384.5, - "pue": 1.13, - "water_intensity_litres_per_kwh": 0.46 - }, - "us-east-2": { - "location": "US East (Ohio)", - "grid_intensity_gco2e_per_kwh": 410.0, - "pue": 1.13, - "water_intensity_litres_per_kwh": 0.52 - }, - "us-west-1": { - "location": "US West (N. California)", - "grid_intensity_gco2e_per_kwh": 220.0, - "pue": 1.13, - "water_intensity_litres_per_kwh": 0.38 - }, - "us-west-2": { - "location": "US West (Oregon)", - "grid_intensity_gco2e_per_kwh": 240.1, - "pue": 1.13, - "water_intensity_litres_per_kwh": 0.18 - }, - "eu-west-1": { - "location": "Europe (Ireland)", - "grid_intensity_gco2e_per_kwh": 334.0, - "pue": 1.13, - "water_intensity_litres_per_kwh": 0.22 - }, - "eu-west-2": { - "location": "Europe (London)", - "grid_intensity_gco2e_per_kwh": 268.0, - "pue": 1.13, - "water_intensity_litres_per_kwh": 0.25 - }, - "eu-central-1": { - "location": "Europe (Frankfurt)", - "grid_intensity_gco2e_per_kwh": 420.5, - "pue": 1.13, - "water_intensity_litres_per_kwh": 0.28 - }, - "eu-north-1": { - "location": "Europe (Stockholm)", - "grid_intensity_gco2e_per_kwh": 8.8, - "pue": 1.13, - "water_intensity_litres_per_kwh": 0.1 - }, - "ap-southeast-1": { - "location": "Asia Pacific (Singapore)", - "grid_intensity_gco2e_per_kwh": 408.0, - "pue": 1.13, - "water_intensity_litres_per_kwh": 0.58 - }, - "ap-southeast-2": { - "location": "Asia Pacific (Sydney)", - "grid_intensity_gco2e_per_kwh": 650.0, - "pue": 1.13, - "water_intensity_litres_per_kwh": 0.45 - }, - "ap-northeast-1": { - "location": "Asia Pacific (Tokyo)", - "grid_intensity_gco2e_per_kwh": 506.0, - "pue": 1.13, - "water_intensity_litres_per_kwh": 0.5 - }, - "ap-south-1": { - "location": "Asia Pacific (Mumbai)", - "grid_intensity_gco2e_per_kwh": 723.0, - "pue": 1.13, - "water_intensity_litres_per_kwh": 0.72 + "aws": { + "regions": { + "us-east-1": { + "location": "US East (N. Virginia)", + "grid_intensity_gco2e_per_kwh": 384.5, + "pue": 1.13, + "water_intensity_litres_per_kwh": 0.46 + }, + "us-east-2": { + "location": "US East (Ohio)", + "grid_intensity_gco2e_per_kwh": 410.0, + "pue": 1.13, + "water_intensity_litres_per_kwh": 0.52 + }, + "us-west-1": { + "location": "US West (N. California)", + "grid_intensity_gco2e_per_kwh": 220.0, + "pue": 1.13, + "water_intensity_litres_per_kwh": 0.38 + }, + "us-west-2": { + "location": "US West (Oregon)", + "grid_intensity_gco2e_per_kwh": 240.1, + "pue": 1.13, + "water_intensity_litres_per_kwh": 0.18 + }, + "eu-west-1": { + "location": "Europe (Ireland)", + "grid_intensity_gco2e_per_kwh": 334.0, + "pue": 1.13, + "water_intensity_litres_per_kwh": 0.22 + }, + "eu-west-2": { + "location": "Europe (London)", + "grid_intensity_gco2e_per_kwh": 268.0, + "pue": 1.13, + "water_intensity_litres_per_kwh": 0.25 + }, + "eu-central-1": { + "location": "Europe (Frankfurt)", + "grid_intensity_gco2e_per_kwh": 420.5, + "pue": 1.13, + "water_intensity_litres_per_kwh": 0.28 + }, + "eu-north-1": { + "location": "Europe (Stockholm)", + "grid_intensity_gco2e_per_kwh": 8.8, + "pue": 1.13, + "water_intensity_litres_per_kwh": 0.1 + }, + "ap-southeast-1": { + "location": "Asia Pacific (Singapore)", + "grid_intensity_gco2e_per_kwh": 408.0, + "pue": 1.13, + "water_intensity_litres_per_kwh": 0.58 + }, + "ap-southeast-2": { + "location": "Asia Pacific (Sydney)", + "grid_intensity_gco2e_per_kwh": 650.0, + "pue": 1.13, + "water_intensity_litres_per_kwh": 0.45 + }, + "ap-northeast-1": { + "location": "Asia Pacific (Tokyo)", + "grid_intensity_gco2e_per_kwh": 506.0, + "pue": 1.13, + "water_intensity_litres_per_kwh": 0.5 + }, + "ap-south-1": { + "location": "Asia Pacific (Mumbai)", + "grid_intensity_gco2e_per_kwh": 723.0, + "pue": 1.13, + "water_intensity_litres_per_kwh": 0.72 + }, + "ca-central-1": { + "location": "Canada (Central)", + "grid_intensity_gco2e_per_kwh": 130.0, + "pue": 1.13, + "water_intensity_litres_per_kwh": 0.2 + }, + "sa-east-1": { + "location": "South America (S\u00e3o Paulo)", + "grid_intensity_gco2e_per_kwh": 74.0, + "pue": 1.13, + "water_intensity_litres_per_kwh": 0.35 + } }, - "ca-central-1": { - "location": "Canada (Central)", - "grid_intensity_gco2e_per_kwh": 130.0, - "pue": 1.13, - "water_intensity_litres_per_kwh": 0.2 + "instances": { + "t3.micro": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 1, + "power_watts": { + "idle": 1.4, + "max": 5.0 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "t3.small": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 2, + "power_watts": { + "idle": 2.0, + "max": 7.0 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "t3.medium": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 4, + "power_watts": { + "idle": 3.4, + "max": 10.2 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "t3.large": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 8, + "power_watts": { + "idle": 6.8, + "max": 20.4 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "t3.xlarge": { + "architecture": "x86_64", + "vcpus": 4, + "memory_gb": 16, + "power_watts": { + "idle": 13.6, + "max": 40.8 + }, + "embodied_co2e_grams_per_month": 2083.3 + }, + "t3a.medium": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 4, + "power_watts": { + "idle": 3.2, + "max": 9.8 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "t3a.large": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 8, + "power_watts": { + "idle": 6.4, + "max": 19.6 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "m5.large": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 8, + "power_watts": { + "idle": 6.8, + "max": 20.4 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "m5.xlarge": { + "architecture": "x86_64", + "vcpus": 4, + "memory_gb": 16, + "power_watts": { + "idle": 13.6, + "max": 40.8 + }, + "embodied_co2e_grams_per_month": 2083.3 + }, + "m5.2xlarge": { + "architecture": "x86_64", + "vcpus": 8, + "memory_gb": 32, + "power_watts": { + "idle": 27.2, + "max": 81.6 + }, + "embodied_co2e_grams_per_month": 4166.7 + }, + "m5a.large": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 8, + "power_watts": { + "idle": 6.5, + "max": 19.5 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "m5a.xlarge": { + "architecture": "x86_64", + "vcpus": 4, + "memory_gb": 16, + "power_watts": { + "idle": 13.0, + "max": 39.0 + }, + "embodied_co2e_grams_per_month": 2083.3 + }, + "c5.large": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 4, + "power_watts": { + "idle": 6.5, + "max": 22.0 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "c5.xlarge": { + "architecture": "x86_64", + "vcpus": 4, + "memory_gb": 8, + "power_watts": { + "idle": 13.0, + "max": 44.0 + }, + "embodied_co2e_grams_per_month": 2083.3 + }, + "c5.2xlarge": { + "architecture": "x86_64", + "vcpus": 8, + "memory_gb": 16, + "power_watts": { + "idle": 26.0, + "max": 88.0 + }, + "embodied_co2e_grams_per_month": 4166.7 + }, + "c5a.large": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 4, + "power_watts": { + "idle": 6.2, + "max": 21.0 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "c5a.xlarge": { + "architecture": "x86_64", + "vcpus": 4, + "memory_gb": 8, + "power_watts": { + "idle": 12.4, + "max": 42.0 + }, + "embodied_co2e_grams_per_month": 2083.3 + }, + "r5.large": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 16, + "power_watts": { + "idle": 8.0, + "max": 24.0 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "r5.xlarge": { + "architecture": "x86_64", + "vcpus": 4, + "memory_gb": 32, + "power_watts": { + "idle": 16.0, + "max": 48.0 + }, + "embodied_co2e_grams_per_month": 2083.3 + }, + "t4g.micro": { + "architecture": "arm64", + "vcpus": 2, + "memory_gb": 1, + "power_watts": { + "idle": 0.9, + "max": 3.2 + }, + "embodied_co2e_grams_per_month": 833.3 + }, + "t4g.small": { + "architecture": "arm64", + "vcpus": 2, + "memory_gb": 2, + "power_watts": { + "idle": 1.4, + "max": 4.5 + }, + "embodied_co2e_grams_per_month": 833.3 + }, + "t4g.medium": { + "architecture": "arm64", + "vcpus": 2, + "memory_gb": 4, + "power_watts": { + "idle": 2.2, + "max": 6.8 + }, + "embodied_co2e_grams_per_month": 833.3 + }, + "t4g.large": { + "architecture": "arm64", + "vcpus": 2, + "memory_gb": 8, + "power_watts": { + "idle": 4.4, + "max": 13.6 + }, + "embodied_co2e_grams_per_month": 833.3 + }, + "t4g.xlarge": { + "architecture": "arm64", + "vcpus": 4, + "memory_gb": 16, + "power_watts": { + "idle": 8.8, + "max": 27.2 + }, + "embodied_co2e_grams_per_month": 1666.7 + }, + "m6g.medium": { + "architecture": "arm64", + "vcpus": 1, + "memory_gb": 4, + "power_watts": { + "idle": 2.1, + "max": 6.6 + }, + "embodied_co2e_grams_per_month": 416.7 + }, + "m6g.large": { + "architecture": "arm64", + "vcpus": 2, + "memory_gb": 8, + "power_watts": { + "idle": 4.1, + "max": 13.2 + }, + "embodied_co2e_grams_per_month": 833.3 + }, + "m6g.xlarge": { + "architecture": "arm64", + "vcpus": 4, + "memory_gb": 16, + "power_watts": { + "idle": 8.2, + "max": 26.4 + }, + "embodied_co2e_grams_per_month": 1666.7 + }, + "m6g.2xlarge": { + "architecture": "arm64", + "vcpus": 8, + "memory_gb": 32, + "power_watts": { + "idle": 16.4, + "max": 52.8 + }, + "embodied_co2e_grams_per_month": 3333.3 + }, + "m7g.medium": { + "architecture": "arm64", + "vcpus": 1, + "memory_gb": 4, + "power_watts": { + "idle": 1.8, + "max": 5.8 + }, + "embodied_co2e_grams_per_month": 416.7 + }, + "m7g.large": { + "architecture": "arm64", + "vcpus": 2, + "memory_gb": 8, + "power_watts": { + "idle": 3.6, + "max": 11.6 + }, + "embodied_co2e_grams_per_month": 833.3 + }, + "m7g.xlarge": { + "architecture": "arm64", + "vcpus": 4, + "memory_gb": 16, + "power_watts": { + "idle": 7.2, + "max": 23.2 + }, + "embodied_co2e_grams_per_month": 1666.7 + }, + "m7g.2xlarge": { + "architecture": "arm64", + "vcpus": 8, + "memory_gb": 32, + "power_watts": { + "idle": 14.4, + "max": 46.4 + }, + "embodied_co2e_grams_per_month": 3333.3 + }, + "c6g.medium": { + "architecture": "arm64", + "vcpus": 1, + "memory_gb": 2, + "power_watts": { + "idle": 2.0, + "max": 7.3 + }, + "embodied_co2e_grams_per_month": 416.7 + }, + "c6g.large": { + "architecture": "arm64", + "vcpus": 2, + "memory_gb": 4, + "power_watts": { + "idle": 3.9, + "max": 14.5 + }, + "embodied_co2e_grams_per_month": 833.3 + }, + "c6g.xlarge": { + "architecture": "arm64", + "vcpus": 4, + "memory_gb": 8, + "power_watts": { + "idle": 7.8, + "max": 29.0 + }, + "embodied_co2e_grams_per_month": 1666.7 + }, + "c6g.2xlarge": { + "architecture": "arm64", + "vcpus": 8, + "memory_gb": 16, + "power_watts": { + "idle": 15.6, + "max": 58.0 + }, + "embodied_co2e_grams_per_month": 3333.3 + }, + "c7g.large": { + "architecture": "arm64", + "vcpus": 2, + "memory_gb": 4, + "power_watts": { + "idle": 3.5, + "max": 13.0 + }, + "embodied_co2e_grams_per_month": 833.3 + }, + "c7g.xlarge": { + "architecture": "arm64", + "vcpus": 4, + "memory_gb": 8, + "power_watts": { + "idle": 7.0, + "max": 26.0 + }, + "embodied_co2e_grams_per_month": 1666.7 + }, + "r6g.large": { + "architecture": "arm64", + "vcpus": 2, + "memory_gb": 16, + "power_watts": { + "idle": 4.8, + "max": 15.0 + }, + "embodied_co2e_grams_per_month": 833.3 + }, + "r6g.xlarge": { + "architecture": "arm64", + "vcpus": 4, + "memory_gb": 32, + "power_watts": { + "idle": 9.6, + "max": 30.0 + }, + "embodied_co2e_grams_per_month": 1666.7 + } }, - "sa-east-1": { - "location": "South America (S\u00e3o Paulo)", - "grid_intensity_gco2e_per_kwh": 74.0, - "pue": 1.13, - "water_intensity_litres_per_kwh": 0.35 + "pricing_usd_per_hour": { + "us-east-1": { + "t3.micro": 0.0104, + "t3.small": 0.0208, + "t3.medium": 0.0416, + "t3.large": 0.0832, + "t3.xlarge": 0.1664, + "t3a.medium": 0.0376, + "t3a.large": 0.0752, + "m5.large": 0.096, + "m5.xlarge": 0.192, + "m5.2xlarge": 0.384, + "m5a.large": 0.086, + "m5a.xlarge": 0.172, + "c5.large": 0.085, + "c5.xlarge": 0.17, + "c5.2xlarge": 0.34, + "c5a.large": 0.077, + "c5a.xlarge": 0.154, + "r5.large": 0.126, + "r5.xlarge": 0.252, + "t4g.micro": 0.0084, + "t4g.small": 0.0168, + "t4g.medium": 0.0336, + "t4g.large": 0.0672, + "t4g.xlarge": 0.1344, + "m6g.medium": 0.0385, + "m6g.large": 0.077, + "m6g.xlarge": 0.154, + "m6g.2xlarge": 0.308, + "m7g.medium": 0.0408, + "m7g.large": 0.0816, + "m7g.xlarge": 0.1632, + "m7g.2xlarge": 0.3264, + "c6g.medium": 0.034, + "c6g.large": 0.068, + "c6g.xlarge": 0.136, + "c6g.2xlarge": 0.272, + "c7g.large": 0.0725, + "c7g.xlarge": 0.145, + "r6g.large": 0.1008, + "r6g.xlarge": 0.2016 + }, + "us-east-2": { + "t3.micro": 0.0104, + "t3.small": 0.0208, + "t3.medium": 0.0416, + "t3.large": 0.0832, + "t3.xlarge": 0.1664, + "t3a.medium": 0.0376, + "t3a.large": 0.0752, + "m5.large": 0.096, + "m5.xlarge": 0.192, + "m5.2xlarge": 0.384, + "m5a.large": 0.086, + "m5a.xlarge": 0.172, + "c5.large": 0.085, + "c5.xlarge": 0.17, + "c5.2xlarge": 0.34, + "c5a.large": 0.077, + "c5a.xlarge": 0.154, + "r5.large": 0.126, + "r5.xlarge": 0.252, + "t4g.micro": 0.0084, + "t4g.small": 0.0168, + "t4g.medium": 0.0336, + "t4g.large": 0.0672, + "t4g.xlarge": 0.1344, + "m6g.medium": 0.0385, + "m6g.large": 0.077, + "m6g.xlarge": 0.154, + "m6g.2xlarge": 0.308, + "m7g.medium": 0.0408, + "m7g.large": 0.0816, + "m7g.xlarge": 0.1632, + "m7g.2xlarge": 0.3264, + "c6g.medium": 0.034, + "c6g.large": 0.068, + "c6g.xlarge": 0.136, + "c6g.2xlarge": 0.272, + "c7g.large": 0.0725, + "c7g.xlarge": 0.145, + "r6g.large": 0.1008, + "r6g.xlarge": 0.2016 + }, + "us-west-1": { + "t3.micro": 0.0116, + "t3.small": 0.0232, + "t3.medium": 0.0464, + "t3.large": 0.0928, + "t3.xlarge": 0.1856, + "m5.large": 0.107, + "m5.xlarge": 0.214, + "m5.2xlarge": 0.428, + "c5.large": 0.096, + "c5.xlarge": 0.192, + "c5.2xlarge": 0.384, + "t4g.medium": 0.0376, + "t4g.large": 0.0752, + "t4g.xlarge": 0.1504, + "m6g.large": 0.086, + "m6g.xlarge": 0.172, + "m6g.2xlarge": 0.344, + "m7g.large": 0.0912, + "m7g.xlarge": 0.1824, + "c6g.large": 0.076, + "c6g.xlarge": 0.152, + "r6g.large": 0.1127, + "r6g.xlarge": 0.2254 + }, + "us-west-2": { + "t3.micro": 0.0104, + "t3.small": 0.0208, + "t3.medium": 0.0416, + "t3.large": 0.0832, + "t3.xlarge": 0.1664, + "t3a.medium": 0.0376, + "t3a.large": 0.0752, + "m5.large": 0.096, + "m5.xlarge": 0.192, + "m5.2xlarge": 0.384, + "m5a.large": 0.086, + "m5a.xlarge": 0.172, + "c5.large": 0.085, + "c5.xlarge": 0.17, + "c5.2xlarge": 0.34, + "c5a.large": 0.077, + "c5a.xlarge": 0.154, + "r5.large": 0.126, + "r5.xlarge": 0.252, + "t4g.micro": 0.0084, + "t4g.small": 0.0168, + "t4g.medium": 0.0336, + "t4g.large": 0.0672, + "t4g.xlarge": 0.1344, + "m6g.medium": 0.0385, + "m6g.large": 0.077, + "m6g.xlarge": 0.154, + "m6g.2xlarge": 0.308, + "m7g.medium": 0.0408, + "m7g.large": 0.0816, + "m7g.xlarge": 0.1632, + "m7g.2xlarge": 0.3264, + "c6g.medium": 0.034, + "c6g.large": 0.068, + "c6g.xlarge": 0.136, + "c6g.2xlarge": 0.272, + "c7g.large": 0.0725, + "c7g.xlarge": 0.145, + "r6g.large": 0.1008, + "r6g.xlarge": 0.2016 + }, + "eu-west-1": { + "t3.micro": 0.0116, + "t3.small": 0.0232, + "t3.medium": 0.0456, + "t3.large": 0.0912, + "t3.xlarge": 0.1824, + "t3a.medium": 0.0416, + "t3a.large": 0.0832, + "m5.large": 0.107, + "m5.xlarge": 0.214, + "m5.2xlarge": 0.428, + "m5a.large": 0.096, + "m5a.xlarge": 0.192, + "c5.large": 0.096, + "c5.xlarge": 0.192, + "c5.2xlarge": 0.384, + "c5a.large": 0.087, + "c5a.xlarge": 0.174, + "r5.large": 0.141, + "r5.xlarge": 0.282, + "t4g.micro": 0.0094, + "t4g.small": 0.0188, + "t4g.medium": 0.0376, + "t4g.large": 0.0752, + "t4g.xlarge": 0.1504, + "m6g.medium": 0.043, + "m6g.large": 0.086, + "m6g.xlarge": 0.172, + "m6g.2xlarge": 0.344, + "m7g.medium": 0.0456, + "m7g.large": 0.0912, + "m7g.xlarge": 0.1824, + "m7g.2xlarge": 0.3648, + "c6g.medium": 0.038, + "c6g.large": 0.076, + "c6g.xlarge": 0.152, + "c6g.2xlarge": 0.304, + "c7g.large": 0.0812, + "c7g.xlarge": 0.1624, + "r6g.large": 0.1127, + "r6g.xlarge": 0.2254 + }, + "eu-west-2": { + "t3.micro": 0.0126, + "t3.small": 0.0252, + "t3.medium": 0.0504, + "t3.large": 0.1008, + "t3.xlarge": 0.2016, + "m5.large": 0.1178, + "m5.xlarge": 0.2356, + "m5.2xlarge": 0.4712, + "c5.large": 0.1054, + "c5.xlarge": 0.2108, + "c5.2xlarge": 0.4216, + "t4g.medium": 0.0414, + "t4g.large": 0.0828, + "t4g.xlarge": 0.1656, + "m6g.large": 0.0945, + "m6g.xlarge": 0.189, + "m6g.2xlarge": 0.378, + "m7g.large": 0.1001, + "m7g.xlarge": 0.2002, + "c6g.large": 0.0836, + "c6g.xlarge": 0.1672, + "r6g.large": 0.124, + "r6g.xlarge": 0.248 + }, + "eu-central-1": { + "t3.micro": 0.012, + "t3.small": 0.024, + "t3.medium": 0.0496, + "t3.large": 0.0992, + "t3.xlarge": 0.1984, + "t3a.medium": 0.0448, + "t3a.large": 0.0896, + "m5.large": 0.115, + "m5.xlarge": 0.23, + "m5.2xlarge": 0.46, + "m5a.large": 0.103, + "m5a.xlarge": 0.206, + "c5.large": 0.102, + "c5.xlarge": 0.204, + "c5.2xlarge": 0.408, + "r5.large": 0.151, + "r5.xlarge": 0.302, + "t4g.micro": 0.01, + "t4g.small": 0.02, + "t4g.medium": 0.0416, + "t4g.large": 0.0832, + "t4g.xlarge": 0.1664, + "m6g.medium": 0.046, + "m6g.large": 0.092, + "m6g.xlarge": 0.184, + "m6g.2xlarge": 0.368, + "m7g.medium": 0.0488, + "m7g.large": 0.0976, + "m7g.xlarge": 0.1952, + "m7g.2xlarge": 0.3904, + "c6g.medium": 0.041, + "c6g.large": 0.082, + "c6g.xlarge": 0.164, + "c6g.2xlarge": 0.328, + "c7g.large": 0.0875, + "c7g.xlarge": 0.175, + "r6g.large": 0.121, + "r6g.xlarge": 0.242 + }, + "eu-north-1": { + "t3.micro": 0.0108, + "t3.small": 0.0216, + "t3.medium": 0.0432, + "t3.large": 0.0864, + "t3.xlarge": 0.1728, + "m5.large": 0.1, + "m5.xlarge": 0.2, + "m5.2xlarge": 0.4, + "c5.large": 0.089, + "c5.xlarge": 0.178, + "c5.2xlarge": 0.356, + "t4g.medium": 0.0362, + "t4g.large": 0.0724, + "t4g.xlarge": 0.1448, + "m6g.large": 0.08, + "m6g.xlarge": 0.16, + "m6g.2xlarge": 0.32, + "m7g.large": 0.0848, + "m7g.xlarge": 0.1696, + "c6g.large": 0.0712, + "c6g.xlarge": 0.1424, + "r6g.large": 0.1054, + "r6g.xlarge": 0.2108 + }, + "ap-southeast-1": { + "t3.micro": 0.0132, + "t3.small": 0.0264, + "t3.medium": 0.0528, + "t3.large": 0.1056, + "t3.xlarge": 0.2112, + "m5.large": 0.124, + "m5.xlarge": 0.248, + "m5.2xlarge": 0.496, + "c5.large": 0.107, + "c5.xlarge": 0.214, + "c5.2xlarge": 0.428, + "t4g.medium": 0.0438, + "t4g.large": 0.0876, + "t4g.xlarge": 0.1752, + "m6g.large": 0.0992, + "m6g.xlarge": 0.1984, + "m6g.2xlarge": 0.3968, + "m7g.large": 0.1051, + "m7g.xlarge": 0.2102, + "c6g.large": 0.086, + "c6g.xlarge": 0.172, + "r6g.large": 0.1307, + "r6g.xlarge": 0.2614 + }, + "ap-southeast-2": { + "t3.micro": 0.0136, + "t3.small": 0.0272, + "t3.medium": 0.0544, + "t3.large": 0.1088, + "t3.xlarge": 0.2176, + "t3a.medium": 0.0492, + "t3a.large": 0.0984, + "m5.large": 0.134, + "m5.xlarge": 0.268, + "m5.2xlarge": 0.536, + "m5a.large": 0.12, + "m5a.xlarge": 0.24, + "c5.large": 0.118, + "c5.xlarge": 0.236, + "c5.2xlarge": 0.472, + "r5.large": 0.176, + "r5.xlarge": 0.352, + "t4g.micro": 0.0113, + "t4g.small": 0.0226, + "t4g.medium": 0.0452, + "t4g.large": 0.0904, + "t4g.xlarge": 0.1808, + "m6g.medium": 0.0535, + "m6g.large": 0.107, + "m6g.xlarge": 0.214, + "m6g.2xlarge": 0.428, + "m7g.medium": 0.0567, + "m7g.large": 0.1134, + "m7g.xlarge": 0.2268, + "m7g.2xlarge": 0.4536, + "c6g.medium": 0.047, + "c6g.large": 0.094, + "c6g.xlarge": 0.188, + "c6g.2xlarge": 0.376, + "c7g.large": 0.1002, + "c7g.xlarge": 0.2004, + "r6g.large": 0.1411, + "r6g.xlarge": 0.2822 + }, + "ap-northeast-1": { + "t3.micro": 0.014, + "t3.small": 0.028, + "t3.medium": 0.056, + "t3.large": 0.112, + "t3.xlarge": 0.224, + "t3a.medium": 0.0504, + "t3a.large": 0.1008, + "m5.large": 0.128, + "m5.xlarge": 0.256, + "m5.2xlarge": 0.512, + "m5a.large": 0.115, + "m5a.xlarge": 0.23, + "c5.large": 0.114, + "c5.xlarge": 0.228, + "c5.2xlarge": 0.456, + "r5.large": 0.169, + "r5.xlarge": 0.338, + "t4g.micro": 0.0116, + "t4g.small": 0.0232, + "t4g.medium": 0.0464, + "t4g.large": 0.0928, + "t4g.xlarge": 0.1856, + "m6g.medium": 0.0549, + "m6g.large": 0.1098, + "m6g.xlarge": 0.2196, + "m6g.2xlarge": 0.4392, + "m7g.medium": 0.0582, + "m7g.large": 0.1164, + "m7g.xlarge": 0.2328, + "m7g.2xlarge": 0.4656, + "c6g.medium": 0.0482, + "c6g.large": 0.0964, + "c6g.xlarge": 0.1928, + "c6g.2xlarge": 0.3856, + "c7g.large": 0.1028, + "c7g.xlarge": 0.2056, + "r6g.large": 0.1448, + "r6g.xlarge": 0.2896 + }, + "ap-south-1": { + "t3.micro": 0.0114, + "t3.small": 0.0228, + "t3.medium": 0.0456, + "t3.large": 0.0912, + "t3.xlarge": 0.1824, + "t3a.medium": 0.041, + "t3a.large": 0.082, + "m5.large": 0.106, + "m5.xlarge": 0.212, + "m5.2xlarge": 0.424, + "c5.large": 0.094, + "c5.xlarge": 0.188, + "c5.2xlarge": 0.376, + "r5.large": 0.1396, + "r5.xlarge": 0.2792, + "t4g.micro": 0.0095, + "t4g.small": 0.019, + "t4g.medium": 0.038, + "t4g.large": 0.076, + "t4g.xlarge": 0.152, + "m6g.medium": 0.0454, + "m6g.large": 0.0908, + "m6g.xlarge": 0.1816, + "m6g.2xlarge": 0.3632, + "m7g.medium": 0.0481, + "m7g.large": 0.0962, + "m7g.xlarge": 0.1924, + "m7g.2xlarge": 0.3848, + "c6g.medium": 0.0399, + "c6g.large": 0.0798, + "c6g.xlarge": 0.1596, + "c6g.2xlarge": 0.3192, + "r6g.large": 0.1197, + "r6g.xlarge": 0.2394 + }, + "ca-central-1": { + "t3.micro": 0.0116, + "t3.small": 0.0232, + "t3.medium": 0.0464, + "t3.large": 0.0928, + "t3.xlarge": 0.1856, + "t3a.medium": 0.0418, + "t3a.large": 0.0836, + "m5.large": 0.107, + "m5.xlarge": 0.214, + "m5.2xlarge": 0.428, + "m5a.large": 0.096, + "m5a.xlarge": 0.192, + "c5.large": 0.095, + "c5.xlarge": 0.19, + "c5.2xlarge": 0.38, + "r5.large": 0.141, + "r5.xlarge": 0.282, + "t4g.micro": 0.0096, + "t4g.small": 0.0192, + "t4g.medium": 0.0386, + "t4g.large": 0.0772, + "t4g.xlarge": 0.1544, + "m6g.medium": 0.0462, + "m6g.large": 0.0924, + "m6g.xlarge": 0.1848, + "m6g.2xlarge": 0.3696, + "m7g.medium": 0.049, + "m7g.large": 0.098, + "m7g.xlarge": 0.196, + "m7g.2xlarge": 0.392, + "c6g.medium": 0.0408, + "c6g.large": 0.0816, + "c6g.xlarge": 0.1632, + "c6g.2xlarge": 0.3264, + "c7g.large": 0.087, + "c7g.xlarge": 0.174, + "r6g.large": 0.1218, + "r6g.xlarge": 0.2436 + }, + "sa-east-1": { + "t3.micro": 0.0168, + "t3.small": 0.0336, + "t3.medium": 0.0672, + "t3.large": 0.1344, + "t3.xlarge": 0.2688, + "m5.large": 0.162, + "m5.xlarge": 0.324, + "m5.2xlarge": 0.648, + "c5.large": 0.144, + "c5.xlarge": 0.288, + "c5.2xlarge": 0.576, + "t4g.medium": 0.056, + "t4g.large": 0.112, + "t4g.xlarge": 0.224, + "m6g.large": 0.1296, + "m6g.xlarge": 0.2592, + "m6g.2xlarge": 0.5184, + "m7g.large": 0.1374, + "m7g.xlarge": 0.2748, + "c6g.large": 0.1152, + "c6g.xlarge": 0.2304, + "r6g.large": 0.1706, + "r6g.xlarge": 0.3412 + } } }, - "instances": { - "t3.micro": { - "architecture": "x86_64", - "vcpus": 2, - "memory_gb": 1, - "power_watts": { - "idle": 1.4, - "max": 5.0 - }, - "embodied_co2e_grams_per_month": 1041.7 - }, - "t3.small": { - "architecture": "x86_64", - "vcpus": 2, - "memory_gb": 2, - "power_watts": { - "idle": 2.0, - "max": 7.0 - }, - "embodied_co2e_grams_per_month": 1041.7 - }, - "t3.medium": { - "architecture": "x86_64", - "vcpus": 2, - "memory_gb": 4, - "power_watts": { - "idle": 3.4, - "max": 10.2 - }, - "embodied_co2e_grams_per_month": 1041.7 - }, - "t3.large": { - "architecture": "x86_64", - "vcpus": 2, - "memory_gb": 8, - "power_watts": { - "idle": 6.8, - "max": 20.4 - }, - "embodied_co2e_grams_per_month": 1041.7 - }, - "t3.xlarge": { - "architecture": "x86_64", - "vcpus": 4, - "memory_gb": 16, - "power_watts": { - "idle": 13.6, - "max": 40.8 - }, - "embodied_co2e_grams_per_month": 2083.3 - }, - "t3a.medium": { - "architecture": "x86_64", - "vcpus": 2, - "memory_gb": 4, - "power_watts": { - "idle": 3.2, - "max": 9.8 - }, - "embodied_co2e_grams_per_month": 1041.7 - }, - "t3a.large": { - "architecture": "x86_64", - "vcpus": 2, - "memory_gb": 8, - "power_watts": { - "idle": 6.4, - "max": 19.6 - }, - "embodied_co2e_grams_per_month": 1041.7 - }, - "m5.large": { - "architecture": "x86_64", - "vcpus": 2, - "memory_gb": 8, - "power_watts": { - "idle": 6.8, - "max": 20.4 - }, - "embodied_co2e_grams_per_month": 1041.7 - }, - "m5.xlarge": { - "architecture": "x86_64", - "vcpus": 4, - "memory_gb": 16, - "power_watts": { - "idle": 13.6, - "max": 40.8 - }, - "embodied_co2e_grams_per_month": 2083.3 - }, - "m5.2xlarge": { - "architecture": "x86_64", - "vcpus": 8, - "memory_gb": 32, - "power_watts": { - "idle": 27.2, - "max": 81.6 - }, - "embodied_co2e_grams_per_month": 4166.7 - }, - "m5a.large": { - "architecture": "x86_64", - "vcpus": 2, - "memory_gb": 8, - "power_watts": { - "idle": 6.5, - "max": 19.5 - }, - "embodied_co2e_grams_per_month": 1041.7 - }, - "m5a.xlarge": { - "architecture": "x86_64", - "vcpus": 4, - "memory_gb": 16, - "power_watts": { - "idle": 13.0, - "max": 39.0 - }, - "embodied_co2e_grams_per_month": 2083.3 - }, - "c5.large": { - "architecture": "x86_64", - "vcpus": 2, - "memory_gb": 4, - "power_watts": { - "idle": 6.5, - "max": 22.0 - }, - "embodied_co2e_grams_per_month": 1041.7 - }, - "c5.xlarge": { - "architecture": "x86_64", - "vcpus": 4, - "memory_gb": 8, - "power_watts": { - "idle": 13.0, - "max": 44.0 - }, - "embodied_co2e_grams_per_month": 2083.3 - }, - "c5.2xlarge": { - "architecture": "x86_64", - "vcpus": 8, - "memory_gb": 16, - "power_watts": { - "idle": 26.0, - "max": 88.0 - }, - "embodied_co2e_grams_per_month": 4166.7 - }, - "c5a.large": { - "architecture": "x86_64", - "vcpus": 2, - "memory_gb": 4, - "power_watts": { - "idle": 6.2, - "max": 21.0 - }, - "embodied_co2e_grams_per_month": 1041.7 - }, - "c5a.xlarge": { - "architecture": "x86_64", - "vcpus": 4, - "memory_gb": 8, - "power_watts": { - "idle": 12.4, - "max": 42.0 - }, - "embodied_co2e_grams_per_month": 2083.3 - }, - "r5.large": { - "architecture": "x86_64", - "vcpus": 2, - "memory_gb": 16, - "power_watts": { - "idle": 8.0, - "max": 24.0 - }, - "embodied_co2e_grams_per_month": 1041.7 - }, - "r5.xlarge": { - "architecture": "x86_64", - "vcpus": 4, - "memory_gb": 32, - "power_watts": { - "idle": 16.0, - "max": 48.0 - }, - "embodied_co2e_grams_per_month": 2083.3 - }, - "t4g.micro": { - "architecture": "arm64", - "vcpus": 2, - "memory_gb": 1, - "power_watts": { - "idle": 0.9, - "max": 3.2 - }, - "embodied_co2e_grams_per_month": 833.3 - }, - "t4g.small": { - "architecture": "arm64", - "vcpus": 2, - "memory_gb": 2, - "power_watts": { - "idle": 1.4, - "max": 4.5 - }, - "embodied_co2e_grams_per_month": 833.3 - }, - "t4g.medium": { - "architecture": "arm64", - "vcpus": 2, - "memory_gb": 4, - "power_watts": { - "idle": 2.2, - "max": 6.8 - }, - "embodied_co2e_grams_per_month": 833.3 - }, - "t4g.large": { - "architecture": "arm64", - "vcpus": 2, - "memory_gb": 8, - "power_watts": { - "idle": 4.4, - "max": 13.6 - }, - "embodied_co2e_grams_per_month": 833.3 - }, - "t4g.xlarge": { - "architecture": "arm64", - "vcpus": 4, - "memory_gb": 16, - "power_watts": { - "idle": 8.8, - "max": 27.2 - }, - "embodied_co2e_grams_per_month": 1666.7 - }, - "m6g.medium": { - "architecture": "arm64", - "vcpus": 1, - "memory_gb": 4, - "power_watts": { - "idle": 2.1, - "max": 6.6 - }, - "embodied_co2e_grams_per_month": 416.7 - }, - "m6g.large": { - "architecture": "arm64", - "vcpus": 2, - "memory_gb": 8, - "power_watts": { - "idle": 4.1, - "max": 13.2 - }, - "embodied_co2e_grams_per_month": 833.3 - }, - "m6g.xlarge": { - "architecture": "arm64", - "vcpus": 4, - "memory_gb": 16, - "power_watts": { - "idle": 8.2, - "max": 26.4 - }, - "embodied_co2e_grams_per_month": 1666.7 - }, - "m6g.2xlarge": { - "architecture": "arm64", - "vcpus": 8, - "memory_gb": 32, - "power_watts": { - "idle": 16.4, - "max": 52.8 - }, - "embodied_co2e_grams_per_month": 3333.3 - }, - "m7g.medium": { - "architecture": "arm64", - "vcpus": 1, - "memory_gb": 4, - "power_watts": { - "idle": 1.8, - "max": 5.8 - }, - "embodied_co2e_grams_per_month": 416.7 - }, - "m7g.large": { - "architecture": "arm64", - "vcpus": 2, - "memory_gb": 8, - "power_watts": { - "idle": 3.6, - "max": 11.6 - }, - "embodied_co2e_grams_per_month": 833.3 - }, - "m7g.xlarge": { - "architecture": "arm64", - "vcpus": 4, - "memory_gb": 16, - "power_watts": { - "idle": 7.2, - "max": 23.2 - }, - "embodied_co2e_grams_per_month": 1666.7 - }, - "m7g.2xlarge": { - "architecture": "arm64", - "vcpus": 8, - "memory_gb": 32, - "power_watts": { - "idle": 14.4, - "max": 46.4 - }, - "embodied_co2e_grams_per_month": 3333.3 - }, - "c6g.medium": { - "architecture": "arm64", - "vcpus": 1, - "memory_gb": 2, - "power_watts": { - "idle": 2.0, - "max": 7.3 - }, - "embodied_co2e_grams_per_month": 416.7 - }, - "c6g.large": { - "architecture": "arm64", - "vcpus": 2, - "memory_gb": 4, - "power_watts": { - "idle": 3.9, - "max": 14.5 - }, - "embodied_co2e_grams_per_month": 833.3 - }, - "c6g.xlarge": { - "architecture": "arm64", - "vcpus": 4, - "memory_gb": 8, - "power_watts": { - "idle": 7.8, - "max": 29.0 - }, - "embodied_co2e_grams_per_month": 1666.7 - }, - "c6g.2xlarge": { - "architecture": "arm64", - "vcpus": 8, - "memory_gb": 16, - "power_watts": { - "idle": 15.6, - "max": 58.0 - }, - "embodied_co2e_grams_per_month": 3333.3 - }, - "c7g.large": { - "architecture": "arm64", - "vcpus": 2, - "memory_gb": 4, - "power_watts": { - "idle": 3.5, - "max": 13.0 - }, - "embodied_co2e_grams_per_month": 833.3 - }, - "c7g.xlarge": { - "architecture": "arm64", - "vcpus": 4, - "memory_gb": 8, - "power_watts": { - "idle": 7.0, - "max": 26.0 - }, - "embodied_co2e_grams_per_month": 1666.7 + "azure": { + "regions": { + "eastus": { + "location": "East US (Virginia)", + "grid_intensity_gco2e_per_kwh": 380.0, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.43 + }, + "eastus2": { + "location": "East US 2 (Virginia)", + "grid_intensity_gco2e_per_kwh": 380.0, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.43 + }, + "westus": { + "location": "West US (California)", + "grid_intensity_gco2e_per_kwh": 200.0, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.35 + }, + "westus2": { + "location": "West US 2 (Washington)", + "grid_intensity_gco2e_per_kwh": 235.0, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.18 + }, + "westus3": { + "location": "West US 3 (Arizona)", + "grid_intensity_gco2e_per_kwh": 350.0, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.55 + }, + "northeurope": { + "location": "North Europe (Ireland)", + "grid_intensity_gco2e_per_kwh": 334.0, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.22 + }, + "westeurope": { + "location": "West Europe (Netherlands)", + "grid_intensity_gco2e_per_kwh": 270.0, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.2 + }, + "uksouth": { + "location": "UK South (London)", + "grid_intensity_gco2e_per_kwh": 268.0, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.25 + }, + "ukwest": { + "location": "UK West (Cardiff)", + "grid_intensity_gco2e_per_kwh": 268.0, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.25 + }, + "swedencentral": { + "location": "Sweden Central", + "grid_intensity_gco2e_per_kwh": 8.8, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.1 + }, + "germanywestcentral": { + "location": "Germany West Central", + "grid_intensity_gco2e_per_kwh": 420.5, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.28 + }, + "southeastasia": { + "location": "Southeast Asia (Singapore)", + "grid_intensity_gco2e_per_kwh": 408.0, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.58 + }, + "australiaeast": { + "location": "Australia East (NSW)", + "grid_intensity_gco2e_per_kwh": 650.0, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.45 + }, + "japaneast": { + "location": "Japan East (Tokyo)", + "grid_intensity_gco2e_per_kwh": 506.0, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.5 + }, + "centralindia": { + "location": "Central India (Pune)", + "grid_intensity_gco2e_per_kwh": 723.0, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.72 + }, + "canadacentral": { + "location": "Canada Central (Toronto)", + "grid_intensity_gco2e_per_kwh": 130.0, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.2 + }, + "brazilsouth": { + "location": "Brazil South (Sao Paulo)", + "grid_intensity_gco2e_per_kwh": 74.0, + "pue": 1.125, + "water_intensity_litres_per_kwh": 0.35 + } }, - "r6g.large": { - "architecture": "arm64", - "vcpus": 2, - "memory_gb": 16, - "power_watts": { - "idle": 4.8, - "max": 15.0 - }, - "embodied_co2e_grams_per_month": 833.3 + "instances": { + "Standard_B2s": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 4, + "power_watts": { + "idle": 1.5, + "max": 5.5 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "Standard_B2ms": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 8, + "power_watts": { + "idle": 3.0, + "max": 11.0 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "Standard_B4ms": { + "architecture": "x86_64", + "vcpus": 4, + "memory_gb": 16, + "power_watts": { + "idle": 6.0, + "max": 22.0 + }, + "embodied_co2e_grams_per_month": 2083.3 + }, + "Standard_D2s_v3": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 8, + "power_watts": { + "idle": 6.8, + "max": 20.4 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "Standard_D4s_v3": { + "architecture": "x86_64", + "vcpus": 4, + "memory_gb": 16, + "power_watts": { + "idle": 13.6, + "max": 40.8 + }, + "embodied_co2e_grams_per_month": 2083.3 + }, + "Standard_D8s_v3": { + "architecture": "x86_64", + "vcpus": 8, + "memory_gb": 32, + "power_watts": { + "idle": 27.2, + "max": 81.6 + }, + "embodied_co2e_grams_per_month": 4166.6 + }, + "Standard_D2s_v4": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 8, + "power_watts": { + "idle": 6.5, + "max": 19.5 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "Standard_D4s_v4": { + "architecture": "x86_64", + "vcpus": 4, + "memory_gb": 16, + "power_watts": { + "idle": 13.0, + "max": 39.0 + }, + "embodied_co2e_grams_per_month": 2083.3 + }, + "Standard_D2ps_v5": { + "architecture": "arm64", + "vcpus": 2, + "memory_gb": 8, + "power_watts": { + "idle": 4.1, + "max": 13.2 + }, + "embodied_co2e_grams_per_month": 833.3 + }, + "Standard_D4ps_v5": { + "architecture": "arm64", + "vcpus": 4, + "memory_gb": 16, + "power_watts": { + "idle": 8.2, + "max": 26.4 + }, + "embodied_co2e_grams_per_month": 1666.7 + }, + "Standard_D8ps_v5": { + "architecture": "arm64", + "vcpus": 8, + "memory_gb": 32, + "power_watts": { + "idle": 16.4, + "max": 52.8 + }, + "embodied_co2e_grams_per_month": 3333.3 + }, + "Standard_F2s_v2": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 4, + "power_watts": { + "idle": 6.5, + "max": 22.0 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "Standard_F4s_v2": { + "architecture": "x86_64", + "vcpus": 4, + "memory_gb": 8, + "power_watts": { + "idle": 13.0, + "max": 44.0 + }, + "embodied_co2e_grams_per_month": 2083.3 + }, + "Standard_F8s_v2": { + "architecture": "x86_64", + "vcpus": 8, + "memory_gb": 16, + "power_watts": { + "idle": 26.0, + "max": 88.0 + }, + "embodied_co2e_grams_per_month": 4166.6 + }, + "Standard_E2s_v3": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 16, + "power_watts": { + "idle": 8.0, + "max": 24.0 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "Standard_E4s_v3": { + "architecture": "x86_64", + "vcpus": 4, + "memory_gb": 32, + "power_watts": { + "idle": 16.0, + "max": 48.0 + }, + "embodied_co2e_grams_per_month": 2083.3 + } }, - "r6g.xlarge": { - "architecture": "arm64", - "vcpus": 4, - "memory_gb": 32, - "power_watts": { - "idle": 9.6, - "max": 30.0 - }, - "embodied_co2e_grams_per_month": 1666.7 + "pricing_usd_per_hour": { + "eastus": { + "Standard_B2s": 0.041, + "Standard_B2ms": 0.081, + "Standard_B4ms": 0.162, + "Standard_D2s_v3": 0.096, + "Standard_D4s_v3": 0.192, + "Standard_D8s_v3": 0.384, + "Standard_D2s_v4": 0.091, + "Standard_D4s_v4": 0.182, + "Standard_D2ps_v5": 0.077, + "Standard_D4ps_v5": 0.154, + "Standard_D8ps_v5": 0.308, + "Standard_F2s_v2": 0.085, + "Standard_F4s_v2": 0.17, + "Standard_F8s_v2": 0.34, + "Standard_E2s_v3": 0.126, + "Standard_E4s_v3": 0.252 + }, + "eastus2": { + "Standard_B2s": 0.041, + "Standard_B2ms": 0.081, + "Standard_B4ms": 0.162, + "Standard_D2s_v3": 0.096, + "Standard_D4s_v3": 0.192, + "Standard_D8s_v3": 0.384, + "Standard_D2s_v4": 0.091, + "Standard_D4s_v4": 0.182, + "Standard_D2ps_v5": 0.077, + "Standard_D4ps_v5": 0.154, + "Standard_D8ps_v5": 0.308, + "Standard_F2s_v2": 0.085, + "Standard_F4s_v2": 0.17, + "Standard_F8s_v2": 0.34, + "Standard_E2s_v3": 0.126, + "Standard_E4s_v3": 0.252 + }, + "westus2": { + "Standard_B2s": 0.041, + "Standard_B2ms": 0.081, + "Standard_B4ms": 0.162, + "Standard_D2s_v3": 0.096, + "Standard_D4s_v3": 0.192, + "Standard_D8s_v3": 0.384, + "Standard_D2s_v4": 0.091, + "Standard_D4s_v4": 0.182, + "Standard_D2ps_v5": 0.077, + "Standard_D4ps_v5": 0.154, + "Standard_D8ps_v5": 0.308, + "Standard_F2s_v2": 0.085, + "Standard_F4s_v2": 0.17, + "Standard_F8s_v2": 0.34, + "Standard_E2s_v3": 0.126, + "Standard_E4s_v3": 0.252 + }, + "northeurope": { + "Standard_B2s": 0.044, + "Standard_B2ms": 0.087, + "Standard_B4ms": 0.174, + "Standard_D2s_v3": 0.104, + "Standard_D4s_v3": 0.208, + "Standard_D8s_v3": 0.416, + "Standard_D2s_v4": 0.098, + "Standard_D4s_v4": 0.196, + "Standard_D2ps_v5": 0.083, + "Standard_D4ps_v5": 0.166, + "Standard_D8ps_v5": 0.332, + "Standard_F2s_v2": 0.091, + "Standard_F4s_v2": 0.183, + "Standard_F8s_v2": 0.366, + "Standard_E2s_v3": 0.136, + "Standard_E4s_v3": 0.271 + }, + "westeurope": { + "Standard_B2s": 0.044, + "Standard_B2ms": 0.087, + "Standard_B4ms": 0.174, + "Standard_D2s_v3": 0.104, + "Standard_D4s_v3": 0.208, + "Standard_D8s_v3": 0.416, + "Standard_D2s_v4": 0.098, + "Standard_D4s_v4": 0.196, + "Standard_D2ps_v5": 0.083, + "Standard_D4ps_v5": 0.166, + "Standard_D8ps_v5": 0.332, + "Standard_F2s_v2": 0.091, + "Standard_F4s_v2": 0.183, + "Standard_F8s_v2": 0.366, + "Standard_E2s_v3": 0.136, + "Standard_E4s_v3": 0.271 + }, + "uksouth": { + "Standard_B2s": 0.046, + "Standard_B2ms": 0.091, + "Standard_B4ms": 0.183, + "Standard_D2s_v3": 0.109, + "Standard_D4s_v3": 0.218, + "Standard_D8s_v3": 0.436, + "Standard_D2s_v4": 0.103, + "Standard_D4s_v4": 0.206, + "Standard_D2ps_v5": 0.087, + "Standard_D4ps_v5": 0.174, + "Standard_D8ps_v5": 0.348, + "Standard_F2s_v2": 0.095, + "Standard_F4s_v2": 0.191, + "Standard_F8s_v2": 0.382, + "Standard_E2s_v3": 0.143, + "Standard_E4s_v3": 0.285 + }, + "swedencentral": { + "Standard_B2s": 0.042, + "Standard_B2ms": 0.083, + "Standard_B4ms": 0.166, + "Standard_D2s_v3": 0.099, + "Standard_D4s_v3": 0.198, + "Standard_D8s_v3": 0.397, + "Standard_D2s_v4": 0.094, + "Standard_D4s_v4": 0.187, + "Standard_D2ps_v5": 0.079, + "Standard_D4ps_v5": 0.159, + "Standard_D8ps_v5": 0.317, + "Standard_F2s_v2": 0.087, + "Standard_F4s_v2": 0.174, + "Standard_F8s_v2": 0.348, + "Standard_E2s_v3": 0.13, + "Standard_E4s_v3": 0.259 + }, + "southeastasia": { + "Standard_B2s": 0.048, + "Standard_B2ms": 0.096, + "Standard_B4ms": 0.192, + "Standard_D2s_v3": 0.114, + "Standard_D4s_v3": 0.229, + "Standard_D8s_v3": 0.458, + "Standard_D2s_v4": 0.108, + "Standard_D4s_v4": 0.217, + "Standard_D2ps_v5": 0.092, + "Standard_D4ps_v5": 0.184, + "Standard_D8ps_v5": 0.368, + "Standard_F2s_v2": 0.1, + "Standard_F4s_v2": 0.2, + "Standard_F8s_v2": 0.4, + "Standard_E2s_v3": 0.15, + "Standard_E4s_v3": 0.301 + }, + "australiaeast": { + "Standard_B2s": 0.052, + "Standard_B2ms": 0.104, + "Standard_B4ms": 0.208, + "Standard_D2s_v3": 0.124, + "Standard_D4s_v3": 0.248, + "Standard_D8s_v3": 0.496, + "Standard_D2s_v4": 0.117, + "Standard_D4s_v4": 0.234, + "Standard_D2ps_v5": 0.099, + "Standard_D4ps_v5": 0.199, + "Standard_D8ps_v5": 0.397, + "Standard_F2s_v2": 0.108, + "Standard_F4s_v2": 0.217, + "Standard_F8s_v2": 0.433, + "Standard_E2s_v3": 0.162, + "Standard_E4s_v3": 0.325 + }, + "japaneast": { + "Standard_B2s": 0.051, + "Standard_B2ms": 0.102, + "Standard_B4ms": 0.204, + "Standard_D2s_v3": 0.121, + "Standard_D4s_v3": 0.242, + "Standard_D8s_v3": 0.484, + "Standard_D2s_v4": 0.115, + "Standard_D4s_v4": 0.229, + "Standard_D2ps_v5": 0.097, + "Standard_D4ps_v5": 0.194, + "Standard_D8ps_v5": 0.388, + "Standard_F2s_v2": 0.105, + "Standard_F4s_v2": 0.211, + "Standard_F8s_v2": 0.422, + "Standard_E2s_v3": 0.158, + "Standard_E4s_v3": 0.317 + }, + "centralindia": { + "Standard_B2s": 0.043, + "Standard_B2ms": 0.086, + "Standard_B4ms": 0.171, + "Standard_D2s_v3": 0.102, + "Standard_D4s_v3": 0.203, + "Standard_D8s_v3": 0.406, + "Standard_D2s_v4": 0.096, + "Standard_D4s_v4": 0.192, + "Standard_D2ps_v5": 0.081, + "Standard_D4ps_v5": 0.162, + "Standard_D8ps_v5": 0.325, + "Standard_F2s_v2": 0.089, + "Standard_F4s_v2": 0.178, + "Standard_F8s_v2": 0.356, + "Standard_E2s_v3": 0.133, + "Standard_E4s_v3": 0.266 + }, + "canadacentral": { + "Standard_B2s": 0.043, + "Standard_B2ms": 0.086, + "Standard_B4ms": 0.171, + "Standard_D2s_v3": 0.101, + "Standard_D4s_v3": 0.202, + "Standard_D8s_v3": 0.404, + "Standard_D2s_v4": 0.096, + "Standard_D4s_v4": 0.191, + "Standard_D2ps_v5": 0.081, + "Standard_D4ps_v5": 0.162, + "Standard_D8ps_v5": 0.323, + "Standard_F2s_v2": 0.088, + "Standard_F4s_v2": 0.177, + "Standard_F8s_v2": 0.354, + "Standard_E2s_v3": 0.132, + "Standard_E4s_v3": 0.264 + }, + "brazilsouth": { + "Standard_B2s": 0.059, + "Standard_B2ms": 0.118, + "Standard_B4ms": 0.237, + "Standard_D2s_v3": 0.141, + "Standard_D4s_v3": 0.281, + "Standard_D8s_v3": 0.562, + "Standard_D2s_v4": 0.133, + "Standard_D4s_v4": 0.266, + "Standard_D2ps_v5": 0.113, + "Standard_D4ps_v5": 0.225, + "Standard_D8ps_v5": 0.45, + "Standard_F2s_v2": 0.123, + "Standard_F4s_v2": 0.246, + "Standard_F8s_v2": 0.492, + "Standard_E2s_v3": 0.184, + "Standard_E4s_v3": 0.368 + } } }, - "pricing_usd_per_hour": { - "us-east-1": { - "t3.micro": 0.0104, - "t3.small": 0.0208, - "t3.medium": 0.0416, - "t3.large": 0.0832, - "t3.xlarge": 0.1664, - "t3a.medium": 0.0376, - "t3a.large": 0.0752, - "m5.large": 0.096, - "m5.xlarge": 0.192, - "m5.2xlarge": 0.384, - "m5a.large": 0.086, - "m5a.xlarge": 0.172, - "c5.large": 0.085, - "c5.xlarge": 0.17, - "c5.2xlarge": 0.34, - "c5a.large": 0.077, - "c5a.xlarge": 0.154, - "r5.large": 0.126, - "r5.xlarge": 0.252, - "t4g.micro": 0.0084, - "t4g.small": 0.0168, - "t4g.medium": 0.0336, - "t4g.large": 0.0672, - "t4g.xlarge": 0.1344, - "m6g.medium": 0.0385, - "m6g.large": 0.077, - "m6g.xlarge": 0.154, - "m6g.2xlarge": 0.308, - "m7g.medium": 0.0408, - "m7g.large": 0.0816, - "m7g.xlarge": 0.1632, - "m7g.2xlarge": 0.3264, - "c6g.medium": 0.034, - "c6g.large": 0.068, - "c6g.xlarge": 0.136, - "c6g.2xlarge": 0.272, - "c7g.large": 0.0725, - "c7g.xlarge": 0.145, - "r6g.large": 0.1008, - "r6g.xlarge": 0.2016 - }, - "us-east-2": { - "t3.micro": 0.0104, - "t3.small": 0.0208, - "t3.medium": 0.0416, - "t3.large": 0.0832, - "t3.xlarge": 0.1664, - "t3a.medium": 0.0376, - "t3a.large": 0.0752, - "m5.large": 0.096, - "m5.xlarge": 0.192, - "m5.2xlarge": 0.384, - "m5a.large": 0.086, - "m5a.xlarge": 0.172, - "c5.large": 0.085, - "c5.xlarge": 0.17, - "c5.2xlarge": 0.34, - "c5a.large": 0.077, - "c5a.xlarge": 0.154, - "r5.large": 0.126, - "r5.xlarge": 0.252, - "t4g.micro": 0.0084, - "t4g.small": 0.0168, - "t4g.medium": 0.0336, - "t4g.large": 0.0672, - "t4g.xlarge": 0.1344, - "m6g.medium": 0.0385, - "m6g.large": 0.077, - "m6g.xlarge": 0.154, - "m6g.2xlarge": 0.308, - "m7g.medium": 0.0408, - "m7g.large": 0.0816, - "m7g.xlarge": 0.1632, - "m7g.2xlarge": 0.3264, - "c6g.medium": 0.034, - "c6g.large": 0.068, - "c6g.xlarge": 0.136, - "c6g.2xlarge": 0.272, - "c7g.large": 0.0725, - "c7g.xlarge": 0.145, - "r6g.large": 0.1008, - "r6g.xlarge": 0.2016 - }, - "us-west-1": { - "t3.micro": 0.0116, - "t3.small": 0.0232, - "t3.medium": 0.0464, - "t3.large": 0.0928, - "t3.xlarge": 0.1856, - "m5.large": 0.107, - "m5.xlarge": 0.214, - "m5.2xlarge": 0.428, - "c5.large": 0.096, - "c5.xlarge": 0.192, - "c5.2xlarge": 0.384, - "t4g.medium": 0.0376, - "t4g.large": 0.0752, - "t4g.xlarge": 0.1504, - "m6g.large": 0.086, - "m6g.xlarge": 0.172, - "m6g.2xlarge": 0.344, - "m7g.large": 0.0912, - "m7g.xlarge": 0.1824, - "c6g.large": 0.076, - "c6g.xlarge": 0.152, - "r6g.large": 0.1127, - "r6g.xlarge": 0.2254 - }, - "us-west-2": { - "t3.micro": 0.0104, - "t3.small": 0.0208, - "t3.medium": 0.0416, - "t3.large": 0.0832, - "t3.xlarge": 0.1664, - "t3a.medium": 0.0376, - "t3a.large": 0.0752, - "m5.large": 0.096, - "m5.xlarge": 0.192, - "m5.2xlarge": 0.384, - "m5a.large": 0.086, - "m5a.xlarge": 0.172, - "c5.large": 0.085, - "c5.xlarge": 0.17, - "c5.2xlarge": 0.34, - "c5a.large": 0.077, - "c5a.xlarge": 0.154, - "r5.large": 0.126, - "r5.xlarge": 0.252, - "t4g.micro": 0.0084, - "t4g.small": 0.0168, - "t4g.medium": 0.0336, - "t4g.large": 0.0672, - "t4g.xlarge": 0.1344, - "m6g.medium": 0.0385, - "m6g.large": 0.077, - "m6g.xlarge": 0.154, - "m6g.2xlarge": 0.308, - "m7g.medium": 0.0408, - "m7g.large": 0.0816, - "m7g.xlarge": 0.1632, - "m7g.2xlarge": 0.3264, - "c6g.medium": 0.034, - "c6g.large": 0.068, - "c6g.xlarge": 0.136, - "c6g.2xlarge": 0.272, - "c7g.large": 0.0725, - "c7g.xlarge": 0.145, - "r6g.large": 0.1008, - "r6g.xlarge": 0.2016 - }, - "eu-west-1": { - "t3.micro": 0.0116, - "t3.small": 0.0232, - "t3.medium": 0.0456, - "t3.large": 0.0912, - "t3.xlarge": 0.1824, - "t3a.medium": 0.0416, - "t3a.large": 0.0832, - "m5.large": 0.107, - "m5.xlarge": 0.214, - "m5.2xlarge": 0.428, - "m5a.large": 0.096, - "m5a.xlarge": 0.192, - "c5.large": 0.096, - "c5.xlarge": 0.192, - "c5.2xlarge": 0.384, - "c5a.large": 0.087, - "c5a.xlarge": 0.174, - "r5.large": 0.141, - "r5.xlarge": 0.282, - "t4g.micro": 0.0094, - "t4g.small": 0.0188, - "t4g.medium": 0.0376, - "t4g.large": 0.0752, - "t4g.xlarge": 0.1504, - "m6g.medium": 0.043, - "m6g.large": 0.086, - "m6g.xlarge": 0.172, - "m6g.2xlarge": 0.344, - "m7g.medium": 0.0456, - "m7g.large": 0.0912, - "m7g.xlarge": 0.1824, - "m7g.2xlarge": 0.3648, - "c6g.medium": 0.038, - "c6g.large": 0.076, - "c6g.xlarge": 0.152, - "c6g.2xlarge": 0.304, - "c7g.large": 0.0812, - "c7g.xlarge": 0.1624, - "r6g.large": 0.1127, - "r6g.xlarge": 0.2254 - }, - "eu-west-2": { - "t3.micro": 0.0126, - "t3.small": 0.0252, - "t3.medium": 0.0504, - "t3.large": 0.1008, - "t3.xlarge": 0.2016, - "m5.large": 0.1178, - "m5.xlarge": 0.2356, - "m5.2xlarge": 0.4712, - "c5.large": 0.1054, - "c5.xlarge": 0.2108, - "c5.2xlarge": 0.4216, - "t4g.medium": 0.0414, - "t4g.large": 0.0828, - "t4g.xlarge": 0.1656, - "m6g.large": 0.0945, - "m6g.xlarge": 0.189, - "m6g.2xlarge": 0.378, - "m7g.large": 0.1001, - "m7g.xlarge": 0.2002, - "c6g.large": 0.0836, - "c6g.xlarge": 0.1672, - "r6g.large": 0.124, - "r6g.xlarge": 0.248 - }, - "eu-central-1": { - "t3.micro": 0.012, - "t3.small": 0.024, - "t3.medium": 0.0496, - "t3.large": 0.0992, - "t3.xlarge": 0.1984, - "t3a.medium": 0.0448, - "t3a.large": 0.0896, - "m5.large": 0.115, - "m5.xlarge": 0.23, - "m5.2xlarge": 0.46, - "m5a.large": 0.103, - "m5a.xlarge": 0.206, - "c5.large": 0.102, - "c5.xlarge": 0.204, - "c5.2xlarge": 0.408, - "r5.large": 0.151, - "r5.xlarge": 0.302, - "t4g.micro": 0.01, - "t4g.small": 0.02, - "t4g.medium": 0.0416, - "t4g.large": 0.0832, - "t4g.xlarge": 0.1664, - "m6g.medium": 0.046, - "m6g.large": 0.092, - "m6g.xlarge": 0.184, - "m6g.2xlarge": 0.368, - "m7g.medium": 0.0488, - "m7g.large": 0.0976, - "m7g.xlarge": 0.1952, - "m7g.2xlarge": 0.3904, - "c6g.medium": 0.041, - "c6g.large": 0.082, - "c6g.xlarge": 0.164, - "c6g.2xlarge": 0.328, - "c7g.large": 0.0875, - "c7g.xlarge": 0.175, - "r6g.large": 0.121, - "r6g.xlarge": 0.242 - }, - "eu-north-1": { - "t3.micro": 0.0108, - "t3.small": 0.0216, - "t3.medium": 0.0432, - "t3.large": 0.0864, - "t3.xlarge": 0.1728, - "m5.large": 0.1, - "m5.xlarge": 0.2, - "m5.2xlarge": 0.4, - "c5.large": 0.089, - "c5.xlarge": 0.178, - "c5.2xlarge": 0.356, - "t4g.medium": 0.0362, - "t4g.large": 0.0724, - "t4g.xlarge": 0.1448, - "m6g.large": 0.08, - "m6g.xlarge": 0.16, - "m6g.2xlarge": 0.32, - "m7g.large": 0.0848, - "m7g.xlarge": 0.1696, - "c6g.large": 0.0712, - "c6g.xlarge": 0.1424, - "r6g.large": 0.1054, - "r6g.xlarge": 0.2108 - }, - "ap-southeast-1": { - "t3.micro": 0.0132, - "t3.small": 0.0264, - "t3.medium": 0.0528, - "t3.large": 0.1056, - "t3.xlarge": 0.2112, - "m5.large": 0.124, - "m5.xlarge": 0.248, - "m5.2xlarge": 0.496, - "c5.large": 0.107, - "c5.xlarge": 0.214, - "c5.2xlarge": 0.428, - "t4g.medium": 0.0438, - "t4g.large": 0.0876, - "t4g.xlarge": 0.1752, - "m6g.large": 0.0992, - "m6g.xlarge": 0.1984, - "m6g.2xlarge": 0.3968, - "m7g.large": 0.1051, - "m7g.xlarge": 0.2102, - "c6g.large": 0.086, - "c6g.xlarge": 0.172, - "r6g.large": 0.1307, - "r6g.xlarge": 0.2614 - }, - "ap-southeast-2": { - "t3.micro": 0.0136, - "t3.small": 0.0272, - "t3.medium": 0.0544, - "t3.large": 0.1088, - "t3.xlarge": 0.2176, - "t3a.medium": 0.0492, - "t3a.large": 0.0984, - "m5.large": 0.134, - "m5.xlarge": 0.268, - "m5.2xlarge": 0.536, - "m5a.large": 0.12, - "m5a.xlarge": 0.24, - "c5.large": 0.118, - "c5.xlarge": 0.236, - "c5.2xlarge": 0.472, - "r5.large": 0.176, - "r5.xlarge": 0.352, - "t4g.micro": 0.0113, - "t4g.small": 0.0226, - "t4g.medium": 0.0452, - "t4g.large": 0.0904, - "t4g.xlarge": 0.1808, - "m6g.medium": 0.0535, - "m6g.large": 0.107, - "m6g.xlarge": 0.214, - "m6g.2xlarge": 0.428, - "m7g.medium": 0.0567, - "m7g.large": 0.1134, - "m7g.xlarge": 0.2268, - "m7g.2xlarge": 0.4536, - "c6g.medium": 0.047, - "c6g.large": 0.094, - "c6g.xlarge": 0.188, - "c6g.2xlarge": 0.376, - "c7g.large": 0.1002, - "c7g.xlarge": 0.2004, - "r6g.large": 0.1411, - "r6g.xlarge": 0.2822 - }, - "ap-northeast-1": { - "t3.micro": 0.014, - "t3.small": 0.028, - "t3.medium": 0.056, - "t3.large": 0.112, - "t3.xlarge": 0.224, - "t3a.medium": 0.0504, - "t3a.large": 0.1008, - "m5.large": 0.128, - "m5.xlarge": 0.256, - "m5.2xlarge": 0.512, - "m5a.large": 0.115, - "m5a.xlarge": 0.23, - "c5.large": 0.114, - "c5.xlarge": 0.228, - "c5.2xlarge": 0.456, - "r5.large": 0.169, - "r5.xlarge": 0.338, - "t4g.micro": 0.0116, - "t4g.small": 0.0232, - "t4g.medium": 0.0464, - "t4g.large": 0.0928, - "t4g.xlarge": 0.1856, - "m6g.medium": 0.0549, - "m6g.large": 0.1098, - "m6g.xlarge": 0.2196, - "m6g.2xlarge": 0.4392, - "m7g.medium": 0.0582, - "m7g.large": 0.1164, - "m7g.xlarge": 0.2328, - "m7g.2xlarge": 0.4656, - "c6g.medium": 0.0482, - "c6g.large": 0.0964, - "c6g.xlarge": 0.1928, - "c6g.2xlarge": 0.3856, - "c7g.large": 0.1028, - "c7g.xlarge": 0.2056, - "r6g.large": 0.1448, - "r6g.xlarge": 0.2896 - }, - "ap-south-1": { - "t3.micro": 0.0114, - "t3.small": 0.0228, - "t3.medium": 0.0456, - "t3.large": 0.0912, - "t3.xlarge": 0.1824, - "t3a.medium": 0.041, - "t3a.large": 0.082, - "m5.large": 0.106, - "m5.xlarge": 0.212, - "m5.2xlarge": 0.424, - "c5.large": 0.094, - "c5.xlarge": 0.188, - "c5.2xlarge": 0.376, - "r5.large": 0.1396, - "r5.xlarge": 0.2792, - "t4g.micro": 0.0095, - "t4g.small": 0.019, - "t4g.medium": 0.038, - "t4g.large": 0.076, - "t4g.xlarge": 0.152, - "m6g.medium": 0.0454, - "m6g.large": 0.0908, - "m6g.xlarge": 0.1816, - "m6g.2xlarge": 0.3632, - "m7g.medium": 0.0481, - "m7g.large": 0.0962, - "m7g.xlarge": 0.1924, - "m7g.2xlarge": 0.3848, - "c6g.medium": 0.0399, - "c6g.large": 0.0798, - "c6g.xlarge": 0.1596, - "c6g.2xlarge": 0.3192, - "r6g.large": 0.1197, - "r6g.xlarge": 0.2394 + "gcp": { + "regions": { + "us-central1": { + "location": "Iowa", + "grid_intensity_gco2e_per_kwh": 340.0, + "pue": 1.1, + "water_intensity_litres_per_kwh": 0.4 + }, + "us-east1": { + "location": "South Carolina", + "grid_intensity_gco2e_per_kwh": 380.0, + "pue": 1.1, + "water_intensity_litres_per_kwh": 0.43 + }, + "us-east4": { + "location": "Virginia", + "grid_intensity_gco2e_per_kwh": 380.0, + "pue": 1.1, + "water_intensity_litres_per_kwh": 0.43 + }, + "us-west1": { + "location": "Oregon", + "grid_intensity_gco2e_per_kwh": 200.0, + "pue": 1.1, + "water_intensity_litres_per_kwh": 0.18 + }, + "us-west2": { + "location": "Los Angeles", + "grid_intensity_gco2e_per_kwh": 220.0, + "pue": 1.1, + "water_intensity_litres_per_kwh": 0.35 + }, + "europe-west1": { + "location": "Belgium", + "grid_intensity_gco2e_per_kwh": 144.0, + "pue": 1.1, + "water_intensity_litres_per_kwh": 0.2 + }, + "europe-west2": { + "location": "London", + "grid_intensity_gco2e_per_kwh": 268.0, + "pue": 1.1, + "water_intensity_litres_per_kwh": 0.25 + }, + "europe-west4": { + "location": "Netherlands", + "grid_intensity_gco2e_per_kwh": 270.0, + "pue": 1.1, + "water_intensity_litres_per_kwh": 0.2 + }, + "europe-north1": { + "location": "Finland", + "grid_intensity_gco2e_per_kwh": 18.0, + "pue": 1.1, + "water_intensity_litres_per_kwh": 0.12 + }, + "asia-southeast1": { + "location": "Singapore", + "grid_intensity_gco2e_per_kwh": 408.0, + "pue": 1.1, + "water_intensity_litres_per_kwh": 0.58 + }, + "asia-east1": { + "location": "Taiwan", + "grid_intensity_gco2e_per_kwh": 486.0, + "pue": 1.1, + "water_intensity_litres_per_kwh": 0.45 + }, + "asia-northeast1": { + "location": "Tokyo", + "grid_intensity_gco2e_per_kwh": 506.0, + "pue": 1.1, + "water_intensity_litres_per_kwh": 0.5 + }, + "asia-south1": { + "location": "Mumbai", + "grid_intensity_gco2e_per_kwh": 723.0, + "pue": 1.1, + "water_intensity_litres_per_kwh": 0.72 + }, + "northamerica-northeast1": { + "location": "Montreal", + "grid_intensity_gco2e_per_kwh": 28.0, + "pue": 1.1, + "water_intensity_litres_per_kwh": 0.2 + }, + "southamerica-east1": { + "location": "Sao Paulo", + "grid_intensity_gco2e_per_kwh": 74.0, + "pue": 1.1, + "water_intensity_litres_per_kwh": 0.35 + } }, - "ca-central-1": { - "t3.micro": 0.0116, - "t3.small": 0.0232, - "t3.medium": 0.0464, - "t3.large": 0.0928, - "t3.xlarge": 0.1856, - "t3a.medium": 0.0418, - "t3a.large": 0.0836, - "m5.large": 0.107, - "m5.xlarge": 0.214, - "m5.2xlarge": 0.428, - "m5a.large": 0.096, - "m5a.xlarge": 0.192, - "c5.large": 0.095, - "c5.xlarge": 0.19, - "c5.2xlarge": 0.38, - "r5.large": 0.141, - "r5.xlarge": 0.282, - "t4g.micro": 0.0096, - "t4g.small": 0.0192, - "t4g.medium": 0.0386, - "t4g.large": 0.0772, - "t4g.xlarge": 0.1544, - "m6g.medium": 0.0462, - "m6g.large": 0.0924, - "m6g.xlarge": 0.1848, - "m6g.2xlarge": 0.3696, - "m7g.medium": 0.049, - "m7g.large": 0.098, - "m7g.xlarge": 0.196, - "m7g.2xlarge": 0.392, - "c6g.medium": 0.0408, - "c6g.large": 0.0816, - "c6g.xlarge": 0.1632, - "c6g.2xlarge": 0.3264, - "c7g.large": 0.087, - "c7g.xlarge": 0.174, - "r6g.large": 0.1218, - "r6g.xlarge": 0.2436 + "instances": { + "n2-standard-2": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 8, + "power_watts": { + "idle": 6.8, + "max": 20.4 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "n2-standard-4": { + "architecture": "x86_64", + "vcpus": 4, + "memory_gb": 16, + "power_watts": { + "idle": 13.6, + "max": 40.8 + }, + "embodied_co2e_grams_per_month": 2083.3 + }, + "n2-standard-8": { + "architecture": "x86_64", + "vcpus": 8, + "memory_gb": 32, + "power_watts": { + "idle": 27.2, + "max": 81.6 + }, + "embodied_co2e_grams_per_month": 4166.7 + }, + "n2d-standard-2": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 8, + "power_watts": { + "idle": 6.5, + "max": 19.5 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "n2d-standard-4": { + "architecture": "x86_64", + "vcpus": 4, + "memory_gb": 16, + "power_watts": { + "idle": 13.0, + "max": 39.0 + }, + "embodied_co2e_grams_per_month": 2083.3 + }, + "t2a-standard-1": { + "architecture": "arm64", + "vcpus": 1, + "memory_gb": 4, + "power_watts": { + "idle": 2.1, + "max": 6.6 + }, + "embodied_co2e_grams_per_month": 416.7 + }, + "t2a-standard-2": { + "architecture": "arm64", + "vcpus": 2, + "memory_gb": 8, + "power_watts": { + "idle": 4.1, + "max": 13.2 + }, + "embodied_co2e_grams_per_month": 833.3 + }, + "t2a-standard-4": { + "architecture": "arm64", + "vcpus": 4, + "memory_gb": 16, + "power_watts": { + "idle": 8.2, + "max": 26.4 + }, + "embodied_co2e_grams_per_month": 1666.7 + }, + "t2a-standard-8": { + "architecture": "arm64", + "vcpus": 8, + "memory_gb": 32, + "power_watts": { + "idle": 16.4, + "max": 52.8 + }, + "embodied_co2e_grams_per_month": 3333.3 + }, + "c2-standard-4": { + "architecture": "x86_64", + "vcpus": 4, + "memory_gb": 16, + "power_watts": { + "idle": 13.0, + "max": 44.0 + }, + "embodied_co2e_grams_per_month": 2083.3 + }, + "c2-standard-8": { + "architecture": "x86_64", + "vcpus": 8, + "memory_gb": 32, + "power_watts": { + "idle": 26.0, + "max": 88.0 + }, + "embodied_co2e_grams_per_month": 4166.7 + }, + "e2-standard-2": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 8, + "power_watts": { + "idle": 3.4, + "max": 10.2 + }, + "embodied_co2e_grams_per_month": 1041.7 + }, + "e2-standard-4": { + "architecture": "x86_64", + "vcpus": 4, + "memory_gb": 16, + "power_watts": { + "idle": 6.8, + "max": 20.4 + }, + "embodied_co2e_grams_per_month": 2083.3 + }, + "t2d-standard-1": { + "architecture": "x86_64", + "vcpus": 1, + "memory_gb": 4, + "power_watts": { + "idle": 1.7, + "max": 5.1 + }, + "embodied_co2e_grams_per_month": 520.8 + }, + "t2d-standard-2": { + "architecture": "x86_64", + "vcpus": 2, + "memory_gb": 8, + "power_watts": { + "idle": 3.4, + "max": 10.2 + }, + "embodied_co2e_grams_per_month": 1041.7 + } }, - "sa-east-1": { - "t3.micro": 0.0168, - "t3.small": 0.0336, - "t3.medium": 0.0672, - "t3.large": 0.1344, - "t3.xlarge": 0.2688, - "m5.large": 0.162, - "m5.xlarge": 0.324, - "m5.2xlarge": 0.648, - "c5.large": 0.144, - "c5.xlarge": 0.288, - "c5.2xlarge": 0.576, - "t4g.medium": 0.056, - "t4g.large": 0.112, - "t4g.xlarge": 0.224, - "m6g.large": 0.1296, - "m6g.xlarge": 0.2592, - "m6g.2xlarge": 0.5184, - "m7g.large": 0.1374, - "m7g.xlarge": 0.2748, - "c6g.large": 0.1152, - "c6g.xlarge": 0.2304, - "r6g.large": 0.1706, - "r6g.xlarge": 0.3412 + "pricing_usd_per_hour": { + "us-central1": { + "n2-standard-2": 0.097, + "n2-standard-4": 0.194, + "n2-standard-8": 0.388, + "n2d-standard-2": 0.086, + "n2d-standard-4": 0.172, + "t2a-standard-1": 0.038, + "t2a-standard-2": 0.076, + "t2a-standard-4": 0.152, + "t2a-standard-8": 0.304, + "c2-standard-4": 0.2, + "c2-standard-8": 0.4, + "e2-standard-2": 0.067, + "e2-standard-4": 0.134, + "t2d-standard-1": 0.037, + "t2d-standard-2": 0.074 + }, + "us-east1": { + "n2-standard-2": 0.097, + "n2-standard-4": 0.194, + "n2-standard-8": 0.388, + "n2d-standard-2": 0.086, + "n2d-standard-4": 0.172, + "t2a-standard-1": 0.038, + "t2a-standard-2": 0.076, + "t2a-standard-4": 0.152, + "t2a-standard-8": 0.304, + "c2-standard-4": 0.2, + "c2-standard-8": 0.4, + "e2-standard-2": 0.067, + "e2-standard-4": 0.134, + "t2d-standard-1": 0.037, + "t2d-standard-2": 0.074 + }, + "us-east4": { + "n2-standard-2": 0.104, + "n2-standard-4": 0.208, + "n2-standard-8": 0.416, + "n2d-standard-2": 0.092, + "n2d-standard-4": 0.185, + "t2a-standard-1": 0.041, + "t2a-standard-2": 0.082, + "t2a-standard-4": 0.163, + "t2a-standard-8": 0.326, + "c2-standard-4": 0.214, + "c2-standard-8": 0.428, + "e2-standard-2": 0.072, + "e2-standard-4": 0.144, + "t2d-standard-1": 0.04, + "t2d-standard-2": 0.079 + }, + "us-west1": { + "n2-standard-2": 0.097, + "n2-standard-4": 0.194, + "n2-standard-8": 0.388, + "n2d-standard-2": 0.086, + "n2d-standard-4": 0.172, + "t2a-standard-1": 0.038, + "t2a-standard-2": 0.076, + "t2a-standard-4": 0.152, + "t2a-standard-8": 0.304, + "c2-standard-4": 0.2, + "c2-standard-8": 0.4, + "e2-standard-2": 0.067, + "e2-standard-4": 0.134, + "t2d-standard-1": 0.037, + "t2d-standard-2": 0.074 + }, + "europe-west1": { + "n2-standard-2": 0.108, + "n2-standard-4": 0.216, + "n2-standard-8": 0.432, + "n2d-standard-2": 0.096, + "n2d-standard-4": 0.192, + "t2a-standard-1": 0.042, + "t2a-standard-2": 0.085, + "t2a-standard-4": 0.169, + "t2a-standard-8": 0.339, + "c2-standard-4": 0.222, + "c2-standard-8": 0.445, + "e2-standard-2": 0.075, + "e2-standard-4": 0.15, + "t2d-standard-1": 0.044, + "t2d-standard-2": 0.087 + }, + "europe-west2": { + "n2-standard-2": 0.116, + "n2-standard-4": 0.231, + "n2-standard-8": 0.462, + "n2d-standard-2": 0.103, + "n2d-standard-4": 0.205, + "t2a-standard-1": 0.045, + "t2a-standard-2": 0.091, + "t2a-standard-4": 0.181, + "t2a-standard-8": 0.362, + "c2-standard-4": 0.238, + "c2-standard-8": 0.475, + "e2-standard-2": 0.08, + "e2-standard-4": 0.16, + "t2d-standard-1": 0.047, + "t2d-standard-2": 0.093 + }, + "europe-north1": { + "n2-standard-2": 0.108, + "n2-standard-4": 0.216, + "n2-standard-8": 0.432, + "n2d-standard-2": 0.096, + "n2d-standard-4": 0.192, + "t2a-standard-1": 0.042, + "t2a-standard-2": 0.085, + "t2a-standard-4": 0.169, + "t2a-standard-8": 0.339, + "c2-standard-4": 0.222, + "c2-standard-8": 0.445, + "e2-standard-2": 0.075, + "e2-standard-4": 0.15, + "t2d-standard-1": 0.044, + "t2d-standard-2": 0.087 + }, + "asia-southeast1": { + "n2-standard-2": 0.117, + "n2-standard-4": 0.234, + "n2-standard-8": 0.469, + "n2d-standard-2": 0.104, + "n2d-standard-4": 0.208, + "t2a-standard-1": 0.046, + "t2a-standard-2": 0.092, + "t2a-standard-4": 0.184, + "t2a-standard-8": 0.368, + "c2-standard-4": 0.241, + "c2-standard-8": 0.482, + "e2-standard-2": 0.081, + "e2-standard-4": 0.162, + "t2d-standard-1": 0.048, + "t2d-standard-2": 0.095 + }, + "asia-northeast1": { + "n2-standard-2": 0.126, + "n2-standard-4": 0.252, + "n2-standard-8": 0.504, + "n2d-standard-2": 0.112, + "n2d-standard-4": 0.223, + "t2a-standard-1": 0.049, + "t2a-standard-2": 0.099, + "t2a-standard-4": 0.197, + "t2a-standard-8": 0.395, + "c2-standard-4": 0.259, + "c2-standard-8": 0.517, + "e2-standard-2": 0.087, + "e2-standard-4": 0.174, + "t2d-standard-1": 0.051, + "t2d-standard-2": 0.102 + }, + "asia-south1": { + "n2-standard-2": 0.102, + "n2-standard-4": 0.204, + "n2-standard-8": 0.407, + "n2d-standard-2": 0.091, + "n2d-standard-4": 0.181, + "t2a-standard-1": 0.04, + "t2a-standard-2": 0.08, + "t2a-standard-4": 0.159, + "t2a-standard-8": 0.319, + "c2-standard-4": 0.209, + "c2-standard-8": 0.419, + "e2-standard-2": 0.07, + "e2-standard-4": 0.14, + "t2d-standard-1": 0.039, + "t2d-standard-2": 0.078 + }, + "northamerica-northeast1": { + "n2-standard-2": 0.104, + "n2-standard-4": 0.208, + "n2-standard-8": 0.416, + "n2d-standard-2": 0.092, + "n2d-standard-4": 0.185, + "t2a-standard-1": 0.041, + "t2a-standard-2": 0.082, + "t2a-standard-4": 0.163, + "t2a-standard-8": 0.326, + "c2-standard-4": 0.214, + "c2-standard-8": 0.428, + "e2-standard-2": 0.072, + "e2-standard-4": 0.144, + "t2d-standard-1": 0.04, + "t2d-standard-2": 0.079 + }, + "southamerica-east1": { + "n2-standard-2": 0.138, + "n2-standard-4": 0.276, + "n2-standard-8": 0.552, + "n2d-standard-2": 0.123, + "n2d-standard-4": 0.245, + "t2a-standard-1": 0.054, + "t2a-standard-2": 0.108, + "t2a-standard-4": 0.216, + "t2a-standard-8": 0.431, + "c2-standard-4": 0.283, + "c2-standard-8": 0.566, + "e2-standard-2": 0.095, + "e2-standard-4": 0.19, + "t2d-standard-1": 0.053, + "t2d-standard-2": 0.105 + } } } } \ No newline at end of file diff --git a/formatters/markdown.test.ts b/formatters/markdown.test.ts index d1785cf..df5912b 100644 --- a/formatters/markdown.test.ts +++ b/formatters/markdown.test.ts @@ -43,6 +43,7 @@ function makeMockResult(overrides: Partial = {}): PlanAnalys planFile: 'plan.json', resources: [], skipped: [], + providers: ['aws' as const], unsupportedTypes: [], totals: makeMockTotals(), ...overrides, diff --git a/formatters/table.test.ts b/formatters/table.test.ts index d69e721..4302de4 100644 --- a/formatters/table.test.ts +++ b/formatters/table.test.ts @@ -43,6 +43,7 @@ function makeMockResult(overrides: Partial = {}): PlanAnalys planFile: 'plan.json', resources: [], skipped: [], + providers: ['aws' as const], unsupportedTypes: [], totals: makeMockTotals(), ...overrides, diff --git a/package.json b/package.json index a82536d..87b78e5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "greenops-cli", - "version": "0.4.0", - "description": "Carbon footprint linting for Terraform plans. Analyses infrastructure changes for CO2e impact and cost, posts recommendations directly on GitHub PRs.", + "version": "0.5.0", + "description": "Carbon footprint linting for Terraform plans — 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": { "greenops-cli": "dist/index.cjs" @@ -23,6 +23,9 @@ "greenops", "cloud", "aws", + "azure", + "gcp", + "multi-cloud", "sustainability", "devops", "ci", diff --git a/policy.test.ts b/policy.test.ts index 8882cc3..d9a8a38 100644 --- a/policy.test.ts +++ b/policy.test.ts @@ -12,6 +12,7 @@ function makeMockResult(overrides: Partial = {}): planFile: 'plan.json', resources: [], skipped: [], + providers: ['aws' as const], unsupportedTypes: [], totals: { currentCo2eGramsPerMonth: 5000, diff --git a/recommendation.test.ts b/recommendation.test.ts index b010a53..3866bc3 100644 --- a/recommendation.test.ts +++ b/recommendation.test.ts @@ -40,7 +40,7 @@ describe('generateRecommendation', () => { assert.equal(rec!.suggestedInstanceType, 'm6g.large', 'Should suggest ARM equivalent'); assert.ok(rec!.co2eDeltaGramsPerMonth < 0, 'Carbon delta should be negative'); assert.ok(rec!.costDeltaUsdPerMonth < 0, 'Cost delta should be negative'); - assert.ok(rec!.rationale.includes('ARM64'), 'Rationale should mention ARM64'); + assert.ok(rec!.rationale.includes('ARM'), 'Rationale should mention ARM'); }); it('returns null for already-ARM instance in cleanest region', () => { diff --git a/types.ts b/types.ts index 2c3837e..9f6ef2f 100644 --- a/types.ts +++ b/types.ts @@ -16,10 +16,13 @@ export type PowerModel = | "IDLE_PLUS_DYNAMIC" // for Lambda-style invocation models (future) | "STATIC_TDP"; // fallback when only max TDP is known +export type CloudProvider = 'aws' | 'azure' | 'gcp'; + export interface ResourceInput { resourceId: string; // e.g., "aws_instance.web_server" - instanceType: string; // e.g., "m5.large" - region: string; // e.g., "us-east-1" + instanceType: string; // e.g., "m5.large" / "Standard_D2s_v3" / "n2-standard-2" + region: string; // e.g., "us-east-1" / "eastus" / "us-central1" + provider?: CloudProvider; // Default: 'aws' — backward compatible hoursPerMonth?: number; // Default: 730 (full calendar month) avgUtilization?: number; // Uses factors.json metadata default (50%) if omitted } @@ -90,6 +93,7 @@ export interface PlanAnalysisResult { analysedAt: string; // ISO timestamp ledgerVersion: string; // from factors.json metadata planFile: string; // path of the input plan + providers: CloudProvider[]; // which cloud providers were detected in this plan resources: Array<{ input: ResourceInput;