From 5211c51d0dca7d4d68e6448742d051106f34d734 Mon Sep 17 00:00:00 2001 From: Peter Hedenskog Date: Thu, 21 Dec 2023 05:47:06 +0100 Subject: [PATCH] Move functionality from co2 to sitespeed.io. (#4034) * Move functionality from co2 to sitespeed.io. See https://github.com/thegreenwebfoundation/co2.js/issues/182 * lint --- lib/plugins/sustainable/helper.js | 104 ++++++++++++++++++++++++++++++ lib/plugins/sustainable/index.js | 23 +++++-- npm-shrinkwrap.json | 14 ++-- package.json | 2 +- 4 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 lib/plugins/sustainable/helper.js diff --git a/lib/plugins/sustainable/helper.js b/lib/plugins/sustainable/helper.js new file mode 100644 index 0000000000..8a07164b5e --- /dev/null +++ b/lib/plugins/sustainable/helper.js @@ -0,0 +1,104 @@ +// Moved from the co2.js project +// see https://github.com/thegreenwebfoundation/co2.js/issues/182 + +export function perDomain(pageXray, greenDomains, CO2) { + const co2PerDomain = []; + for (let domain of Object.keys(pageXray.domains)) { + let co2; + co2 = + greenDomains && greenDomains.includes(domain) + ? CO2.perByte(pageXray.domains[domain].transferSize, true) + : CO2.perByte(pageXray.domains[domain].transferSize); + co2PerDomain.push({ + domain, + co2, + transferSize: pageXray.domains[domain].transferSize + }); + } + co2PerDomain.sort((a, b) => b.co2 - a.co2); + + return co2PerDomain; +} + +export function perPage(pageXray, green, CO2) { + // Accept an xray object, and if we receive a boolean as the second + // argument, we assume every request we make is sent to a server + // running on renwewable power. + + // if we receive an array of domains, return a number accounting the + // reduced CO2 from green hosted domains + + const domainCO2 = perDomain(pageXray, green, CO2); + let totalCO2 = 0; + for (let domain of domainCO2) { + totalCO2 += domain.co2; + } + return totalCO2; +} + +export function perContentType(pageXray, greenDomains, CO2) { + const co2PerContentType = {}; + for (let asset of pageXray.assets) { + const domain = new URL(asset.url).domain; + const transferSize = asset.transferSize; + const co2ForTransfer = CO2.perByte( + transferSize, + greenDomains && greenDomains.includes(domain) + ); + const contentType = asset.type; + if (!co2PerContentType[contentType]) { + co2PerContentType[contentType] = { co2: 0, transferSize: 0 }; + } + co2PerContentType[contentType].co2 += co2ForTransfer; + co2PerContentType[contentType].transferSize += transferSize; + } + // restructure and sort + const all = []; + for (let type of Object.keys(co2PerContentType)) { + all.push({ + type, + co2: co2PerContentType[type].co2, + transferSize: co2PerContentType[type].transferSize + }); + } + all.sort((a, b) => b.co2 - a.co2); + return all; +} + +export function getDirtiestResources(pageXray, greenDomains, CO2) { + const allAssets = []; + for (let asset of pageXray.assets) { + const domain = new URL(asset.url).domain; + const transferSize = asset.transferSize; + const co2ForTransfer = CO2.perByte( + transferSize, + greenDomains && greenDomains.includes(domain) + ); + allAssets.push({ url: asset.url, co2: co2ForTransfer, transferSize }); + } + allAssets.sort((a, b) => b.co2 - a.co2); + + return allAssets.slice(0, allAssets.length > 10 ? 10 : allAssets.length); +} + +export function perParty(pageXray, greenDomains, CO2) { + let firstParty = 0; + let thirdParty = 0; + // calculate co2 per first/third party + const firstPartyRegEx = pageXray.firstPartyRegEx; + for (let d of Object.keys(pageXray.domains)) { + // eslint-disable-next-line unicorn/prefer-regexp-test + if (d.match(firstPartyRegEx)) { + thirdParty += CO2.perByte( + pageXray.domains[d].transferSize, + greenDomains && greenDomains.includes(d) + ); + } else { + firstParty += CO2.perByte( + pageXray.domains[d].transferSize, + greenDomains && greenDomains.includes(d) + ); + } + } + return { firstParty, thirdParty }; +} diff --git a/lib/plugins/sustainable/index.js b/lib/plugins/sustainable/index.js index 0911befdda..1aca3d7d0a 100644 --- a/lib/plugins/sustainable/index.js +++ b/lib/plugins/sustainable/index.js @@ -8,6 +8,13 @@ import intel from 'intel'; import { co2, hosting } from '@tgwf/co2'; import { SitespeedioPlugin } from '@sitespeed.io/plugin'; import { Aggregator } from './aggregator.js'; +import { + getDirtiestResources, + perParty, + perContentType, + perPage, + perDomain +} from './helper.js'; const fsp = fs.promises; const __dirname = fileURLToPath(new URL('.', import.meta.url)); @@ -129,7 +136,7 @@ export default class SustainablePlugin extends SitespeedioPlugin { } const CO2 = new co2(this.sustainableOptions.model); - const co2PerDomain = CO2.perDomain(message.data, hostingGreenCheck); + const co2PerDomain = perDomain(message.data, hostingGreenCheck, CO2); const baseDomain = message.data.baseDomain; const hostingInfo = { @@ -142,21 +149,23 @@ export default class SustainablePlugin extends SitespeedioPlugin { hostingInfo.green = true; } - const co2PerParty = CO2.perParty(message.data, hostingGreenCheck); + const co2PerParty = perParty(message.data, hostingGreenCheck, CO2); // Fetch the resources with the largest CO2 impact. ie, // the resources to optimise, host somewhere green, or contact // a supplier about - const dirtiestResources = CO2.dirtiestResources( + const dirtiestResources = getDirtiestResources( message.data, - hostingGreenCheck + hostingGreenCheck, + CO2 ); - const co2PerContentType = CO2.perContentType( + const co2PerContentType = perContentType( message.data, - hostingGreenCheck + hostingGreenCheck, + CO2 ); - const co2PerPageView = CO2.perPage(message.data, hostingGreenCheck); + const co2PerPageView = perPage(message.data, hostingGreenCheck, CO2); const totalCO2 = this.sustainableOptions.pageViews ? this.sustainableOptions.pageViews * co2PerPageView diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 7e2374110a..67e90e5f9d 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -12,7 +12,7 @@ "@google-cloud/storage": "6.9.5", "@influxdata/influxdb-client": "1.33.2", "@sitespeed.io/plugin": "0.0.6", - "@tgwf/co2": "0.13.6", + "@tgwf/co2": "0.13.10", "aws-sdk": "2.1327.0", "axe-core": "4.8.2", "browsertime": "19.3.1", @@ -1026,9 +1026,9 @@ } }, "node_modules/@tgwf/co2": { - "version": "0.13.6", - "resolved": "https://registry.npmjs.org/@tgwf/co2/-/co2-0.13.6.tgz", - "integrity": "sha512-uV8y2voiAb9iQwzK+5dTFLNBu5P5/oCyMAvqD3/+X74sMCc/N2+4MhIY9JmpO1gBQykE647dd4l/40GiE7cKSw==", + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/@tgwf/co2/-/co2-0.13.10.tgz", + "integrity": "sha512-iQZMYdLP+l1Kyi03EEE4jAZgzlAA8HRAbfgT3GgN6sUgbSoNS/UHijICUqgkum9aHIkDSOrtwkgbB/x3Gc/cQA==", "engines": { "node": ">=14.0.0" } @@ -10048,9 +10048,9 @@ } }, "@tgwf/co2": { - "version": "0.13.6", - "resolved": "https://registry.npmjs.org/@tgwf/co2/-/co2-0.13.6.tgz", - "integrity": "sha512-uV8y2voiAb9iQwzK+5dTFLNBu5P5/oCyMAvqD3/+X74sMCc/N2+4MhIY9JmpO1gBQykE647dd4l/40GiE7cKSw==" + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/@tgwf/co2/-/co2-0.13.10.tgz", + "integrity": "sha512-iQZMYdLP+l1Kyi03EEE4jAZgzlAA8HRAbfgT3GgN6sUgbSoNS/UHijICUqgkum9aHIkDSOrtwkgbB/x3Gc/cQA==" }, "@tokenizer/token": { "version": "0.3.0", diff --git a/package.json b/package.json index 667c63d38e..e2b8504cd7 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "dependencies": { "@google-cloud/storage": "6.9.5", "@influxdata/influxdb-client": "1.33.2", - "@tgwf/co2": "0.13.6", + "@tgwf/co2": "0.13.10", "aws-sdk": "2.1327.0", "axe-core": "4.8.2", "browsertime": "19.3.1",