@tgwf/co2 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -5
- package/README.md +22 -25
- package/data/Lean-ICT-Materials-1byte-Model-2018.xlsx +0 -0
- package/data/Lean-ICT-Materials-Forecast-Model-2018.xlsx +0 -0
- package/data/fixtures/url2green.test.json.gz +0 -0
- package/images/swd-energy-usage.png +0 -0
- package/package.json +3 -2
- package/src/1byte.js +52 -8
- package/src/1byte.test.js +17 -0
- package/src/co2.js +19 -34
- package/src/co2.test.js +267 -118
- package/src/constants/file-size.js +5 -0
- package/src/constants/index.js +3 -0
- package/src/helpers/index.js +5 -0
- package/src/hosting-api.test.js +5 -5
- package/src/hosting-database.test.js +5 -5
- package/src/hosting-json.js +21 -1
- package/src/hosting-json.test.js +19 -6
- package/src/hosting.test.js +10 -10
- package/src/readme.md +66 -0
- package/src/sustainable-web-design.js +224 -0
- package/src/sustainable-web-design.test.js +81 -0
package/src/readme.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# How to use the different models in CO2.js
|
|
2
|
+
|
|
3
|
+
CO2js offers two models for understanding the environmental impact of compute - the 1byte model (`1byte.js`), and the Sustainable Web Design model (`swd.js`)
|
|
4
|
+
|
|
5
|
+
### The 1byte model
|
|
6
|
+
|
|
7
|
+
The default model in use is the 1byte model as used by the Shift Project, as introduced in their report on CO2 emissions from digital infrastructure, [Lean ICT: for a sober digital][soberDigital].
|
|
8
|
+
|
|
9
|
+
This returns a number for the estimated CO2 emissions for the corresponding number of bytes sent over the wire, and has been used for video streaming, file downloads and websites.
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
// assume you have imported or required the CO2 class from 1byte.js
|
|
13
|
+
// for your runtime - either a browser, nodejs, etc.
|
|
14
|
+
|
|
15
|
+
const bytesSent = (1024 * 1024 * 1024)
|
|
16
|
+
const co2Emission = new CO2();
|
|
17
|
+
const estimatedCO2 = co2Emission.perByte(bytesSent)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### The Sustainable Web Design model
|
|
22
|
+
|
|
23
|
+
As of version 0.9, CO2.js also provides the [Sustainable Web Design model][swd] for calculating emissions from digital services. As the name suggests, this has been designed for helping understand the environmental impact of websites. Further details are available on the [Sustainable Web Design website explaining the model](https://sustainablewebdesign.org/calculating-digital-emissions/), but for convenience, a short summary is below.
|
|
24
|
+
|
|
25
|
+
#### How the SWD works
|
|
26
|
+
|
|
27
|
+
This model uses data transfer as an proxy indicator for total resource usage, and uses this number to extrapolate energy usage numbers for your application as a fraction of the energy used by the total system comprised of:
|
|
28
|
+
|
|
29
|
+
1. the use-phase energy of datacentres serving content
|
|
30
|
+
2. the use-phase energy network transfering the data
|
|
31
|
+
3. the use-phase energy of user device an user is accessing content on
|
|
32
|
+
4. the total embodied energy used to create all of the above
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
It then converts these energy figures to carbon emissions, based on the carbon intensity of electricity from the [Ember annual global electricity review][Ember-annual-global-electricity-review].
|
|
37
|
+
|
|
38
|
+
The carbon intensity of electricity figures for the swd model include the the full lifecycle emissions including upstream methane, supply-chain and manufacturing emissions, and include all gases, converted into CO2 equivalent over a 100 year timescale.
|
|
39
|
+
|
|
40
|
+
This follows the approach used by the IPCC 5th Assessment Report Annex 3 (2014), for the carbon intensity of electricity.
|
|
41
|
+
|
|
42
|
+
[Ember's methodology notes][ember-methodology] detail where the rest of this data comes from in more detail, as well as any further assumptions made.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
[ember-methodology]: https://ember-climate.org/app/uploads/2022/03/GER22-Methodology.pdf
|
|
46
|
+
|
|
47
|
+
[Ember-annual-global-electricity-review]: https://ember-climate.org/insights/research/european-electricity-review-2022/
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
### Sample usage
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
// assume you have imported or required the CO2 class from swd.js
|
|
56
|
+
// for your runtime - either a browser, nodejs, etc.
|
|
57
|
+
|
|
58
|
+
// assume a 1 megabyte webpage
|
|
59
|
+
const bytesSent = (1024 * 1024 * 1024)
|
|
60
|
+
|
|
61
|
+
const co2Emission = new CO2();
|
|
62
|
+
const estimatedCO2ForTransfer = co2Emission.emissionsPerVisitInGrams(bytesSent)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
[soberDigital]: https://theshiftproject.org/en/lean-ict-2/
|
|
66
|
+
[swd]: https://sustainablewebdesign.org/calculating-digital-emissions
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sustainable Web Design
|
|
5
|
+
*
|
|
6
|
+
* Updated calculations and figures from
|
|
7
|
+
* https://sustainablewebdesign.org/calculating-digital-emissions/
|
|
8
|
+
*
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
const { fileSize } = require("./constants");
|
|
12
|
+
const { formatNumber } = require("./helpers");
|
|
13
|
+
|
|
14
|
+
// this refers to the estimated total energy use for the internet around 2000 TWh,
|
|
15
|
+
// divided by the total transfer it enables around 2500 exabytes
|
|
16
|
+
const KWH_PER_GB = 0.81;
|
|
17
|
+
|
|
18
|
+
// these constants outline how the energy is attributed to
|
|
19
|
+
// different parts of the system in the SWD model
|
|
20
|
+
const END_USER_DEVICE_ENERGY = 0.52;
|
|
21
|
+
const NETWORK_ENERGY = 0.14;
|
|
22
|
+
const DATACENTER_ENERGY = 0.15;
|
|
23
|
+
const PRODUCTION_ENERGY = 0.19;
|
|
24
|
+
|
|
25
|
+
// These carbon intensity figures https://ember-climate.org/data/data-explorer
|
|
26
|
+
// - Global carbon intensity for 2021
|
|
27
|
+
const GLOBAL_INTENSITY = 442;
|
|
28
|
+
const RENEWABLES_INTENSITY = 50;
|
|
29
|
+
|
|
30
|
+
// Taken from: https://gitlab.com/wholegrain/carbon-api-2-0/-/blob/master/includes/carbonapi.php
|
|
31
|
+
|
|
32
|
+
const FIRST_TIME_VIEWING_PERCENTAGE = 0.25;
|
|
33
|
+
const RETURNING_VISITOR_PERCENTAGE = 0.75;
|
|
34
|
+
const PERCENTAGE_OF_DATA_LOADED_ON_SUBSEQUENT_LOAD = 0.02;
|
|
35
|
+
|
|
36
|
+
class SustainableWebDesign {
|
|
37
|
+
constructor(options) {
|
|
38
|
+
this.options = options;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Accept a figure for bytes transferred and return an object representing
|
|
43
|
+
* the share of the total enrgy use of the entire system, broken down
|
|
44
|
+
* by each corresponding system component
|
|
45
|
+
*
|
|
46
|
+
* @param {number} bytes - the data transferred in bytes
|
|
47
|
+
* @return {object} Object containing the energy in kilowatt hours, keyed by system component
|
|
48
|
+
*/
|
|
49
|
+
energyPerByteByComponent(bytes) {
|
|
50
|
+
const transferedBytesToGb = bytes / fileSize.GIGABYTE;
|
|
51
|
+
const energyUsage = transferedBytesToGb * KWH_PER_GB;
|
|
52
|
+
|
|
53
|
+
// return the total energy, with breakdown by component
|
|
54
|
+
return {
|
|
55
|
+
consumerDeviceEnergy: energyUsage * END_USER_DEVICE_ENERGY,
|
|
56
|
+
networkEnergy: energyUsage * NETWORK_ENERGY,
|
|
57
|
+
productionEnergy: energyUsage * PRODUCTION_ENERGY,
|
|
58
|
+
dataCenterEnergy: energyUsage * DATACENTER_ENERGY,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Accept an object keys by the different system components, and
|
|
63
|
+
* return an object with the co2 figures key by the each component
|
|
64
|
+
*
|
|
65
|
+
* @param {object} energyBycomponent - energy grouped by the four system components
|
|
66
|
+
* @param {number} [carbonIntensity] - carbon intensity to apply to the datacentre values
|
|
67
|
+
* @return {number} the total number in grams of CO2 equivalent emissions
|
|
68
|
+
*/
|
|
69
|
+
co2byComponent(energyBycomponent, carbonIntensity = GLOBAL_INTENSITY) {
|
|
70
|
+
const returnCO2ByComponent = {};
|
|
71
|
+
for (const [key, value] of Object.entries(energyBycomponent)) {
|
|
72
|
+
// we update the datacentre, as that's what we have information
|
|
73
|
+
// about.
|
|
74
|
+
if (key === "dataCenterEnergy") {
|
|
75
|
+
returnCO2ByComponent[key] = value * carbonIntensity;
|
|
76
|
+
} else {
|
|
77
|
+
// We don't have info about the device location,
|
|
78
|
+
// nor the network path used, nor the production emissions
|
|
79
|
+
// so we revert to global figures
|
|
80
|
+
returnCO2ByComponent[key] = value * GLOBAL_INTENSITY;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return returnCO2ByComponent;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Accept a figure for bytes transferred and return a single figure for CO2
|
|
88
|
+
* emissions. Where information exists about the origin data is being
|
|
89
|
+
* fetched from, a different carbon intensity figure
|
|
90
|
+
* is applied for the datacentre share of the carbon intensity.
|
|
91
|
+
*
|
|
92
|
+
* @param {number} bytes - the data transferred in bytes
|
|
93
|
+
* @param {number} `carbonIntensity` the carbon intensity for datacentre (average figures, not marginal ones)
|
|
94
|
+
* @return {number} the total number in grams of CO2 equivalent emissions
|
|
95
|
+
*/
|
|
96
|
+
perByte(bytes, carbonIntensity = GLOBAL_INTENSITY) {
|
|
97
|
+
const energyBycomponent = this.energyPerByteByComponent(bytes);
|
|
98
|
+
|
|
99
|
+
// when faced with falsy values, fallback to global intensity
|
|
100
|
+
if (Boolean(carbonIntensity) === false) {
|
|
101
|
+
carbonIntensity = GLOBAL_INTENSITY;
|
|
102
|
+
}
|
|
103
|
+
// if we have a boolean, we have a green result from the green web checker
|
|
104
|
+
// use the renewables intensity
|
|
105
|
+
if (carbonIntensity === true) {
|
|
106
|
+
carbonIntensity = RENEWABLES_INTENSITY;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// otherwise when faced with non numeric values throw an error
|
|
110
|
+
if (typeof carbonIntensity !== "number") {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`perByte expects a numeric value or boolean for the carbon intensity value. Received: ${carbonIntensity}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const co2ValuesbyComponent = this.co2byComponent(
|
|
117
|
+
energyBycomponent,
|
|
118
|
+
carbonIntensity
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// pull out our values…
|
|
122
|
+
const co2Values = Object.values(co2ValuesbyComponent);
|
|
123
|
+
|
|
124
|
+
// so we can return their sum
|
|
125
|
+
return co2Values.reduce(
|
|
126
|
+
(prevValue, currentValue) => prevValue + currentValue
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Accept a figure for bytes transferred and return the number of kilowatt hours used
|
|
132
|
+
* by the total system for this data transfer
|
|
133
|
+
*
|
|
134
|
+
* @param {number} bytes
|
|
135
|
+
* @return {number} the number of kilowatt hours used
|
|
136
|
+
*/
|
|
137
|
+
energyPerByte(bytes) {
|
|
138
|
+
const energyByComponent = this.energyPerByteByComponent(bytes);
|
|
139
|
+
|
|
140
|
+
// pull out our values…
|
|
141
|
+
const energyValues = Object.values(energyByComponent);
|
|
142
|
+
|
|
143
|
+
// so we can return their sum
|
|
144
|
+
return energyValues.reduce(
|
|
145
|
+
(prevValue, currentValue) => prevValue + currentValue
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Accept a figure for bytes transferred, and return an object containing figures
|
|
151
|
+
* per system component, with the caching assumptions applied. This tries to account
|
|
152
|
+
* for webpages being loaded from a cache by browsers, so if you had a thousand page views,
|
|
153
|
+
* and tried to work out the energy per visit, the numbers would reflect the reduced amounts
|
|
154
|
+
* of transfer.
|
|
155
|
+
*
|
|
156
|
+
* @param {number} bytes - the data transferred in bytes for loading a webpage
|
|
157
|
+
* @param {number} firstView - what percentage of visits are loading this page for the first time
|
|
158
|
+
* @param {number} returnView - what percentage of visits are loading this page for subsequent times
|
|
159
|
+
* @param {number} dataReloadRatio - what percentage of a page is reloaded on each subsequent page view
|
|
160
|
+
*
|
|
161
|
+
* @return {object} Object containing the energy in kilowatt hours, keyed by system component
|
|
162
|
+
*/
|
|
163
|
+
energyPerVisitByComponent(
|
|
164
|
+
bytes,
|
|
165
|
+
firstView = FIRST_TIME_VIEWING_PERCENTAGE,
|
|
166
|
+
returnView = RETURNING_VISITOR_PERCENTAGE,
|
|
167
|
+
dataReloadRatio = PERCENTAGE_OF_DATA_LOADED_ON_SUBSEQUENT_LOAD
|
|
168
|
+
) {
|
|
169
|
+
const energyBycomponent = this.energyPerByteByComponent(bytes);
|
|
170
|
+
const cacheAdjustedSegmentEnergy = {};
|
|
171
|
+
|
|
172
|
+
for (const [key, value] of Object.entries(energyBycomponent)) {
|
|
173
|
+
// represent the first load
|
|
174
|
+
cacheAdjustedSegmentEnergy[key] = value * firstView;
|
|
175
|
+
|
|
176
|
+
// then represent the subsequent load
|
|
177
|
+
cacheAdjustedSegmentEnergy[key] += value * returnView * dataReloadRatio;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return cacheAdjustedSegmentEnergy;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Accept a figure for bytes, and return the total figure for energy per visit
|
|
185
|
+
* using the default caching assumptions for loading a single website
|
|
186
|
+
*
|
|
187
|
+
* @param {number} bytes
|
|
188
|
+
* @return {number} the total energy use for the visit, after applying the caching assumptions
|
|
189
|
+
*/
|
|
190
|
+
energyPerVisit(bytes) {
|
|
191
|
+
// fetch the values using the default caching assumptions
|
|
192
|
+
const energyValues = Object.values(this.energyPerVisitByComponent(bytes));
|
|
193
|
+
|
|
194
|
+
// return the summed of the values to return our single number
|
|
195
|
+
return energyValues.reduce(
|
|
196
|
+
(prevValue, currentValue) => prevValue + currentValue
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// TODO: this method looks like it applies the carbon intensity
|
|
201
|
+
// change to the *entire* system, not just the datacenter.
|
|
202
|
+
emissionsPerVisitInGrams(energyPerVisit, carbonintensity = GLOBAL_INTENSITY) {
|
|
203
|
+
return formatNumber(energyPerVisit * carbonintensity);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
annualEnergyInKwh(energyPerVisit, monthlyVisitors = 1000) {
|
|
207
|
+
return energyPerVisit * monthlyVisitors * 12;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
annualEmissionsInGrams(co2grams, monthlyVisitors = 1000) {
|
|
211
|
+
return co2grams * monthlyVisitors * 12;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
annualSegmentEnergy(annualEnergy) {
|
|
215
|
+
return {
|
|
216
|
+
consumerDeviceEnergy: formatNumber(annualEnergy * END_USER_DEVICE_ENERGY),
|
|
217
|
+
networkEnergy: formatNumber(annualEnergy * NETWORK_ENERGY),
|
|
218
|
+
dataCenterEnergy: formatNumber(annualEnergy * DATACENTER_ENERGY),
|
|
219
|
+
productionEnergy: formatNumber(annualEnergy * PRODUCTION_ENERGY),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
module.exports = SustainableWebDesign;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const SustainableWebDesign = require("./sustainable-web-design");
|
|
2
|
+
|
|
3
|
+
describe("sustainable web design model", () => {
|
|
4
|
+
const swd = new SustainableWebDesign();
|
|
5
|
+
const averageWebsiteInBytes = 2257715.2;
|
|
6
|
+
|
|
7
|
+
describe("energyPerByteByComponent", () => {
|
|
8
|
+
it("should return a object with numbers for each system component", () => {
|
|
9
|
+
const groupedEnergy = swd.energyPerByteByComponent(averageWebsiteInBytes);
|
|
10
|
+
|
|
11
|
+
expect(groupedEnergy.consumerDeviceEnergy).toBeCloseTo(0.00088564, 8);
|
|
12
|
+
expect(groupedEnergy.networkEnergy).toBeCloseTo(0.00023844, 8);
|
|
13
|
+
expect(groupedEnergy.productionEnergy).toBeCloseTo(0.0003236, 8);
|
|
14
|
+
expect(groupedEnergy.dataCenterEnergy).toBeCloseTo(0.00025547, 8);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("energyPerByte", () => {
|
|
19
|
+
it("should return a number in kilowatt hours for the given data transfer in bytes", () => {
|
|
20
|
+
const energyForTransfer = swd.energyPerByte(averageWebsiteInBytes);
|
|
21
|
+
expect(energyForTransfer).toBeCloseTo(0.00170316, 7);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("perByte", () => {
|
|
26
|
+
it("should return a single number for CO2 emissions", () => {
|
|
27
|
+
expect(typeof swd.perByte(2257715.2)).toBe("number");
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("energyPerVisit", function () {
|
|
32
|
+
it("should return a number", () => {
|
|
33
|
+
expect(typeof swd.energyPerVisit(averageWebsiteInBytes)).toBe("number");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should calculate the correct energy", () => {
|
|
37
|
+
expect(swd.energyPerVisit(averageWebsiteInBytes)).toBe(
|
|
38
|
+
0.0004513362121582032
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("emissionsPerVisitInGrams", function () {
|
|
44
|
+
it("should calculate the correct co2 per visit", () => {
|
|
45
|
+
const averageWebsiteInBytes = 2257715.2;
|
|
46
|
+
const energy = swd.energyPerVisit(averageWebsiteInBytes);
|
|
47
|
+
expect(swd.emissionsPerVisitInGrams(energy)).toEqual(0.2);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should accept a dynamic KwH value", () => {
|
|
51
|
+
const averageWebsiteInBytes = 2257715.2;
|
|
52
|
+
const energy = swd.energyPerVisit(averageWebsiteInBytes);
|
|
53
|
+
expect(swd.emissionsPerVisitInGrams(energy, 245)).toEqual(0.11);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("annualEnergyInKwh", function () {
|
|
58
|
+
it("should calculate the correct energy in kWh", () => {
|
|
59
|
+
expect(swd.annualEnergyInKwh(averageWebsiteInBytes)).toBe(27092582400);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("annualEmissionsInGrams", function () {
|
|
64
|
+
it("should calculate the corrent energy in grams", () => {
|
|
65
|
+
expect(swd.annualEmissionsInGrams(averageWebsiteInBytes)).toBe(
|
|
66
|
+
27092582400
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("annualSegmentEnergy", function () {
|
|
72
|
+
it("should return the correct values", () => {
|
|
73
|
+
expect(swd.annualSegmentEnergy(averageWebsiteInBytes)).toEqual({
|
|
74
|
+
consumerDeviceEnergy: 1174011.9,
|
|
75
|
+
dataCenterEnergy: 338657.28,
|
|
76
|
+
networkEnergy: 316080.13,
|
|
77
|
+
productionEnergy: 428965.89,
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|