Skip to content

Commit

Permalink
fix tests, clean up comments, update per-visit types
Browse files Browse the repository at this point in the history
  • Loading branch information
sfishel18 committed Mar 24, 2024
1 parent d3fe9ce commit 8e66245
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 33 deletions.
1 change: 1 addition & 0 deletions __mocks__/https.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const https = jest.createMockFromModule("https");
import { Stream } from "stream";

const stream = new Stream();
stream.statusCode = 200;

https.get.mockImplementation((url, options, callback) => {
url, { headers: getApiRequestHeaders("TestRunner") }, callback(stream);
Expand Down
12 changes: 5 additions & 7 deletions src/co2.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { parseOptions, toTotalCO2 } from "./helpers/index.js";
class CO2 {
/**
* @param {object} options
* @param {'1byte' | 'swd'=} options.model
* @param {'segment'=} options.results
* @param {'1byte' | 'swd'=} options.model The model to use (OneByte or Sustainable Web Design)
* @param {'segment'=} options.results Optional. Whether to return segment-level emissions estimates.
*/
constructor(options = {}) {
this.model = new SustainableWebDesign();
Expand Down Expand Up @@ -53,7 +53,7 @@ class CO2 {
*
* @param {number} bytes
* @param {boolean} green
* @return {number | CO2ByComponentWithTotal} the amount of CO2 in grammes
* @return {number | AdjustedCO2ByComponentWithTotal} the amount of CO2 in grammes
*/
perVisit(bytes, green = false) {
if ("perVisit" in this.model) {
Expand Down Expand Up @@ -126,9 +126,7 @@ class CO2 {
}

return {
co2: toTotalCO2(
this.model.perVisit(bytes, green, this._segment, adjustments)
),
co2: this.model.perVisit(bytes, green, this._segment, adjustments),
green,
variables: {
description:
Expand Down Expand Up @@ -221,6 +219,7 @@ class CO2 {
/** @type {Record<string, Omit<CO2PerContentType, 'type'>>} */
const co2PerContentType = {};
for (let asset of pageXray.assets) {
// TODO (simon) check that this `domain` -> `host` conversion is correct
const domain = new URL(asset.url).host;
const transferSize = asset.transferSize;
const co2ForTransfer = toTotalCO2(
Expand Down Expand Up @@ -276,7 +275,6 @@ class CO2 {
/**
* @param {PageXRay} pageXray
* @param {string[]=} greenDomains
* @returns
*/
perParty(pageXray, greenDomains) {
let firstParty = 0;
Expand Down
1 change: 1 addition & 0 deletions src/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ function parseOptions(options) {
}
adjustments.gridIntensity["device"] = {
country: device.country,
// TODO (simon) check that parseFloat can be safely removed here
value: averageIntensity.data[device.country?.toUpperCase()],
};
} else if (typeof device === "number") {
Expand Down
10 changes: 5 additions & 5 deletions src/hosting-json.node.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ const gunzip = promisify(zlib.gunzip);
/**
* Converts a readable stream to a string.
* @param {fs.ReadStream} stream - The readable stream to convert.
* @returns {Promise<string>} A promise that resolves to the string representation of the stream.
* @returns {Promise<Buffer>} A promise that resolves to the string representation of the stream.
*/
async function streamToString(stream) {
async function streamToBuffer(stream) {
return new Promise((resolve, reject) => {
/** @type {Buffer[]} */
const chunks = [];
stream.on("error", reject);
stream.on("data", (chunk) =>
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
chunks.push(chunk instanceof Buffer ? chunk : Buffer.from(chunk))
);
stream.on("end", () => resolve(Buffer.concat(chunks).toString()));
stream.on("end", () => resolve(Buffer.concat(chunks)));
});
}

Expand All @@ -31,7 +31,7 @@ async function streamToString(stream) {
*/
async function getGzippedFileAsJson(jsonPath) {
const readStream = fs.createReadStream(jsonPath);
const text = await streamToString(readStream);
const text = await streamToBuffer(readStream);
const unzipped = await gunzip(text);
return unzipped.toString();
}
Expand Down
48 changes: 34 additions & 14 deletions src/sustainable-web-design.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,13 @@ class SustainableWebDesign {
* Accept an object keys by the different system components, and
* return an object with the co2 figures key by the each component
*
* @param {EnergyByComponent} energyByComponent - energy grouped by the four system components
* @template {AdjustedEnergyByComponent | EnergyByComponent} EnergyObject
* @template [CO2Object=EnergyObject extends AdjustedEnergyByComponent ? AdjustedCO2ByComponent : CO2ByComponent]
* @param {EnergyObject} energyByComponent - energy grouped by the four system components
* // TODO (simon) check on this type for carbonIntensity
* @param {(number | boolean)=} carbonIntensity - carbon intensity to apply to the datacentre values
* @param {ModelAdjustments=} options - carbon intensity to apply to the datacentre values
* @return {CO2ByComponent} the total number in grams of CO2 equivalent emissions
* @return {CO2Object} the total number in grams of CO2 equivalent emissions
*/
co2byComponent(
energyByComponent,
Expand Down Expand Up @@ -84,14 +87,28 @@ class SustainableWebDesign {
dataCenterCarbonIntensity = RENEWABLES_GRID_INTENSITY;
}

return {
dataCenterCO2:
energyByComponent.dataCenterEnergy * dataCenterCarbonIntensity,
consumerDeviceCO2:
energyByComponent.consumerDeviceEnergy * deviceCarbonIntensity,
networkCO2: energyByComponent.networkEnergy * networkCarbonIntensity,
productionCO2: energyByComponent.productionEnergy * globalEmissions,
};
/** @type {Record<string, number>} */
const returnCO2ByComponent = {};
for (const [key, value] of Object.entries(energyByComponent)) {
// we update the datacentre, as that's what we have information
// about.
if (key.startsWith("dataCenterEnergy")) {
returnCO2ByComponent[key.replace("Energy", "CO2")] =
value * dataCenterCarbonIntensity;
} else if (key.startsWith("consumerDeviceEnergy")) {
returnCO2ByComponent[key.replace("Energy", "CO2")] =
value * deviceCarbonIntensity;
} else if (key.startsWith("networkEnergy")) {
returnCO2ByComponent[key.replace("Energy", "CO2")] =
value * networkCarbonIntensity;
} else {
// Use the global intensity for the remaining segments
returnCO2ByComponent[key.replace("Energy", "CO2")] =
value * globalEmissions;
}
}

return /** @type {CO2Object} */ (returnCO2ByComponent);
}

/**
Expand Down Expand Up @@ -152,15 +169,16 @@ class SustainableWebDesign {
* @param {boolean} carbonIntensity - a boolean indicating whether the data center is green or not
* @param {boolean} segmentResults - a boolean indicating whether to return the results broken down by component
* @param {ModelAdjustments=} options - an object containing the grid intensity and first/return visitor values
* @return {number | CO2ByComponentWithTotal} the total number in grams of CO2 equivalent emissions, or an object containing the breakdown by component
* @return {number | AdjustedCO2ByComponentWithTotal} the total number in grams of CO2 equivalent emissions, or an object containing the breakdown by component
*/
perVisit(
bytes,
carbonIntensity = false,
segmentResults = false,
options = undefined
) {
const energyBycomponent = this.energyPerByteByComponent(bytes);
// TODO (simon) figure out if this method call is correct
const energyBycomponent = this.energyPerVisitByComponent(bytes, options);

if (typeof carbonIntensity !== "boolean") {
// otherwise when faced with non numeric values throw an error
Expand Down Expand Up @@ -221,7 +239,7 @@ class SustainableWebDesign {
* @param {number=} returnView - what percentage of visits are loading this page for subsequent times
* @param {number=} dataReloadRatio - what percentage of a page is reloaded on each subsequent page view
*
* @return {AdjustedEnergyComponent} Object containing the energy in kilowatt hours, keyed by system component
* @return {AdjustedEnergyByComponent} Object containing the energy in kilowatt hours, keyed by system component
*/
energyPerVisitByComponent(
bytes,
Expand Down Expand Up @@ -259,7 +277,9 @@ class SustainableWebDesign {
value * returnView * dataReloadRatio;
}

return /** @type {AdjustedEnergyComponent} */ (cacheAdjustedSegmentEnergy);
return /** @type {AdjustedEnergyByComponent} */ (
cacheAdjustedSegmentEnergy
);
}

/**
Expand Down
28 changes: 21 additions & 7 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
* Or, an object, which contains a key of country and a value that is an Alpha-3 ISO country code.
*
* @typedef ModelOptions
* @property {ModelOptionsGridIntensity=} gridIntensity
* @property {number=} dataReloadRatio
* @property {number=} returnVisitPercentage
* @property {number=} firstVisitPercentage
* @property {ModelOptionsGridIntensity=} gridIntensity Segment-level description of the grid carbon intensity.
* @property {number=} dataReloadRatio A number between 0 and 1 representing the percentage of data that is downloaded by return visitors.
* @property {number=} returnVisitPercentage A number between 0 and 1 representing the percentage of returning visitors.
* @property {number=} firstVisitPercentage A number between 0 and 1 representing the percentage of new visitors.
*
* @typedef ModelAdjustmentSegment
* @property {number} value
Expand Down Expand Up @@ -55,7 +55,7 @@
* @property {TraceResultVariables} variables - The variables used to calculate the CO2 estimate
*
* @typedef CO2EstimateTraceResultPerVisit
* @property {number} co2 - The CO2 estimate in grams/kilowatt-hour
* @property {number | AdjustedCO2ByComponentWithTotal} co2 - The CO2 estimate in grams/kilowatt-hour
* @property {boolean} green - Whether the domain is green or not
* @property {TraceResultVariables} variables - The variables used to calculate the CO2 estimate
*
Expand All @@ -81,22 +81,36 @@
* @property {number} productionEnergy
* @property {number} dataCenterEnergy
*
* @typedef {Object} AdjustedEnergyComponent
* @type {{ [K in keyof EnergyByComponent as `${K} - first`]: EnergyByComponent[K] } & { [K in keyof EnergyByComponent as `${K} - subsequest`]: EnergyByComponent[K] }}
* @typedef {Object} AdjustedEnergyByComponent
* @type {{
* [K in keyof EnergyByComponent as `${K} - first`]: EnergyByComponent[K]
* } & {
* [K in keyof EnergyByComponent as `${K} - subsequest`]: EnergyByComponent[K]
* }}
*
* @typedef CO2ByComponent
* @property {number} consumerDeviceCO2
* @property {number} networkCO2
* @property {number} productionCO2
* @property {number} dataCenterCO2
*
* @typedef {Object} AdjustedCO2ByComponent
* @type {{
* [K in keyof CO2ByComponent as `${K} - first`]: CO2ByComponent[K]
* } & {
* [K in keyof CO2ByComponent as `${K} - subsequest`]: CO2ByComponent[K]
* }}
*
* @typedef CO2ByComponentWithTotal
* @property {number} consumerDeviceCO2
* @property {number} networkCO2
* @property {number} productionCO2
* @property {number} dataCenterCO2
* @property {number} total
*
* @typedef {Object} AdjustedCO2ByComponentWithTotal
* @type {AdjustedCO2ByComponent & { total: number }}
*
* @typedef PageXRayDomain
* @property {number} transferSize
*
Expand Down

0 comments on commit 8e66245

Please sign in to comment.