diff --git a/manifests/examples/builtins/sci-embodied/failure-invalid-default-emission-value.yml b/manifests/examples/builtins/sci-embodied/failure-invalid-default-emission-value.yml deleted file mode 100644 index 503300f84..000000000 --- a/manifests/examples/builtins/sci-embodied/failure-invalid-default-emission-value.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: sci-embodied -description: failure with `defaults.device/emissions-embodied` being string instead of number -tags: -initialize: - plugins: - "sci-embodied": # a model that calculates m from te, tir, el, rr and rtor - method: SciEmbodied - path: "builtin" -tree: - children: - child: - pipeline: - compute: - - sci-embodied # duration & config -> embodied - defaults: - device/emissions-embodied: "fail" # gCO2eq - time-reserved: 3600 # 1hr in seconds - device/expected-lifespan: 94608000 # 3 years in seconds - resources-reserved: 1 - resources-total: 8 - inputs: - - timestamp: 2023-07-06T00:00 - duration: 3600 diff --git a/manifests/examples/builtins/sci-embodied/failure-missing-expected-lifespan.yml b/manifests/examples/builtins/sci-embodied/failure-missing-expected-lifespan.yml deleted file mode 100644 index 8fd3e5784..000000000 --- a/manifests/examples/builtins/sci-embodied/failure-missing-expected-lifespan.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: sci-embodied -description: missing device/expected-lifespan -tags: -initialize: - plugins: - "sci-embodied": # a model that calculates m from te, tir, el, rr and rtor - method: SciEmbodied - path: "builtin" -tree: - children: - child: - pipeline: - compute: - - sci-embodied # duration & config -> embodied - defaults: - device/emissions-embodied: 1533.120 # gCO2eq - time-reserved: 3600 # 1hr in seconds - #device/expected-lifespan: 94608000 # 3 years in seconds - resources-reserved: 1 - resources-total: 8 - inputs: - - timestamp: 2023-07-06T00:00 - duration: 3600 diff --git a/manifests/examples/builtins/sci-embodied/scenario-1.yml b/manifests/examples/builtins/sci-embodied/scenario-1.yml new file mode 100644 index 000000000..57bfdd5cf --- /dev/null +++ b/manifests/examples/builtins/sci-embodied/scenario-1.yml @@ -0,0 +1,27 @@ +name: embodied-carbon demo +description: +tags: +aggregation: + metrics: + - embodied-carbon + type: "both" +initialize: + plugins: + embodied-carbon: + method: SciEmbodied + path: builtin + config: + output-parameter: "embodied-carbon" +tree: + children: + child: + pipeline: + compute: + - embodied-carbon + inputs: + - timestamp: 2023-08-06T00:00 + duration: 3600 + hdd: 2 + - timestamp: 2023-08-06T10:00 + duration: 3600 + hdd: 2 diff --git a/manifests/examples/builtins/sci-embodied/scenario-2.yml b/manifests/examples/builtins/sci-embodied/scenario-2.yml new file mode 100644 index 000000000..d4c2640ff --- /dev/null +++ b/manifests/examples/builtins/sci-embodied/scenario-2.yml @@ -0,0 +1,37 @@ +name: embodied-carbon demo +description: +tags: +initialize: + plugins: + embodied-carbon: + method: SciEmbodied + path: builtin + config: + baseline-vcpus: 1 + baseline-memory: 16 + lifespan: 157680000 + baseline-emissions: 2000000 + vcpu-emissions-constant: 100000 + memory-emissions-constant: 1172 + ssd-emissions-constant: 50000 + hdd-emissions-constant: 100000 + gpu-emissions-constant: 150000 + output-parameter: "embodied-carbon" +tree: + children: + child: + pipeline: + compute: + - embodied-carbon + defaults: + vCPUs: 4 + memory: 32 + ssd: 1 + hdd: 1 + gpu: 1 + total-vcpus: 16 + inputs: + - timestamp: 2023-08-06T00:00 + duration: 3600 + - timestamp: 2023-08-06T10:00 + duration: 3600 diff --git a/manifests/examples/builtins/sci-embodied/success.yml b/manifests/examples/builtins/sci-embodied/success.yml index 991569404..c37677931 100644 --- a/manifests/examples/builtins/sci-embodied/success.yml +++ b/manifests/examples/builtins/sci-embodied/success.yml @@ -3,7 +3,16 @@ description: successful path tags: initialize: plugins: - "sci-embodied": # a model that calculates m from te, tir, el, rr and rtor + "csv-lookup": + path: builtin + method: CSVLookup + config: + filepath: >- + https://raw.githubusercontent.com/Green-Software-Foundation/if-data/main/cloud-metdata-azure-instances.csv + query: + instance-class: cloud/instance-type + output: ["cpu-cores-utilized", "vcpus-allocated"] + "sci-embodied-new": # a model that calculates m from te, tir, el, rr and rtor method: SciEmbodied path: "builtin" tree: @@ -11,6 +20,7 @@ tree: child: pipeline: compute: + - csv-lookup - sci-embodied # duration & config -> embodied defaults: device/emissions-embodied: 1533.120 # gCO2eq diff --git a/package-lock.json b/package-lock.json index 663d3b063..ca145548d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@commitlint/cli": "^18.6.0", "@commitlint/config-conventional": "^18.6.0", - "@grnsft/if-core": "^0.0.18", + "@grnsft/if-core": "^0.0.20", "axios": "^1.7.2", "csv-parse": "^5.5.6", "csv-stringify": "^6.4.6", @@ -1186,9 +1186,9 @@ } }, "node_modules/@grnsft/if-core": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/@grnsft/if-core/-/if-core-0.0.18.tgz", - "integrity": "sha512-fZVGADUFf+9CQm+upGwGbQ5zlj7mndxZJ/Bfx2V/8+diU//xvK2vACC/7a1M0pHHhP8tKbfgdGopwWUI3POzcw==", + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@grnsft/if-core/-/if-core-0.0.20.tgz", + "integrity": "sha512-D5P97E0O9qIw9Ok0qCcy3zmNl8j1sr/vwfPsueFzkbmF6ZnLDBL1vapn8MzSRFsp3lJoH/mStDZ3uf3+S1L17g==", "dependencies": { "typescript": "^5.1.6", "zod": "^3.23.8" diff --git a/package.json b/package.json index 63ab8bf07..802c9f138 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "dependencies": { "@commitlint/cli": "^18.6.0", "@commitlint/config-conventional": "^18.6.0", - "@grnsft/if-core": "^0.0.18", + "@grnsft/if-core": "^0.0.20", "axios": "^1.7.2", "csv-parse": "^5.5.6", "csv-stringify": "^6.4.6", @@ -86,7 +86,6 @@ "lint": "gts lint", "pre-commit": "lint-staged", "prepare": "husky install", - "prepublish": "npm run build", "release": "release-it", "test": "jest --verbose --testPathPattern=src/__tests__/" }, diff --git a/src/__tests__/if-run/builtins/sci-embodied.test.ts b/src/__tests__/if-run/builtins/sci-embodied.test.ts index 40670a821..9014a0982 100644 --- a/src/__tests__/if-run/builtins/sci-embodied.test.ts +++ b/src/__tests__/if-run/builtins/sci-embodied.test.ts @@ -2,10 +2,7 @@ import {ERRORS} from '@grnsft/if-core/utils'; import {SciEmbodied} from '../../../if-run/builtins/sci-embodied'; -import {STRINGS} from '../../../if-run/config'; - const {InputValidationError} = ERRORS; -const {SCI_EMBODIED_ERROR} = STRINGS; describe('builtins/sci-embodied:', () => { describe('SciEmbodied: ', () => { @@ -13,7 +10,7 @@ describe('builtins/sci-embodied:', () => { inputs: {}, outputs: {}, }; - const sciEmbodied = SciEmbodied(undefined, parametersMetadata, {}); + const sciEmbodied = SciEmbodied({}, parametersMetadata, {}); describe('init: ', () => { it('successfully initalized.', () => { @@ -27,19 +24,13 @@ describe('builtins/sci-embodied:', () => { const inputs = [ { timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 1, - 'resources-total': 1, + duration: 3600, + vCPUs: 2, }, { timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30 * 2, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 1, - 'resources-total': 1, + duration: 3600, + vCPUs: 4, }, ]; @@ -50,46 +41,33 @@ describe('builtins/sci-embodied:', () => { expect(result).toStrictEqual([ { timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 1, - 'resources-total': 1, - 'carbon-embodied': 4.10958904109589, + duration: 3600, + vCPUs: 2, + 'embodied-carbon': 31.39269406392694, }, { timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30 * 2, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 1, - 'resources-total': 1, - 'carbon-embodied': 4.10958904109589 * 2, + duration: 3600, + vCPUs: 4, + 'embodied-carbon': 37.10045662100457, }, ]); }); it('executes when `mapping` has valid data.', async () => { const mapping = { - 'device/emissions-embodied': 'device/carbon-footprint', + vCPUs: 'device/cpu-cores', }; - const sciEmbodied = SciEmbodied(undefined, parametersMetadata, mapping); + const sciEmbodied = SciEmbodied({}, parametersMetadata, mapping); const inputs = [ { timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30, - 'device/carbon-footprint': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 1, - 'resources-total': 1, + duration: 3600, }, { timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30 * 2, - 'device/carbon-footprint': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 1, - 'resources-total': 1, + duration: 3600, + 'device/cpu-cores': 2, }, ]; @@ -100,175 +78,32 @@ describe('builtins/sci-embodied:', () => { expect(result).toStrictEqual([ { timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30, - 'device/carbon-footprint': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 1, - 'resources-total': 1, - 'carbon-embodied': 4.10958904109589, + duration: 3600, + 'embodied-carbon': 28.538812785388128, }, { timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30 * 2, - 'device/carbon-footprint': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 1, - 'resources-total': 1, - 'carbon-embodied': 4.10958904109589 * 2, + duration: 3600, + 'device/cpu-cores': 2, + 'embodied-carbon': 31.39269406392694, }, ]); }); it('executes when the `mapping` maps output parameter.', async () => { const mapping = { - 'carbon-embodied': 'carbon', + 'embodied-carbon': 'carbon', }; - const sciEmbodied = SciEmbodied(undefined, parametersMetadata, mapping); - const inputs = [ - { - timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 1, - 'resources-total': 1, - }, - { - timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30 * 2, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 1, - 'resources-total': 1, - }, - ]; - - const result = await sciEmbodied.execute(inputs); - - expect.assertions(1); - - expect(result).toStrictEqual([ - { - timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 1, - 'resources-total': 1, - carbon: 4.10958904109589, - }, - { - timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30 * 2, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 1, - 'resources-total': 1, - carbon: 4.10958904109589 * 2, - }, - ]); - }); - - it('returns a result when `vcpus-allocated` and `vcpus-total` are in the input.', async () => { - const inputs = [ - { - timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'vcpus-allocated': 1, - 'vcpus-total': 1, - }, - ]; - - const result = await sciEmbodied.execute(inputs); - - expect.assertions(1); - - expect(result).toStrictEqual([ - { - timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'vcpus-allocated': 1, - 'vcpus-total': 1, - 'carbon-embodied': 4.10958904109589, - }, - ]); - }); - - it('returns a result when `vcpus-allocated` and `vcpus-total` are preferred to `resources-reserved` and `resources-total`.', async () => { - const inputs = [ - { - timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 2, - 'resources-total': 2, - 'vcpus-allocated': 1, - 'vcpus-total': 1, - }, - { - timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30 * 2, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'vcpus-allocated': 1, - 'vcpus-total': 1, - 'resources-reserved': 2, - 'resources-total': 2, - }, - ]; - - const result = await sciEmbodied.execute(inputs); - - expect.assertions(1); - - expect(result).toStrictEqual([ - { - timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'vcpus-allocated': 1, - 'vcpus-total': 1, - 'carbon-embodied': 4.10958904109589, - 'resources-reserved': 2, - 'resources-total': 2, - }, - { - timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30 * 2, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'vcpus-allocated': 1, - 'vcpus-total': 1, - 'carbon-embodied': 4.10958904109589 * 2, - 'resources-reserved': 2, - 'resources-total': 2, - }, - ]); - }); - - it('returns a result when `vcpus-allocated` and `vcpus-total` are miised.', async () => { + const sciEmbodied = SciEmbodied({}, parametersMetadata, mapping); const inputs = [ { timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 1, - 'resources-total': 1, + duration: 3600, }, { timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30 * 2, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 1, - 'resources-total': 1, + duration: 3600, + 'device/cpu-cores': 2, }, ]; @@ -279,42 +114,29 @@ describe('builtins/sci-embodied:', () => { expect(result).toStrictEqual([ { timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'carbon-embodied': 4.10958904109589, - 'resources-reserved': 1, - 'resources-total': 1, + duration: 3600, + carbon: 28.538812785388128, }, { timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30 * 2, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'carbon-embodied': 4.10958904109589 * 2, - 'resources-reserved': 1, - 'resources-total': 1, + duration: 3600, + 'device/cpu-cores': 2, + carbon: 28.538812785388128, }, ]); }); - it('throws an error when `device/emissions-embodied` is string.', async () => { + it('throws an error when `vCPUs` is string.', async () => { const inputs = [ { timestamp: '2021-01-01T00:00:00Z', duration: 60 * 60 * 24 * 30, - 'device/emissions-embodied': '10,00', - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 1, - 'resources-total': 1, + vCPUs: 'string', }, { timestamp: '2021-01-01T00:00:00Z', duration: 60 * 60 * 24 * 30 * 2, - 'device/emissions-embodied': 200, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'resources-reserved': 1, - 'resources-total': 1, + vCPUs: 'string', }, ]; @@ -324,87 +146,12 @@ describe('builtins/sci-embodied:', () => { } catch (error) { expect(error).toStrictEqual( new InputValidationError( - `"device/emissions-embodied" parameter is ${SCI_EMBODIED_ERROR( - 'gco2e' - )}. Error code: invalid_union.` + '"vCPUs" parameter is expected number, received string. Error code: invalid_type.' ) ); expect(error).toBeInstanceOf(InputValidationError); } }); - - it('throws an exception on missing `device/emissions-embodied`.', async () => { - const errorMessage = - '"device/emissions-embodied" parameter is required. Error code: invalid_union.'; - const inputs = [ - { - timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30, - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, - 'vcpus-allocated': 1, - 'vcpus-total': 1, - }, - ]; - - expect.assertions(2); - - try { - await sciEmbodied.execute(inputs); - } catch (error) { - expect(error).toStrictEqual(new InputValidationError(errorMessage)); - expect(error).toBeInstanceOf(InputValidationError); - } - }); - - it('throws an exception on missing `device/expected-lifespan`.', async () => { - const errorMessage = - '"device/expected-lifespan" parameter is required. Error code: invalid_union.'; - const inputs = [ - { - timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30, - 'device/emissions-embodied': 200, - 'vcpus-allocated': 1, - 'vcpus-total': 1, - }, - ]; - - expect.assertions(2); - - try { - await sciEmbodied.execute(inputs); - } catch (error) { - expect(error).toStrictEqual(new InputValidationError(errorMessage)); - expect(error).toBeInstanceOf(InputValidationError); - } - }); - - it('throws an exception on invalid values.', async () => { - const inputs = [ - { - timestamp: '2021-01-01T00:00:00Z', - duration: 60 * 60 * 24 * 30, - 'device/emissions-embodied': '200', - 'vcpus-allocated': true, - 'vcpus-total': 1, - }, - ]; - - expect.assertions(2); - - try { - await sciEmbodied.execute(inputs); - } catch (error) { - expect(error).toBeInstanceOf(InputValidationError); - expect(error).toStrictEqual( - new InputValidationError( - `"device/emissions-embodied" parameter is ${SCI_EMBODIED_ERROR( - 'gco2e' - )}. Error code: invalid_union.` - ) - ); - } - }); }); }); }); diff --git a/src/__tests__/if-run/builtins/time-sync.test.ts b/src/__tests__/if-run/builtins/time-sync.test.ts index b3350fbe0..0bfc59b5a 100644 --- a/src/__tests__/if-run/builtins/time-sync.test.ts +++ b/src/__tests__/if-run/builtins/time-sync.test.ts @@ -1,3 +1,4 @@ +import {AGGREGATION_METHODS} from '@grnsft/if-core/consts'; import {ERRORS} from '@grnsft/if-core/utils'; import {Settings, DateTime} from 'luxon'; @@ -7,7 +8,6 @@ import {storeAggregationMetrics} from '../../../if-run/lib/aggregate'; import {TimeSync} from '../../../if-run/builtins/time-sync'; import {STRINGS} from '../../../if-run/config'; -import {AGGREGATION_METHODS} from '../../../if-run/types/aggregation'; Settings.defaultZone = 'utc'; const { @@ -463,10 +463,12 @@ describe('builtins/time-sync:', () => { { timestamp: '2023-12-12T00:00:00.000Z', duration: 1, + 'cpu/utilization': null, }, { timestamp: '2023-12-12T00:00:01.000Z', duration: 1, + 'cpu/utilization': null, }, ]; @@ -576,10 +578,12 @@ describe('builtins/time-sync:', () => { { timestamp: '2023-12-12T00:00:00.000Z', duration: 5, + 'resources-total': null, }, { timestamp: '2023-12-12T00:00:05.000Z', duration: 5, + 'resources-total': null, }, ]; @@ -739,12 +743,12 @@ describe('builtins/time-sync:', () => { { timestamp: '2023-12-12T00:00:00.000Z', duration: 5, - 'resources-total': 10, + 'resources-total': null, }, { timestamp: '2023-12-12T00:00:05.000Z', duration: 5, - 'resources-total': 10, + 'resources-total': null, }, ]; diff --git a/src/__tests__/if-run/lib/aggregate.test.ts b/src/__tests__/if-run/lib/aggregate.test.ts index 00d9c6d0e..d053fea9f 100644 --- a/src/__tests__/if-run/lib/aggregate.test.ts +++ b/src/__tests__/if-run/lib/aggregate.test.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ +import {AGGREGATION_METHODS} from '@grnsft/if-core/consts'; import {AggregationParams} from '../../../common/types/manifest'; @@ -6,7 +7,6 @@ import { aggregate, storeAggregationMetrics, } from '../../../if-run/lib/aggregate'; -import {AGGREGATION_METHODS} from '../../../if-run/types/aggregation'; describe('lib/aggregate: ', () => { beforeAll(() => { @@ -22,6 +22,10 @@ describe('lib/aggregate: ', () => { }); describe('aggregate(): ', () => { + beforeAll(() => { + storeAggregationMetrics({carbon: 'sum'}); + }); + it('returns tree if aggregation is missing.', () => { const tree = {}; const aggregation = undefined; diff --git a/src/__tests__/if-run/lib/environment.test.ts b/src/__tests__/if-run/lib/environment.test.ts index 63127fd5a..b7b6ab60b 100644 --- a/src/__tests__/if-run/lib/environment.test.ts +++ b/src/__tests__/if-run/lib/environment.test.ts @@ -2,7 +2,7 @@ import {injectEnvironment} from '../../../if-run/lib/environment'; -describe.skip('lib/envirnoment: ', () => { +describe('lib/environment: ', () => { describe('injectEnvironment(): ', () => { const context = {}; diff --git a/src/__tests__/if-run/util/aggregation-helper.test.ts b/src/__tests__/if-run/util/aggregation-helper.test.ts index f83536f39..d76fcbb94 100644 --- a/src/__tests__/if-run/util/aggregation-helper.test.ts +++ b/src/__tests__/if-run/util/aggregation-helper.test.ts @@ -1,13 +1,11 @@ +import {AGGREGATION_METHODS} from '@grnsft/if-core/consts'; import {ERRORS} from '@grnsft/if-core/utils'; import {PluginParams} from '@grnsft/if-core/types'; import {AggregationParams} from '../../../common/types/manifest'; import {aggregateInputsIntoOne} from '../../../if-run/util/aggregation-helper'; -import { - AGGREGATION_METHODS, - AggregationMetric, -} from '../../../if-run/types/aggregation'; +import {AggregationMetric} from '../../../if-run/types/aggregation'; import {storeAggregationMetrics} from '../../../if-run/lib/aggregate'; import {STRINGS} from '../../../if-run/config'; @@ -25,6 +23,7 @@ describe('util/aggregation-helper: ', () => { [metric]: AGGREGATION_METHODS[2], })); storeAggregationMetrics(...convertedMetrics); + storeAggregationMetrics({carbon: 'sum'}); }); describe('aggregateInputsIntoOne(): ', () => { @@ -47,6 +46,7 @@ describe('util/aggregation-helper: ', () => { }); it('passes `timestamp`, `duration` to aggregator if aggregation is temporal.', () => { + storeAggregationMetrics({carbon: 'sum'}); const inputs: PluginParams[] = [ {timestamp: '', duration: 10, carbon: 10}, {timestamp: '', duration: 10, carbon: 20}, diff --git a/src/common/types/manifest.ts b/src/common/types/manifest.ts index 1efab523d..488d8eeb5 100644 --- a/src/common/types/manifest.ts +++ b/src/common/types/manifest.ts @@ -1,6 +1,5 @@ import {z} from 'zod'; - -import {AggregationMethodTypes} from '../../if-run/types/aggregation'; +import {AggregationMethodTypes} from '@grnsft/if-core/types'; import {manifestSchema} from '../util/validations'; diff --git a/src/common/util/debug-logger.ts b/src/common/util/debug-logger.ts index 1a9a614d9..488903c96 100644 --- a/src/common/util/debug-logger.ts +++ b/src/common/util/debug-logger.ts @@ -99,7 +99,7 @@ const debugLog = (level: LogLevel, args: any[], debugMode: boolean) => { return; } - if (args[0].includes('# start')) { + if (typeof args[0] === 'string' && args[0].includes('# start')) { originalConsole.log(...args); return; } diff --git a/src/if-run/builtins/sci-embodied/README.md b/src/if-run/builtins/sci-embodied/README.md index 8933de211..a40072278 100644 --- a/src/if-run/builtins/sci-embodied/README.md +++ b/src/if-run/builtins/sci-embodied/README.md @@ -2,28 +2,30 @@ Software systems cause emissions through the hardware that they operate on, both through the energy that the physical hardware consumes and the emissions associated with manufacturing the hardware. Embodied carbon refers to the carbon emitted during the manufacture and eventual disposal of a component. It is added to the operational carbon (carbon emitted when a component is used) to give an overall SCI score. -Read more on [embodied carbon](https://github.com/Green-Software-Foundation/sci/blob/main/Software_Carbon_Intensity/Software_Carbon_Intensity_Specification.md#embodied-emissions) +Read more on [embodied carbon](https://github.com/Green-Software-Foundation/sci/blob/main/Software_Carbon_Intensity/Software_Carbon_Intensity_Specification.md#embodied-emissions). + +Our plugin follows the Cloud Carbon Footprint methodology for calculating embodied carbon and extends it to scale down the total embodied carbon for a piece of hardware by the portion of it that should be allocated to a particular application, using a `usage-ratio` and `time`. The `usage-ratio` is a term that can be used to scale by, for example, the storage you actually use on a shared server, rather than the total storage available for that hardware, or the time you are active compared to the hardware lifespan. -## Parameters -### Plugin config +## Parameters -Not Needed +### Plugin Configuration -### Plugin parameter metadata +The `SciEmbodied` plugin requires a configuration object and parameter metadata (optional) to do the calculation. -The `parameter-metadata` section contains information about `description`, `unit` and `aggregation-method` of the parameters of the inputs and outputs +### Plugin Parameter Metadata -- `inputs`: describe the parameters of the `inputs`. Each parameter has: +The `parameter-metadata` section contains information about the `description`, `unit`, and `aggregation-method` of the input and output parameters. - - `description`: description of the parameter - - `unit`: unit of the parameter - - `aggregation-method`: aggregation method of the parameter (it can be `sum`, `avg` or `none`) +- `inputs`: Describes the parameters for the input data. Each parameter includes: + - `description`: A brief description of the parameter. + - `unit`: The unit of measurement for the parameter. + - `aggregation-method`: The method used to aggregate this parameter (`sum`, `avg`, or `none`). -- `outputs`: describe the `carbon-embodied` parameter. The parameter has the following attributes: - - `description`: description of the parameter - - `unit`: unit of the parameter - - `aggregation-method`: aggregation method of the parameter (it can be `sum`, `avg` or `none`) +- `outputs`: Describes the `carbon-embodied` parameter, which includes: + - `description`: A brief description of the parameter. + - `unit`: The unit of measurement for the parameter. + - `aggregation-method`: The method used to aggregate this parameter (`sum`, `avg`, or `none`). ### Mapping @@ -37,75 +39,77 @@ sci-embodied: 'parameter-name-in-the-plugin': 'parameter-name-in-the-input' ``` -### Inputs - -- `device/emissions-embodied`: the sum of Life Cycle Assessment (LCA) emissions for the component -- `device/expected-lifespan`: the length of time, in seconds, between a component's manufacture and its disposal -- `resources-reserved`: the number of resources reserved for use by the software -- `resources-total`: the total number of resources available -- `duration`: the amount of time covered by an observation, in this context it is used as the share of the total life span of the hardware reserved for use by an application, in seconds. - -> Note that if you have a plugin pipeline that adds `vcpus-allocated` and `vcpus-total` to each observation, such as the `cloud-metadata` plugin, those values will be used **in preference** to the given `resources-reserved` and `resources-total` fields. +### Config -## Returns +`baseline-vcpus`: the number of CPUs to use in the baseline server, defaults tothe CCF value of 1, +`baseline-memory`: the amount of memory to use in the baseline server, defaults tothe CCF value of 16, +`baseline-emissions`: the embodied carbon assumed to represent a baseline server, in g +`lifespan`: the lifespan of the device, in seconds. Defaults to 4 years (126144000 seconds), +`time`: the time to consider when scaling the total device embodied carbon, if not given defaults to `duration` +`vcpu-emissions-constant`: emissions for a CPU in gCO2e. Defaults tothe CCF value (100000), +`memory-emissions-constant`: value used in calculating emissions due to memory, defaults to the CCf value of 533/384 +`ssd-emissions-constant`: emissions for a SSD in gCO2e. Defaults tothe CCF value (50000), +`hdd-emissions-constant`: emissions for a CPU in gCO2e. Defaults tothe CCF value (100000), +`gpu-emissions-constant`: emissions for a GPU in gCO2e. Defaults tothe CCF value (150000), +`output-parameter`: name to give the output value, defaults to `embodied-carbon` -- `carbon-embodied`: the carbon emitted in manufacturing and disposing of a component, in gCO2eq +Note that if you do not provide any config at all, we will fallback to defaults for everything, equivalent to setting the baseline server equal to the CCF version, which has 1000000g of embodied emissions. -## Calculation +### Inputs -To calculate the embodied carbon, `m` for a software application, use the equation: +- `vCPUs`: number of CPUs available on device +- `memory`: amount of RAM available on device, in GB +- `ssd`: number of SSD drives mounted on device +- `hdd`: number of HDD drives mounted on device +- `gpu`: number of GPUs available on device +- `duration`: The length of time the hardware is reserved for use by the software, in seconds. +- `time`: the time to use for scalign the total embodied carbon per timestap, if you do not want to use `duration` +- `usage-ratio`: the ratio by which to scale down the total embodied carbon according to your usage, e.g. for a shared storage server the total storage divided by your actual storage. -``` -m = te * ts * rs -``` +Note that if you do not provide any inputs at all, we fall back to defaults that are equivalent to using the full resources of the baseline server, scaled only by `duration`. -Where: +### Outputs -- `device/emissions-embodied` = Total embodied emissions; the sum of Life Cycle Assessment (LCA) emissions for the component. +- `carbon-embodied`: The total embodied emissions for the component, measured in gCO2e, per timestep. -- `timeShare` = Time-share; the share of the total life span of the hardware reserved for use by an application. +## Calculation - - `timeShare` is calculated as `duration/'device/expected-lifespan'`, where: - - `duration` = the length of time the hardware is reserved for use by the software. - - `device/expected-lifespan` = Expected lifespan: the length of time, in seconds, between a component's manufacture and its disposal. +The plugin calculates the total embodied carbon emissions using the following steps: -- `resourceShare` = Resource-share; the share of the total available resources of the hardware reserved for use by an application. - - `resourceShare` is calculated as `resources-reserved/resources-total`, where: - - `resources-reserved` = Resources reserved; the number of resources reserved for use by the software. - - `resources-total` = Total Resources; the total number of resources available. + - CPU emissions (`cpuE`) are calculated based on the difference between allocated vCPUs and baseline vCPUs. + - Memory emissions (`memoryE`) are calculated based on the difference between allocated memory and baseline memory. + - Emissions for HDD, SSD, and GPU are also calculated based on their respective differences from baseline values. + - The total embodied emissions are calculated by summing the baseline emissions with the above components scaling by the usage ratio and time. ## Implementation -IF implements the plugin based on the logic described above. To run the plugin, you must first create an instance of `SciEmbodied`. Then, you can call `execute()` to return `m`. - -## Usage - -The following snippet demonstrates how to call the `sci-embodied` plugin from Typescript. +The plugin can be instantiated and executed as follows: ```typescript import {SciEmbodied} from 'builtins'; -const parametersMetadata = {inputs: {}, outputs: {}}; -const mapping = {}; -const sciEmbodied = SciEmbodied(undefined, parametersMetadata, mapping); +const sciEmbodied = SciEmbodied(config, parametersMetadata, {}); const results = await sciEmbodied.execute([ { - 'device/emissions-embodied': 200, // in gCO2e for total resource units - duration: 60 * 60 * 24 * 30, // time reserved in seconds, can point to another field "duration" - 'device/expected-lifespan': 60 * 60 * 24 * 365 * 4, // lifespan in seconds (4 years) - 'resources-reserved': 1, // resource units reserved / used - 'resources-total': 1, // total resource units available + duration: 3600, // time reserved in seconds + vCPUs: 2, // allocated vCPUs + memory: 32, // allocated memory in GB + ssd: 100, // allocated SSD storage in GB + hdd: 1000, // allocated HDD storage in GB + gpu: 1, // allocated GPUs }, ]); + +console.log(results); ``` -## Example manifest +# Example Manifest IF users will typically call the plugin as part of a pipeline defined in a `manifest` file. In this case, instantiating the plugin is handled by `if-run` and does not have to be done explicitly by the user. The following is an example `manifest` that calls `sci-embodied`: ```yaml name: sci-embodied -description: simple demo invoking sci-embodied +description: demo invoking sci-embodied tags: initialize: plugins: @@ -119,34 +123,15 @@ tree: child: pipeline: compute: - - sci-embodied # duration & config -> embodied - defaults: - device/carbon-footprint: 1533.120 # gCO2eq - device/expected-lifespan: 3 # 3 years in seconds - resources-reserved: 1 - resources-total: 8 + - sci-embodied inputs: - - timestamp: 2023-07-06T00:00 + - timestamp: 2024-08-19T00:00 duration: 3600 ``` -You can run this example `manifest` by executing the following command from the project root: +To run this example manifest, use the following command: -```sh +```bash npm i -g @grnsft/if -if-run --manifest manifests/plugins/sci-embodied.yml --output manifests/outputs/sci-embodied.yml +if-run --manifest manifests/plugins/sci-embodied/success.yml --output manifests/outputs/success.yml ``` - -The results will be saved to a new `yaml` file in `./examples/outputs`. - -## Errors - -`SciEmbodied` uses one of IF's error classes - -### `SciEmbodiedError` - -This error class is used to describe a problem with one of the input values to `sci-embodied`. This is typically due to an incorrect type or a reference to a value that is not available. - -You will receive a specific error message explaining which parameter is problematic, and you can check and replace where appropriate. - -For more information on our error classes, please visit [our docs](https://if.greensoftware.foundation/reference/errors) diff --git a/src/if-run/builtins/sci-embodied/index.ts b/src/if-run/builtins/sci-embodied/index.ts index c6c953b50..2b3c4ccfa 100644 --- a/src/if-run/builtins/sci-embodied/index.ts +++ b/src/if-run/builtins/sci-embodied/index.ts @@ -1,24 +1,23 @@ -import {z} from 'zod'; +import {z, ZodType} from 'zod'; + import { + mapConfigIfNeeded, mapInputIfNeeded, mapOutputIfNeeded, } from '@grnsft/if-core/utils/helpers'; import { ExecutePlugin, + ConfigParams, ParameterMetadata, MappingParams, PluginParametersMetadata, PluginParams, } from '@grnsft/if-core/types'; -import {validate, allDefined} from '../../../common/util/validations'; - -import {STRINGS} from '../../config'; - -const {SCI_EMBODIED_ERROR} = STRINGS; +import {validate} from '../../../common/util/validations'; export const SciEmbodied = ( - _config: undefined, + config: ConfigParams = {}, parametersMetadata: PluginParametersMetadata, mapping: MappingParams ): ExecutePlugin => { @@ -26,165 +25,149 @@ export const SciEmbodied = ( kind: 'execute', inputs: { ...({ - 'device/emissions-embodied': { - description: 'total embodied emissions of some component', - unit: 'gCO2e', - 'aggregation-method': 'sum', + vCPUs: { + description: 'number of CPUs allocated to an application', + unit: 'CPUs', + 'aggregation-method': 'copy', }, - 'device/expected-lifespan': { - description: 'Total Expected Lifespan of the Component in Seconds', - unit: 'seconds', - 'aggregation-method': 'sum', + memory: { + description: 'RAM available for a resource, in GB', + unit: 'GB', + 'aggregation-method': 'copy', }, - 'resources-reserved': { - description: 'resources reserved for an application', - unit: 'count', - 'aggregation-method': 'none', + ssd: { + description: 'number of SSDs available for a resource', + unit: 'SSDs', + 'aggregation-method': 'copy', }, - 'resources-total': { - description: 'total resources available', - unit: 'count', - 'aggregation-method': 'none', + hdd: { + description: 'number of HDDs available for a resource', + unit: 'HDDs', + 'aggregation-method': 'copy', }, - 'vcpus-allocated': { - description: 'number of vcpus allocated to particular resource', - unit: 'count', - 'aggregation-method': 'none', + gpu: { + description: 'number of GPUs available for a resource', + unit: 'GPUs', + 'aggregation-method': 'copy', }, - 'vcpus-total': { + 'usage-ratio': { description: - 'total number of vcpus available on a particular resource', - unit: 'count', - 'aggregation-method': 'none', + 'a scaling factor that can be used to describe the ratio of actual resource usage comapred to real device usage, e.g. 0.25 if you are using 2 out of 8 vCPUs, 0.1 if you are responsible for 1 out of 10 GB of storage, etc', + unit: 'dimensionless', + 'aggregation-method': 'copy', + }, + time: { + description: + 'a time unit to scale the embodied carbon by, in seconds. If not provided,time defaults to the value of the timestep duration.', + unit: 'seconds', + 'aggregation-method': 'copy', }, } as ParameterMetadata), ...parametersMetadata?.inputs, }, outputs: parametersMetadata?.outputs || { - 'carbon-embodied': { - description: 'embodied emissions of the component', + 'embodied-carbon': { + description: 'embodied carbon for a resource, scaled by usage', unit: 'gCO2e', 'aggregation-method': 'sum', }, }, }; - const METRICS = [ - 'device/emissions-embodied', - 'device/expected-lifespan', - 'resources-reserved', - 'vcpus-allocated', - 'resources-total', - 'vcpus-total', - ]; - /** - * Calculate the Embodied carbon for a list of inputs. + * Checks for required fields in input. */ - const execute = (inputs: PluginParams[]) => - inputs.map(input => { - const mappedInput = mapInputIfNeeded(input, mapping); - const safeInput = validateInput(mappedInput); - - const result = { - ...input, - 'carbon-embodied': calculateEmbodiedCarbon(safeInput), - }; - - return mapOutputIfNeeded(result, mapping); + const validateConfig = () => { + const schema = z.object({ + 'baseline-vcpus': z.number().gte(0).default(1), + 'baseline-memory': z.number().gte(0).default(16), + 'baseline-emissions': z.number().gte(0).default(1000000), + lifespan: z.number().gt(0).default(126144000), + 'vcpu-emissions-constant': z.number().gte(0).default(100000), + 'memory-emissions-constant': z + .number() + .gte(0) + .default(533 / 384), + 'ssd-emissions-constant': z.number().gte(0).default(50000), + 'hdd-emissions-constant': z.number().gte(0).default(100000), + 'gpu-emissions-constant': z.number().gte(0).default(150000), + 'output-parameter': z.string().optional(), }); - /** - * Calculate the Embodied carbon for the input. - * M = totalEmissions * (duration/ExpectedLifespan) * (resourcesReserved/totalResources) - */ - const calculateEmbodiedCarbon = (input: PluginParams) => { - const totalEmissions = input['device/emissions-embodied']; - const duration = input['duration']; - const expectedLifespan = input['device/expected-lifespan']; - const resourcesReserved = - input['vcpus-allocated'] || input['resources-reserved']; - const totalResources = input['vcpus-total'] || input['resources-total']; - - return ( - totalEmissions * - (duration / expectedLifespan) * - (resourcesReserved / totalResources) + const mappedConfig = mapConfigIfNeeded(config, mapping); + + return validate>( + schema as ZodType, + mappedConfig ); }; /** - * Checks for required fields in input. + * Validates single observation for safe calculation. */ const validateInput = (input: PluginParams) => { - const commonSchemaPart = (errorMessage: (unit: string) => string) => ({ - 'device/emissions-embodied': z - .number({ - invalid_type_error: errorMessage('gCO2e'), - }) - .gte(0) - .min(0), - 'device/expected-lifespan': z - .number({ - invalid_type_error: errorMessage('gCO2e'), - }) - .gte(0) - .min(0), - duration: z - .number({ - invalid_type_error: errorMessage('seconds'), - }) - .gte(1), + const schema = z.object({ + duration: z.number().gt(0), + vCPUs: z.number().gt(0).default(1), + memory: z.number().gt(0).default(16), + ssd: z.number().gte(0).default(0), + hdd: z.number().gte(0).default(0), + gpu: z.number().gte(0).default(0), + 'usage-ratio': z.number().gt(0).default(1), + time: z.number().gt(0).optional(), }); - const vcpusSchemaPart = { - 'vcpus-allocated': z - .number({ - invalid_type_error: SCI_EMBODIED_ERROR('count'), - }) - .gte(0) - .min(0), - 'vcpus-total': z - .number({ - invalid_type_error: SCI_EMBODIED_ERROR('count'), - }) - .gte(0) - .min(0), - }; - - const resourcesSchemaPart = { - 'resources-reserved': z - .number({ - invalid_type_error: SCI_EMBODIED_ERROR('count'), - }) - .gte(0) - .min(0), - 'resources-total': z - .number({ - invalid_type_error: SCI_EMBODIED_ERROR('count'), - }) - .gte(0) - .min(0), - }; + return validate>(schema as ZodType, input); + }; - const schemaWithVcpus = z.object({ - ...commonSchemaPart(SCI_EMBODIED_ERROR), - ...vcpusSchemaPart, - }); - const schemaWithResources = z.object({ - ...commonSchemaPart(SCI_EMBODIED_ERROR), - ...resourcesSchemaPart, - }); + /** + * 1. Validates configuration and assigns defaults values if not provided. + * 2. Maps through observations and validates them. + * 3. Calculates total embodied carbon by substracting and the difference between baseline server and given one. + */ + const execute = (inputs: PluginParams[]) => { + const safeConfig = validateConfig(); - const schema = schemaWithVcpus.or(schemaWithResources).refine(allDefined, { - message: `All ${METRICS} should be present.`, - }); + return inputs.map(input => { + const mappedInput = mapInputIfNeeded(input, mapping); + const safeInput = validateInput(mappedInput); - return validate>(schema, input); + const cpuE = + (safeInput.vCPUs - safeConfig['baseline-vcpus']) * + safeConfig['vcpu-emissions-constant']; + const memoryE = + (safeInput.memory - safeConfig['baseline-memory']) * + ((safeConfig['memory-emissions-constant'] * + safeConfig['baseline-memory']) / + 16) * + 1000; + const hddE = safeInput.hdd * safeConfig['hdd-emissions-constant']; + const gpuE = safeInput.gpu * safeConfig['gpu-emissions-constant']; + const ssdE = safeInput.ssd * safeConfig['ssd-emissions-constant']; + const time = safeInput['time'] || safeInput.duration; + + const totalEmbodied = + safeConfig['baseline-emissions'] + cpuE + memoryE + ssdE + hddE + gpuE; + + const totalEmbodiedScaledByUsage = + totalEmbodied * safeInput['usage-ratio']; + + const totalEmbodiedScaledByUsageAndTime = + totalEmbodiedScaledByUsage * (time / safeConfig['lifespan']); + + const embodiedCarbonKey = + safeConfig['output-parameter'] || 'embodied-carbon'; + const result = { + ...input, + [embodiedCarbonKey]: totalEmbodiedScaledByUsageAndTime, + }; + + return mapOutputIfNeeded(result, mapping); + }); }; return { - metadata, execute, + metadata, }; }; diff --git a/src/if-run/builtins/time-sync/index.ts b/src/if-run/builtins/time-sync/index.ts index f72cae045..d85f08c32 100644 --- a/src/if-run/builtins/time-sync/index.ts +++ b/src/if-run/builtins/time-sync/index.ts @@ -265,6 +265,12 @@ export const TimeSync = ( return acc; } + if (method === 'none') { + acc[key] = null; + + return acc; + } + acc[key] = method === 'sum' ? convertPerInterval(input[key], input['duration']) @@ -310,13 +316,22 @@ export const TimeSync = ( const method = getAggregationMethod(metric); + if (method === 'none') { + acc[metric] = null; + + return acc; + } + if (method === 'avg' || method === 'sum') { acc[metric] = 0; return acc; } - acc[metric] = input[metric]; + if (method === 'copy') { + acc[metric] = input[metric]; + return acc; + } return acc; }, {} as PluginParams); @@ -379,7 +394,8 @@ export const TimeSync = ( method = 'sum'; } - if (!method) { + if (method === 'none') { + acc[metric] = null; return; } @@ -391,7 +407,7 @@ export const TimeSync = ( return; } - if (method === 'none') { + if (method === 'copy') { acc[metric] = input[metric]; return; diff --git a/src/if-run/config/strings.ts b/src/if-run/config/strings.ts index 82208f621..e549c3b72 100644 --- a/src/if-run/config/strings.ts +++ b/src/if-run/config/strings.ts @@ -97,8 +97,6 @@ https://if.greensoftware.foundation/major-concepts/manifest-file`, '`functional-unit` value is missing from input data or it is not a positive integer', REGEX_MISMATCH: (input: any, match: string) => `\`${input}\` does not match the ${match} regex expression`, - SCI_EMBODIED_ERROR: (unit: string) => - `invalid number. please provide it as \`${unit}\` to input`, MISSING_MIN_MAX: 'Config is missing min or max value', INVALID_MIN_MAX: (name: string) => `Min value should not be greater than or equal to max value of ${name}`, diff --git a/src/if-run/index.ts b/src/if-run/index.ts index 6d9f61a78..5607df6b6 100644 --- a/src/if-run/index.ts +++ b/src/if-run/index.ts @@ -1,4 +1,6 @@ #!/usr/bin/env node +import {AGGREGATION_METHODS} from '@grnsft/if-core/consts'; + import {STRINGS as COMMON_STRINGS} from '../common/config'; import {validateManifest} from '../common/util/validations'; import {debugLogger} from '../common/util/debug-logger'; @@ -12,8 +14,6 @@ import {compute} from './lib/compute'; import {exhaust} from './lib/exhaust'; import {explain} from './lib/explain'; -import {AGGREGATION_METHODS} from './types/aggregation'; - import {parseIfRunProcessArgs} from './util/args'; import {andHandle} from './util/helpers'; diff --git a/src/if-run/lib/aggregate.ts b/src/if-run/lib/aggregate.ts index cd19b00e9..43b84fcf6 100644 --- a/src/if-run/lib/aggregate.ts +++ b/src/if-run/lib/aggregate.ts @@ -1,3 +1,4 @@ +import {AGGREGATION_METHODS} from '@grnsft/if-core/consts'; import {PluginParams} from '@grnsft/if-core/types'; import {debugLogger} from '../../common/util/debug-logger'; @@ -158,5 +159,5 @@ export const getAggregationMethod = (unitName: string) => { memoizedLog(logger.warn, UNKNOWN_PARAM(unitName)); - return undefined; + return AGGREGATION_METHODS[2]; }; diff --git a/src/if-run/types/aggregation.ts b/src/if-run/types/aggregation.ts index c3b143a1f..5315b298f 100644 --- a/src/if-run/types/aggregation.ts +++ b/src/if-run/types/aggregation.ts @@ -1,7 +1,7 @@ +import {AggregationMethodTypes} from '@grnsft/if-core/types'; + export type AggregationResult = Record; export const AGGREGATION_TYPES = ['horizontal', 'vertical', 'both'] as const; -export const AGGREGATION_METHODS = ['sum', 'avg', 'none'] as const; -export type AggregationMethodTypes = 'sum' | 'avg' | 'none'; export type AggregationMetric = Record; diff --git a/src/if-run/util/aggregation-helper.ts b/src/if-run/util/aggregation-helper.ts index e8fe63de5..e573720b1 100644 --- a/src/if-run/util/aggregation-helper.ts +++ b/src/if-run/util/aggregation-helper.ts @@ -12,8 +12,8 @@ const {METRIC_MISSING} = STRINGS; const {AGGREGATION_ADDITIONAL_PARAMS} = CONFIG; /** - * Aggregates child node level metrics. Validates if metric aggregation type is `none`, then rejects with error. - * Appends aggregation additional params to metrics. Otherwise iterates over inputs by aggregating per given `metrics`. + * Aggregates child node level metrics. Appends aggregation additional params to metrics. + * Otherwise iterates over inputs by aggregating per given `metrics`. */ export const aggregateInputsIntoOne = ( inputs: PluginParams[], @@ -37,7 +37,12 @@ export const aggregateInputsIntoOne = ( } else { const method = getAggregationMethod(metric); - if (!method) { + if (method === 'none') { + return acc; + } + + if (method === 'copy') { + acc[metric] = input[metric]; return acc; }