diff --git a/package.json b/package.json index 1092d39f0..cc05d8769 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "email": "info@gsf.com" }, "bundledDependencies": [ + "typescript-cubic-spline", "node-fetch", "axios", "typescript", @@ -20,7 +21,8 @@ "axios": "^1.4.0", "node-fetch": "^3.3.1", "ts-sync-request": "^1.4.1", - "typescript": "^5.1.6" + "typescript": "^5.1.6", + "typescript-cubic-spline": "^1.0.1" }, "devDependencies": { "@babel/core": "7.22.10", diff --git a/src/lib/teads-cpu/index.test.ts b/src/lib/teads-cpu/index.test.ts new file mode 100644 index 000000000..557733dd6 --- /dev/null +++ b/src/lib/teads-cpu/index.test.ts @@ -0,0 +1,73 @@ +import {describe, expect, jest, test} from '@jest/globals'; +import {TeadsCPUModel} from './index'; + +jest.setTimeout(30000); + +describe('teads:configure test', () => { + test('initialize with params', async () => { + const impactModel = new TeadsCPUModel(); + await impactModel.configure('test', { + tdp: 200, + }); + await expect( + impactModel.calculate([ + { + duration: 3600, + cpu: 0.5, + datetime: '2021-01-01T00:00:00Z' + }, + ]) + ).resolves.toStrictEqual([ + { + energy: 0.15, + duration: 3600, + cpu: 0.5, + datetime: '2021-01-01T00:00:00Z', + }, + ]); + }); + test('teads:initialize with params', async () => { + const impactModel = new TeadsCPUModel(); + await impactModel.configure('test', { + tdp: 300, + }); + await expect( + impactModel.calculate([ + { + duration: 3600, + cpu: 0.1, + datetime: '2021-01-01T00:00:00Z', + }, + { + duration: 3600, + cpu: 0.5, + datetime: '2021-01-01T00:00:00Z', + }, + { + duration: 3600, + cpu: 1, + datetime: '2021-01-01T00:00:00Z', + }, + ]) + ).resolves.toStrictEqual([ + { + duration: 3600, + cpu: 0.1, + datetime: '2021-01-01T00:00:00Z', + energy: 0.096, + }, + { + duration: 3600, + cpu: 0.5, + datetime: '2021-01-01T00:00:00Z', + energy: 0.225, + }, + { + duration: 3600, + cpu: 1, + datetime: '2021-01-01T00:00:00Z', + energy: 0.306, + }, + ]); + }); +}); diff --git a/src/lib/teads-cpu/index.ts b/src/lib/teads-cpu/index.ts new file mode 100644 index 000000000..53d8f36ea --- /dev/null +++ b/src/lib/teads-cpu/index.ts @@ -0,0 +1,134 @@ +import {IImpactModelInterface} from '../interfaces'; +import Spline from 'typescript-cubic-spline'; +import {KeyValuePair} from '../../types/boavizta'; + +export class TeadsCPUModel implements IImpactModelInterface { + // Defined for compatibility. Not used in TEADS. + authParams: object | undefined; + // name of the data source + name: string | undefined; + // tdp of the chip being measured + tdp: number = 100; + // default power curve provided by the Teads Team + curve: number[] = [0.12, 0.32, 0.75, 1.02]; + // default percentage points + points: number[] = [0, 10, 50, 100]; + // spline interpolation of the power curve + spline: Spline = new Spline(this.points, this.curve); + + /** + * Defined for compatibility. Not used in TEADS. + */ + authenticate(authParams: object): void { + this.authParams = authParams; + } + + /** + * Configures the TEADS Plugin for IEF + * @param {string} name name of the resource + * @param {Object} staticParams static parameters for the resource + * @param {number} staticParams.tdp Thermal Design Power in Watts + */ + async configure( + name: string, + staticParams: object | undefined = undefined + ): Promise { + this.name = name; + + if (staticParams === undefined) { + throw new Error('Required Parameters not provided'); + } + + if ('tdp' in staticParams) { + this.tdp = staticParams?.tdp as number; + } else { + throw new Error('`tdp` Thermal Design Power not provided. Can not compute energy.'); + } + + if ('curve' in staticParams) { + this.curve = staticParams?.curve as number[]; + this.spline = new Spline(this.points, this.curve); + } + + + return this; + } + + /** + * Calculate the total emissions for a list of observations + * + * Each Observation require: + * @param {Object[]} observations ISO 8601 datetime string + * @param {string} observations[].datetime ISO 8601 datetime string + * @param {number} observations[].duration observation duration in seconds + * @param {number} observations[].cpu percentage cpu usage + */ + async calculate( + observations: object | object[] | undefined + ): Promise { + if (observations === undefined) { + throw new Error('Required Parameters not provided'); + } + + const results: KeyValuePair[] = []; + if (Array.isArray(observations)) { + observations.forEach((observation: KeyValuePair) => { + const e = this.calculateEnergy(observation); + results.push({ + energy: e, + ...observation + }); + }); + } + + return results; + } + + + /** + * Calculates the energy consumption for a single observation + * requires + * + * duration: duration of the observation in seconds + * cpu: cpu usage in percentage + * datetime: ISO 8601 datetime string + * + * Uses a spline method on the teads cpu wattage data + */ + private calculateEnergy(observation: KeyValuePair) { + if ( + !('duration' in observation) || + !('cpu' in observation) || + !('datetime' in observation) + ) { + throw new Error( + 'Required Parameters duration,cpu,datetime not provided for observation' + ); + } + + // duration is in seconds + const duration = observation['duration']; + + // convert cpu usage to percentage + const cpu = observation['cpu'] * 100.0; + + const wattage = this.spline.at(cpu) * this.tdp; + // duration is in seconds + // wattage is in watts + // eg: 30W x 300s = 9000 J + // 1 Wh = 3600 J + // 9000 J / 3600 = 2.5 Wh + // J / 3600 = Wh + // 2.5 Wh / 1000 = 0.0025 kWh + // Wh / 1000 = kWh + // (wattage * duration) / (seconds in an hour) / 1000 = kWh + return (wattage * duration) / 3600 / 1000; + } + + /** + * Returns model identifier + */ + modelIdentifier(): string { + return 'teads.cpu'; + } +} diff --git a/yarn.lock b/yarn.lock index c7b1ed89c..6c20197a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5035,6 +5035,11 @@ types-ramda@^0.29.4: dependencies: ts-toolbelt "^9.6.0" +typescript-cubic-spline@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/typescript-cubic-spline/-/typescript-cubic-spline-1.0.1.tgz#340ef0f4f068fa28be0a0c3b4484e11f55b40610" + integrity sha512-h1dvp2YK66CU/p1thrBjQ61/CBSmkZw4Uh28ay8v9UjAA0gQdCQ+Etkie9sdj74WjYuYHEom5qLHjrci1IVMPA== + typescript@^5.1.6, typescript@~5.1.6: version "5.1.6" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274"