Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add carbon emissions admin configuration options #16307

Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3139773
Add carbon emissions admin configuration options
Renni771 Jun 12, 2023
611527b
Move carbon intensity reference data to backend and add admin docs
Renni771 Jun 24, 2023
2a8f864
Format code
Renni771 Jun 24, 2023
a19f06b
Merge dev
Renni771 Jun 24, 2023
a398ce7
Rebuild config to set `geographical_server_location_code` to its default
Renni771 Jun 24, 2023
03a4695
Fix carbon_intensity.csv formatting
Renni771 Jun 24, 2023
34d34e2
Correctly type power_usage_effectiveness flag in config
Renni771 Jun 28, 2023
a7a1db5
Add defaults to carbon intensity and PUE props
Renni771 Jun 29, 2023
8127e01
Merge branch 'dev' into add-carbon-emissions-admin-configuration-options
Renni771 Jun 29, 2023
bde21b0
Use f-string format for region name string
Renni771 Jun 29, 2023
ae36108
Fix job metrics heading hierarchy
Renni771 Jun 29, 2023
6a7429b
Move logic to get carbon intensity entry for location into its own mo…
Renni771 Jun 29, 2023
a11a772
Replace `yield` with `yield from` for carbon intensity csv file reader
Renni771 Jun 30, 2023
9a1df1c
Correctly make carbon_emissions its own module by renaming the init file
Renni771 Jun 30, 2023
a0dd18e
Fix typo and use `log.warning` instead of `log.warn`
Renni771 Jun 30, 2023
33dcace
Add `lib/galaxy/carbon_emissions` module symlink to `packages/app/gal…
Renni771 Jun 30, 2023
aa4177c
Add unit tests for `get_carbon_intensity_entry`
Renni771 Jun 30, 2023
6a15748
Add client side tests for dynamic PUE, carbon intensity and location …
Renni771 Jun 30, 2023
a5ed407
Run formatter on code
Renni771 Jun 30, 2023
c72904d
Fix types in config_schema
Renni771 Jul 1, 2023
753d199
Merge dev and resolve conflicts
Renni771 Jul 3, 2023
e428ca2
Fix copy paste error
Renni771 Jul 6, 2023
06ae184
Serialize geographical location code and remove unneeded carbon emiss…
Renni771 Jul 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion client/src/components/DatasetInformation/DatasetDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@
<inheritance-chain :dataset-id="datasetId" :dataset-name="dataset.name" />
<job-metrics
v-if="config"
:dataset-id="datasetId"
:carbon-intensity="config.carbon_intensity"
:geographical-server-location-name="config.geographical_server_location_name"
:power-usage-effectiveness="config.power_usage_effectiveness"
:should-show-aws-estimate="config.aws_estimate"
:dataset-id="datasetId" />
:should-show-carbon-emissions-estimates="config.carbon_emissions_estimates" />
<job-destination-params v-if="currentUser.is_admin" :job-id="dataset.creating_job" />
<job-dependencies :dependencies="job.dependencies"></job-dependencies>
<div v-if="dataset.peek">
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/JobMetrics/AwsEstimate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const computedAwsEstimate = computed(() => {

<template>
<div v-if="computedAwsEstimate" id="aws-estimate" class="mt-4">
<h3>AWS estimate</h3>
<h2>AWS estimate</h2>

<strong id="aws-cost">{{ computedAwsEstimate.price }} USD</strong>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { mount } from "@vue/test-utils";
import { getLocalVue } from "tests/jest/helpers";

import { worldwideCarbonIntensity, worldwidePowerUsageEffectiveness } from "./carbonEmissionConstants.js";
import CarbonEmissions from "./CarbonEmissions";

const localVue = getLocalVue();
Expand All @@ -17,13 +18,15 @@ const testServerInstance = {
};

describe("CarbonEmissions/CarbonEmissions.vue", () => {
it("correctly calculates carbon emissions.", async () => {
it("correctly calculates carbon emissions.", () => {
const wrapper = mount(CarbonEmissions, {
propsData: {
carbonIntensity: worldwideCarbonIntensity,
coresAllocated: 1,
estimatedServerInstance: testServerInstance,
jobRuntimeInSeconds: oneHourInSeconds,
coresAllocated: 1,
memoryAllocatedInMebibyte: oneGibibyteMemoryInMebibyte,
powerUsageEffectiveness: worldwidePowerUsageEffectiveness,
},
localVue,
});
Expand All @@ -42,14 +45,118 @@ describe("CarbonEmissions/CarbonEmissions.vue", () => {
it("does not render memory estimates when no value can be determined.", () => {
const wrapper = mount(CarbonEmissions, {
propsData: {
estimatedServerInstance: testServerInstance,
jobRuntime: 1,
coresAllocated: 1,
estimatedServerInstance: testServerInstance,
jobRuntimeInSeconds: 1,
},
localVue,
});

expect(wrapper.find("#memory-carbon-emissions").exists()).toBe(false);
expect(wrapper.find("#memory-energy-usage").exists()).toBe(false);
});

it("takes the configured `powerUsageEffectiveness` value into account.", () => {
const wrapper = mount(CarbonEmissions, {
propsData: {
carbonIntensity: 1,
coresAllocated: 1,
estimatedServerInstance: testServerInstance,
jobRuntimeInSeconds: 1,
memoryAllocatedInMebibyte: 1,
powerUsageEffectiveness: 0,
},
localVue,
});

const cpuEmissions = wrapper.find("#cpu-carbon-emissions").text();
const cpuEnergyUsage = wrapper.find("#cpu-energy-usage").text();
const memoryEmissions = wrapper.find("#memory-carbon-emissions").text();
const memoryEnergyUsage = wrapper.find("#memory-energy-usage").text();

expect(cpuEmissions).toMatch("0 g CO2e");
expect(cpuEnergyUsage).toMatch("0 kW⋅h");
expect(memoryEmissions).toMatch("0 g CO2e");
expect(memoryEnergyUsage).toMatch("0 kW⋅h");
});

it("takes the configured `carbonIntensity` value into account.", () => {
const wrapper = mount(CarbonEmissions, {
propsData: {
carbonIntensity: 0,
coresAllocated: 1,
estimatedServerInstance: testServerInstance,
jobRuntimeInSeconds: 1,
memoryAllocatedInMebibyte: 1,
powerUsageEffectiveness: 1,
},
localVue,
});

const cpuEmissions = wrapper.find("#cpu-carbon-emissions").text();
const memoryEmissions = wrapper.find("#memory-carbon-emissions").text();

expect(cpuEmissions).toMatch("0 g CO2e");
expect(memoryEmissions).toMatch("0 g CO2e");
});

it("displays text saying that global values were used when the `geographicalServerLocationName` prop is set to `GLOBAL`.", () => {
const carbonIntensity = worldwideCarbonIntensity;
const wrapper = mount(CarbonEmissions, {
propsData: {
carbonIntensity,
coresAllocated: 2,
estimatedServerInstance: testServerInstance,
jobRuntimeInSeconds: 2,
geographicalServerLocationName: "GLOBAL",
},
localVue,
});
const locationText = wrapper.find("#location-explanation").element;
expect(locationText).toHaveTextContent(
`1. Based off of the global carbon intensity value of ${carbonIntensity}.`
);
});

it("displays text saying that the carbon intensity value corresponding to `geographicalServerLocationName` was used.", () => {
const locationName = "Italy";
const carbonIntensity = worldwideCarbonIntensity;
const wrapper = mount(CarbonEmissions, {
propsData: {
carbonIntensity,
coresAllocated: 2,
estimatedServerInstance: testServerInstance,
jobRuntimeInSeconds: 2,
memoryAllocatedInMebibyte: oneGibibyteMemoryInMebibyte,
powerUsageEffectiveness: worldwideCarbonIntensity,
geographicalServerLocationName: locationName,
},
localVue,
});

const locationElement = wrapper.find("#location-explanation").element;
expect(locationElement).toHaveTextContent(
`1. based off of this galaxy instance's configured location of ${locationName}, which has a carbon intensity value of ${carbonIntensity} gCO2/kWh.`
);
});

it("displays text saying that global average values for PUE where used when `powerUsageEffectiveness` matches the global average.", () => {
const powerUsageEffectiveness = worldwidePowerUsageEffectiveness;
const wrapper = mount(CarbonEmissions, {
propsData: {
carbonIntensity: 1,
coresAllocated: 1,
estimatedServerInstance: testServerInstance,
jobRuntimeInSeconds: 1,
memoryAllocatedInMebibyte: 1,
powerUsageEffectiveness,
},
localVue,
});

const locationElement = wrapper.find("#pue").element;
expect(locationElement).toHaveTextContent(
`2. Using the global default power usage effectiveness value of ${powerUsageEffectiveness}.`
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@ interface CarbonEmissionsProps {
};
jobRuntimeInSeconds: number;
coresAllocated: number;
powerUsageEffectiveness: number;
geographicalServerLocationName: string;
memoryAllocatedInMebibyte?: number;
carbonIntensity: number;
}

const props = withDefaults(defineProps<CarbonEmissionsProps>(), {
memoryAllocatedInMebibyte: 0,
});

const carbonEmissions = computed(() => {
const powerUsageEffectiveness = carbonEmissionsConstants.worldwidePowerUsageEffectiveness;
const memoryPowerUsed = carbonEmissionsConstants.memoryPowerUsage;
const runtimeInHours = props.jobRuntimeInSeconds / (60 * 60); // Convert to hours
const memoryAllocatedInGibibyte = props.memoryAllocatedInMebibyte / 1024; // Convert to gibibyte
Expand All @@ -42,8 +44,8 @@ const carbonEmissions = computed(() => {
const normalizedTdpPerCore = tdpPerCore * props.coresAllocated;

// Power needed in Watt
const powerNeededCpu = powerUsageEffectiveness * normalizedTdpPerCore;
const powerNeededMemory = powerUsageEffectiveness * memoryAllocatedInGibibyte * memoryPowerUsed;
const powerNeededCpu = props.powerUsageEffectiveness * normalizedTdpPerCore;
const powerNeededMemory = props.powerUsageEffectiveness * memoryAllocatedInGibibyte * memoryPowerUsed;
const totalPowerNeeded = powerNeededCpu + powerNeededMemory;

// Energy needed. Convert Watt to kWh
Expand All @@ -52,7 +54,7 @@ const carbonEmissions = computed(() => {
const totalEnergyNeeded = (runtimeInHours * totalPowerNeeded) / 1000;

// Carbon emissions (carbon intensity is in grams/kWh so emissions results are in grams of CO2)
const carbonIntensity = carbonEmissionsConstants.worldwideCarbonIntensity;
const carbonIntensity = props.carbonIntensity;
const cpuCarbonEmissions = energyNeededCPU * carbonIntensity;
const memoryCarbonEmissions = energyNeededMemory * carbonIntensity;
const totalCarbonEmissions = totalEnergyNeeded * carbonIntensity;
Expand Down Expand Up @@ -266,9 +268,9 @@ function getEnergyNeededText(energyNeededInKiloWattHours: number) {
}
</script>

<template>
<div v-if="carbonEmissions && carbonEmissionsComparisons" class="mt-4">
<Heading h1 separator inline bold> Carbon Footprint </Heading>
<template v-if="carbonEmissions && carbonEmissionsComparisons">
<div class="mt-4">
<Heading h2 separator inline bold> Carbon Footprint </Heading>

<section class="carbon-emission-values my-4">
<div class="emissions-grid">
Expand Down Expand Up @@ -307,8 +309,8 @@ function getEnergyNeededText(energyNeededInKiloWattHours: number) {

<thead>
<th>Component</th>
<th>Carbon Emissions <sup>1.</sup> <sup>2.</sup></th>
<th>Energy Usage <sup>1.</sup></th>
<th>Carbon Emissions <sup>1.</sup> <sup>2.</sup> <sup>3.</sup></th>
<th>Energy Usage <sup>2.</sup> <sup>3.</sup></th>
</thead>

<tbody>
Expand All @@ -333,16 +335,34 @@ function getEnergyNeededText(energyNeededInKiloWattHours: number) {
</table>

<p class="p-0 m-0">
<strong>1.</strong> based off of the closest AWS EC2 instance comparable to the server that ran this
job. Estimates depend on the core count, allocated memory and the job runtime. The closest estimate
is a <strong>{{ estimatedServerInstance.name }}</strong> instance.
<span v-if="geographicalServerLocationName === 'GLOBAL'" id="location-explanation">
<strong>1.</strong> Based off of the global carbon intensity value of
{{ carbonEmissionsConstants.worldwideCarbonIntensity }}.
</span>
<span v-else id="location-explanation">
<strong>1.</strong> based off of this galaxy instance's configured location of
<strong>{{ geographicalServerLocationName }}</strong
>, which has a carbon intensity value of {{ carbonIntensity }} gCO2/kWh.
</span>

<br />

<strong>2.</strong> CO2e represents other types of greenhouse gases the have similar global warming
potential as a metric unit amount of CO2 itself.
<span
v-if="powerUsageEffectiveness === carbonEmissionsConstants.worldwidePowerUsageEffectiveness"
id="pue">
<strong>2.</strong> Using the global default power usage effectiveness value of
{{ carbonEmissionsConstants.worldwidePowerUsageEffectiveness }}.
</span>
<span v-else id="pue">
<strong>2.</strong> using the galaxy instance's configured power usage effectiveness ratio value
of of {{ powerUsageEffectiveness }}.
</span>

<br />

<strong>3.</strong> based off of the closest AWS EC2 instance comparable to the server that ran this
job. Estimates depend on the core count, allocated memory and the job runtime. The closest estimate
is a <strong>{{ estimatedServerInstance.name }}</strong> instance.
</p>

<router-link
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const treeYear = 11000;
// source: http://www.sciencedirect.com/science/article/pii/S0269749101002640
export const treeMonth = 917;

// source: https://www.iea.org/reports/global-energy-co2-status-report-2019/emissions
// source: (in gCO2/kWh) https://www.iea.org/reports/global-energy-co2-status-report-2019/emissions
export const worldwideCarbonIntensity = 475.0;

// source: https://journal.uptimeinstitute.com/is-pue-actually-going-up/
Expand Down
33 changes: 28 additions & 5 deletions client/src/components/JobMetrics/JobMetrics.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@ import { computed, ref, unref } from "vue";

import { useJobMetricsStore } from "@/stores/jobMetricsStore";

import { worldwideCarbonIntensity, worldwidePowerUsageEffectiveness } from "./CarbonEmissions/carbonEmissionConstants";

import AwsEstimate from "./AwsEstimate.vue";
import CarbonEmissions from "./CarbonEmissions/CarbonEmissions.vue";

const props = defineProps({
jobId: {
type: String,
default: null,
},
datasetFilesize: {
type: Number,
default: 0,
Expand All @@ -27,10 +25,30 @@ const props = defineProps({
type: Boolean,
default: true,
},
jobId: {
type: String,
default: null,
},
powerUsageEffectiveness: {
type: Number,
default: worldwidePowerUsageEffectiveness,
},
geographicalServerLocationName: {
type: String,
default: "GLOBAL",
},
carbonIntensity: {
type: Number,
default: worldwideCarbonIntensity,
},
shouldShowAwsEstimate: {
type: Boolean,
default: false,
},
shouldShowCarbonEmissionsEstimates: {
type: Boolean,
default: true,
},
});

const jobMetricsStore = useJobMetricsStore();
Expand Down Expand Up @@ -184,7 +202,12 @@ const estimatedServerInstance = computed(() => {
:memory-allocated-in-mebibyte="memoryAllocatedInMebibyte" />

<CarbonEmissions
v-if="estimatedServerInstance && jobRuntimeInSeconds && coresAllocated"
v-if="
shouldShowCarbonEmissionsEstimates && estimatedServerInstance && jobRuntimeInSeconds && coresAllocated
"
:carbon-intensity="carbonIntensity"
:geographical-server-location-name="geographicalServerLocationName"
:power-usage-effectiveness="powerUsageEffectiveness"
:estimated-server-instance="estimatedServerInstance"
:job-runtime-in-seconds="jobRuntimeInSeconds"
:cores-allocated="coresAllocated"
Expand Down
Loading