From c89d83ac63958b3981df82057edf2dc77e38e9a3 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Fri, 10 May 2024 10:47:11 +0100 Subject: [PATCH 01/14] feat(lib): add readme to builtins/multiply --- src/builtins/multiply/README.md | 95 +++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 src/builtins/multiply/README.md diff --git a/src/builtins/multiply/README.md b/src/builtins/multiply/README.md new file mode 100644 index 000000000..fbbc14e7d --- /dev/null +++ b/src/builtins/multiply/README.md @@ -0,0 +1,95 @@ +# Multiply + +`multiply` is a generic plugin for multiplying two or more values in an `input` array. + +You provide the names of the values you want to multiply, and a name to use to append the product to the output array. + +For example, you could multiply `cpu/energy` and `network/energy` and name the result `energy-product`. `energy-product` would then be added to every observation in your input array as the product of `cpu/energy` and `network/energy`. + +## Parameters + +### Plugin config + +Two parameters are required in global config: `input-parameters` and `output-parameter`. + +`input-parameters`: an array of strings. Each string should match an existing key in the `inputs` array +`output-parameter`: a string defining the name to use to add the product of the input parameters to the output array. + +### Inputs + +All of `input-parameters` must be available in the input array. + +## Returns + +- `output-parameter`: the product of all `input-parameters` with the parameter name defined by `output-parameter` in global config. + +## Calculation + +```pseudocode +output = input0 * input1 * input2 ... inputN +``` + +## Implementation + +To run the plugin, you must first create an instance of `Multiply`. Then, you can call `execute()`. + +```typescript +import {Multiply} from '@grnsft/if-plugins'; + +const config = { + inputParameters: ['cpu/energy', 'network/energy'], + outputParameter: 'energy-product', +}; + +const mult = Multiply(config); +const result = await mult.execute([ + { + duration: 3600, + timestamp: '2021-01-01T00:00:00Z', + 'cpu/energy': 0.001, + 'memory/energy': 0.0005, + }, +]); +``` + +## 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 `ie` and does not have to be done explicitly by the user. The following is an example manifest that calls `multiply`: + +```yaml +name: multiply-demo +description: +tags: +initialize: + outputs: + - yaml + plugins: + multiply: + method: Multiply + path: '@grnsft/if-plugins' + global-config: + input-parameters: ['cpu/energy', 'network/energy'] + output-parameter: 'energy-product' +tree: + children: + child: + pipeline: + - multiply + config: + multiply: + inputs: + - timestamp: 2023-08-06T00:00 + duration: 3600 + cpu/energy: 0.001 + network/energy: 0.001 +``` + +You can run this example by saving it as `./examples/manifests/test/multiply.yml` and executing the following command from the project root: + +```sh +npm i -g @grnsft/if +npm i -g @grnsft/if-plugins +ie --manifest ./examples/manifests/test/multiply.yml --output ./examples/outputs/multiply.yml +``` + +The results will be saved to a new `yaml` file in `./examples/outputs` From 46bb1fe811afd63b33e8a204addd95bc433aaf81 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Fri, 10 May 2024 10:48:43 +0100 Subject: [PATCH 02/14] feat(lib): export multiply from builtins --- src/builtins/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/builtins/index.ts b/src/builtins/index.ts index d227e22e4..bfa82cc35 100644 --- a/src/builtins/index.ts +++ b/src/builtins/index.ts @@ -1,2 +1,3 @@ export {GroupBy} from './group-by'; export {TimeSync} from './time-sync'; +export {Multiply} from './multiply'; From 194b51fec27c52521599cf16c167c2cfe0a48d2f Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Fri, 10 May 2024 10:49:04 +0100 Subject: [PATCH 03/14] feat(lib): move types into if --- src/util/helpers.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/util/helpers.ts b/src/util/helpers.ts index 55698cac6..4fc1f982d 100644 --- a/src/util/helpers.ts +++ b/src/util/helpers.ts @@ -3,11 +3,21 @@ import {promisify} from 'node:util'; import {ERRORS} from './errors'; import {logger} from './logger'; - +import {ErrorFormatParams} from '../types/helpers'; import {STRINGS} from '../config'; const {ISSUE_TEMPLATE} = STRINGS; +/** + * Formats given error according to class instance, scope and message. + */ +export const buildErrorMessage = + (classInstanceName: string) => (params: ErrorFormatParams) => { + const {scope, message} = params; + + return `${classInstanceName}${scope ? `(${scope})` : ''}: ${message}.`; + }; + /** * Impact engine error handler. Logs errors and appends issue template if error is unknown. */ From 9bf98d684cfa7c385fd729c3fa74bd96050efbc9 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Fri, 10 May 2024 10:49:24 +0100 Subject: [PATCH 04/14] feat(lib): add more helpers to if --- src/types/helpers.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/types/helpers.ts diff --git a/src/types/helpers.ts b/src/types/helpers.ts new file mode 100644 index 000000000..ed413174b --- /dev/null +++ b/src/types/helpers.ts @@ -0,0 +1,4 @@ +export type ErrorFormatParams = { + scope?: string; + message: string; +}; From 9656476b81380440a14fccc7b4dab4e165868c48 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Fri, 10 May 2024 10:49:43 +0100 Subject: [PATCH 05/14] feat(lib): move types into if --- src/builtins/multiply/types.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/builtins/multiply/types.ts diff --git a/src/builtins/multiply/types.ts b/src/builtins/multiply/types.ts new file mode 100644 index 000000000..88f4cae49 --- /dev/null +++ b/src/builtins/multiply/types.ts @@ -0,0 +1,4 @@ +export type MultiplyConfig = { + 'input-parameters': string[]; + 'output-parameter': string; +}; From 3ebfa35797e29b79e1b92ecc3312c31ccffd857e Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Fri, 10 May 2024 10:50:02 +0100 Subject: [PATCH 06/14] feat(lib): move plugin code into if --- src/builtins/multiply/index.ts | 87 ++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/builtins/multiply/index.ts diff --git a/src/builtins/multiply/index.ts b/src/builtins/multiply/index.ts new file mode 100644 index 000000000..5666a6093 --- /dev/null +++ b/src/builtins/multiply/index.ts @@ -0,0 +1,87 @@ +import {z} from 'zod'; + +import {buildErrorMessage} from '../../util/helpers'; +import {ERRORS} from '../../util/errors'; +import {validate} from '../../util/validations'; + +import {ExecutePlugin, PluginParams} from '../../types/interface'; +import {MultiplyConfig} from './types'; + +const {InputValidationError} = ERRORS; + +export const Multiply = (globalConfig: MultiplyConfig): ExecutePlugin => { + const errorBuilder = buildErrorMessage(Multiply.name); + const metadata = { + kind: 'execute', + }; + + /** + * Checks global config value are valid. + */ + const validateGlobalConfig = () => { + const globalConfigSchema = z.object({ + 'input-parameters': z.array(z.string()), + 'output-parameter': z.string().min(1), + }); + + return validate>( + globalConfigSchema, + globalConfig + ); + }; + + /** + * Checks for required fields in input. + */ + const validateSingleInput = ( + input: PluginParams, + inputParameters: string[] + ) => { + inputParameters.forEach(metricToMultiply => { + if ( + input[metricToMultiply] === undefined || + isNaN(input[metricToMultiply]) + ) { + throw new InputValidationError( + errorBuilder({ + message: `${metricToMultiply} is missing from the input array`, + }) + ); + } + }); + + return input; + }; + + /** + * Calculate the product of each input parameter. + */ + const execute = (inputs: PluginParams[]): PluginParams[] => { + const safeGlobalConfig = validateGlobalConfig(); + const inputParameters = safeGlobalConfig['input-parameters']; + const outputParameter = safeGlobalConfig['output-parameter']; + + return inputs.map(input => { + const safeInput = validateSingleInput(input, inputParameters); + + return { + ...input, + [outputParameter]: calculateProduct(safeInput, inputParameters), + }; + }); + }; + + /** + * Calculates the product of the energy components. + */ + const calculateProduct = (input: PluginParams, inputParameters: string[]) => + inputParameters.reduce( + (accumulator, metricToMultiply) => accumulator * input[metricToMultiply], + 1 + ); + + return { + metadata, + execute, + }; +}; From 749f7390e4e02117c5cd94b1af1ffd6796f9613f Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Fri, 10 May 2024 10:50:46 +0100 Subject: [PATCH 07/14] test(lib): move unit tests into if --- src/__tests__/unit/builtins/multiply.test.ts | 102 +++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/__tests__/unit/builtins/multiply.test.ts diff --git a/src/__tests__/unit/builtins/multiply.test.ts b/src/__tests__/unit/builtins/multiply.test.ts new file mode 100644 index 000000000..a4451179d --- /dev/null +++ b/src/__tests__/unit/builtins/multiply.test.ts @@ -0,0 +1,102 @@ +import {Multiply} from '../../../builtins/multiply'; + +import {ERRORS} from '../../../util/errors'; + +const {InputValidationError} = ERRORS; + +describe('lib/multiply: ', () => { + describe('Multiply: ', () => { + const globalConfig = { + 'input-parameters': ['cpu/energy', 'network/energy', 'memory/energy'], + 'output-parameter': 'energy', + }; + const multiply = Multiply(globalConfig); + + describe('init: ', () => { + it('successfully initalized.', () => { + expect(multiply).toHaveProperty('metadata'); + expect(multiply).toHaveProperty('execute'); + }); + }); + + describe('execute(): ', () => { + it('successfully applies Multiply strategy to given input.', async () => { + expect.assertions(1); + + const expectedResult = [ + { + duration: 3600, + 'cpu/energy': 2, + 'network/energy': 2, + 'memory/energy': 2, + energy: 8, + timestamp: '2021-01-01T00:00:00Z', + }, + ]; + + const result = await multiply.execute([ + { + duration: 3600, + 'cpu/energy': 2, + 'network/energy': 2, + 'memory/energy': 2, + timestamp: '2021-01-01T00:00:00Z', + }, + ]); + + expect(result).toStrictEqual(expectedResult); + }); + + it('throws an error on missing params in input.', async () => { + const expectedMessage = + 'Multiply: cpu/energy is missing from the input array.'; + + expect.assertions(1); + + try { + await multiply.execute([ + { + duration: 3600, + timestamp: '2021-01-01T00:00:00Z', + }, + ]); + } catch (error) { + expect(error).toStrictEqual( + new InputValidationError(expectedMessage) + ); + } + }); + + it('returns a result with input params not related to energy.', async () => { + expect.assertions(1); + const newConfig = { + 'input-parameters': ['carbon', 'other-carbon'], + 'output-parameter': 'carbon-product', + }; + const multiply = Multiply(newConfig); + + const data = [ + { + duration: 3600, + timestamp: '2021-01-01T00:00:00Z', + carbon: 3, + 'other-carbon': 2, + }, + ]; + const response = await multiply.execute(data); + + const expectedResult = [ + { + duration: 3600, + carbon: 3, + 'other-carbon': 2, + 'carbon-product': 6, + timestamp: '2021-01-01T00:00:00Z', + }, + ]; + + expect(response).toEqual(expectedResult); + }); + }); + }); +}); From 422464889ba9a352af85d00501dc9c48446c1069 Mon Sep 17 00:00:00 2001 From: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> Date: Mon, 20 May 2024 14:21:15 +0100 Subject: [PATCH 08/14] Apply suggestions from code review Signed-off-by: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> --- src/builtins/multiply/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/builtins/multiply/README.md b/src/builtins/multiply/README.md index fbbc14e7d..2c7758a00 100644 --- a/src/builtins/multiply/README.md +++ b/src/builtins/multiply/README.md @@ -34,7 +34,7 @@ output = input0 * input1 * input2 ... inputN To run the plugin, you must first create an instance of `Multiply`. Then, you can call `execute()`. ```typescript -import {Multiply} from '@grnsft/if-plugins'; +import {Multiply} from 'builtins'; const config = { inputParameters: ['cpu/energy', 'network/energy'], @@ -66,7 +66,7 @@ initialize: plugins: multiply: method: Multiply - path: '@grnsft/if-plugins' + path: 'builtins' global-config: input-parameters: ['cpu/energy', 'network/energy'] output-parameter: 'energy-product' From 69057c5571ebb687f46807371af2bca6432de630 Mon Sep 17 00:00:00 2001 From: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> Date: Mon, 20 May 2024 21:01:29 +0100 Subject: [PATCH 09/14] Update src/builtins/multiply/README.md Signed-off-by: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> --- src/builtins/multiply/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/builtins/multiply/README.md b/src/builtins/multiply/README.md index 2c7758a00..602d507a4 100644 --- a/src/builtins/multiply/README.md +++ b/src/builtins/multiply/README.md @@ -88,7 +88,6 @@ You can run this example by saving it as `./examples/manifests/test/multiply.yml ```sh npm i -g @grnsft/if -npm i -g @grnsft/if-plugins ie --manifest ./examples/manifests/test/multiply.yml --output ./examples/outputs/multiply.yml ``` From 97eb4e63d57449d17da988a6f722e5e38481304d Mon Sep 17 00:00:00 2001 From: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> Date: Tue, 21 May 2024 09:20:06 +0100 Subject: [PATCH 10/14] Update src/builtins/multiply/README.md Signed-off-by: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> --- src/builtins/multiply/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builtins/multiply/README.md b/src/builtins/multiply/README.md index 602d507a4..d4e27674c 100644 --- a/src/builtins/multiply/README.md +++ b/src/builtins/multiply/README.md @@ -66,7 +66,7 @@ initialize: plugins: multiply: method: Multiply - path: 'builtins' + path: 'builtin' global-config: input-parameters: ['cpu/energy', 'network/energy'] output-parameter: 'energy-product' From a28ab40561a7997310c0c634bb537a674f7d5613 Mon Sep 17 00:00:00 2001 From: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> Date: Tue, 21 May 2024 09:23:19 +0100 Subject: [PATCH 11/14] Update helpers.ts Signed-off-by: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> --- src/util/helpers.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/util/helpers.ts b/src/util/helpers.ts index ff36d016f..098e093fc 100644 --- a/src/util/helpers.ts +++ b/src/util/helpers.ts @@ -3,7 +3,6 @@ import {promisify} from 'node:util'; import {ErrorFormatParams} from '../types/helpers'; import {ERRORS} from './errors'; import {logger} from './logger'; -import {ErrorFormatParams} from '../types/helpers'; import {STRINGS} from '../config'; const {ISSUE_TEMPLATE} = STRINGS; From aced6e282a74c0352e4ab23546848ecb55455b62 Mon Sep 17 00:00:00 2001 From: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> Date: Tue, 21 May 2024 09:25:38 +0100 Subject: [PATCH 12/14] Update helpers.ts Signed-off-by: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> --- src/util/helpers.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/util/helpers.ts b/src/util/helpers.ts index 098e093fc..400c61ab3 100644 --- a/src/util/helpers.ts +++ b/src/util/helpers.ts @@ -7,16 +7,6 @@ import {STRINGS} from '../config'; const {ISSUE_TEMPLATE} = STRINGS; -/** - * Formats given error according to class instance, scope and message. - */ -export const buildErrorMessage = - (classInstanceName: string) => (params: ErrorFormatParams) => { - const {scope, message} = params; - - return `${classInstanceName}${scope ? `(${scope})` : ''}: ${message}.`; - }; - /** * Impact engine error handler. Logs errors and appends issue template if error is unknown. */ From 473be32262e5c7ca51934c51bda1bbbbe08cf594 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Tue, 21 May 2024 09:27:34 +0100 Subject: [PATCH 13/14] fix(lib): run linter --- src/builtins/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/builtins/index.ts b/src/builtins/index.ts index 12c1a4e9c..061f7b157 100644 --- a/src/builtins/index.ts +++ b/src/builtins/index.ts @@ -2,4 +2,3 @@ export {GroupBy} from './group-by'; export {TimeSync} from './time-sync'; export {Multiply} from './multiply'; export {Sci} from './sci'; - From 2b7d73ed5f3d529d8fc86e98cb99a8ace95f8074 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Wed, 22 May 2024 09:57:55 +0100 Subject: [PATCH 14/14] fix(lib): update import path for Multiply in manifests --- manifests/examples/generics.yml | 2 +- manifests/examples/mock-cpu-util-to-carbon.yml | 2 +- .../plugins/multiply/failure-input-parameter-is-missing.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifests/examples/generics.yml b/manifests/examples/generics.yml index 715d8f03b..3720d6692 100644 --- a/manifests/examples/generics.yml +++ b/manifests/examples/generics.yml @@ -24,7 +24,7 @@ initialize: coefficient: 2 output-parameter: energy-doubled "multiply": - path: "@grnsft/if-plugins" + path: "builtin" method: Multiply global-config: input-parameters: ["cpu/utilization", "duration"] diff --git a/manifests/examples/mock-cpu-util-to-carbon.yml b/manifests/examples/mock-cpu-util-to-carbon.yml index 95a4acba5..468dbe3ed 100644 --- a/manifests/examples/mock-cpu-util-to-carbon.yml +++ b/manifests/examples/mock-cpu-util-to-carbon.yml @@ -7,7 +7,7 @@ initialize: path: 'builtin' method: GroupBy operational-carbon: - path: '@grnsft/if-plugins' + path: 'builtin' method: Multiply global-config: input-parameters: ['cpu/energy', 'grid/carbon-intensity'] diff --git a/manifests/plugins/multiply/failure-input-parameter-is-missing.yml b/manifests/plugins/multiply/failure-input-parameter-is-missing.yml index 14849e186..90951d29f 100644 --- a/manifests/plugins/multiply/failure-input-parameter-is-missing.yml +++ b/manifests/plugins/multiply/failure-input-parameter-is-missing.yml @@ -6,7 +6,7 @@ initialize: plugins: multiply: method: Multiply - path: "@grnsft/if-plugins" + path: "builtin" global-config: input-parameters: ["cpu/energy", "network/energy"] output-parameter: "energy-product"