From 5f308bf72be400ea2cd669429f7842ea875598c5 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Mon, 20 May 2024 14:05:02 +0100 Subject: [PATCH 1/8] feat(lib): export subtract function --- src/builtins/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/builtins/index.ts b/src/builtins/index.ts index d227e22e4..37a736c47 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 {Subtract} from './subtract'; From 6abccfdeb8ab9225541d92b2c4227cc5d7c08334 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Mon, 20 May 2024 14:05:53 +0100 Subject: [PATCH 2/8] feat(lib): add subtrcat func source to builtins --- src/builtins/subtract/index.ts | 101 +++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/builtins/subtract/index.ts diff --git a/src/builtins/subtract/index.ts b/src/builtins/subtract/index.ts new file mode 100644 index 000000000..0af02898a --- /dev/null +++ b/src/builtins/subtract/index.ts @@ -0,0 +1,101 @@ +import {z} from 'zod'; + +import {ERRORS} from '../../util/errors'; +import {buildErrorMessage} from '../../util/helpers'; +import {validate} from '../../util/validations'; + +import {ExecutePlugin, PluginParams} from '../../types/interface'; +import {SubtractConfig} from './types'; + +const {InputValidationError} = ERRORS; + +export const Subtract = (globalConfig: SubtractConfig): ExecutePlugin => { + const errorBuilder = buildErrorMessage(Subtract.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(metricToSubtract => { + validateParamExists(input, metricToSubtract); + validateNumericString(input[metricToSubtract]); + }); + + return input; + }; + + const validateParamExists = (input: PluginParams, param: string) => { + if (input[param] === undefined) { + throw new InputValidationError( + errorBuilder({ + message: `${param} is missing from the input array`, + }) + ); + } + }; + + const validateNumericString = (str: string) => { + if (isNaN(+Number(str))) { + throw new InputValidationError( + errorBuilder({ + message: `${str} is not numberic`, + }) + ); + } + }; + + /** + * Subtract items from inputParams[1..n] from inputParams[0] and write the result in a new param outputParam. + */ + const execute = (inputs: PluginParams[]): PluginParams[] => { + const { + 'input-parameters': inputParameters, + 'output-parameter': outputParameter, + } = validateGlobalConfig(); + return inputs.map(input => { + validateSingleInput(input, inputParameters); + + return { + ...input, + [outputParameter]: calculateDiff(input, inputParameters), + }; + }); + }; + + /** + * Calculates the diff between the 1st item in the inputs nad the rest of the items + */ + const calculateDiff = (input: PluginParams, inputParameters: string[]) => { + const [firstItem, ...restItems] = inputParameters; + return restItems.reduce( + (accumulator, metricToSubtract) => accumulator - input[metricToSubtract], + input[firstItem] // Starting accumulator with the value of the first item + ); + }; + + return { + metadata, + execute, + }; +}; From 9ef2166c2a34ee723592d0d75edc649e0f97649a Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Mon, 20 May 2024 14:06:50 +0100 Subject: [PATCH 3/8] feat(lib): add types for subtract --- src/builtins/subtract/types.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/builtins/subtract/types.ts diff --git a/src/builtins/subtract/types.ts b/src/builtins/subtract/types.ts new file mode 100644 index 000000000..4dc6775d2 --- /dev/null +++ b/src/builtins/subtract/types.ts @@ -0,0 +1,4 @@ +export type SubtractConfig = { + 'input-parameters': string[]; + 'output-parameter': string; +}; From e171844341f9af1953f6586ffd81c352ff6b5efa Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Mon, 20 May 2024 14:07:23 +0100 Subject: [PATCH 4/8] feat(lib): add types/helpers --- 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 f541d865daf7d919dda636d9aaa532c64c16e056 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Mon, 20 May 2024 14:07:59 +0100 Subject: [PATCH 5/8] feat(lib): add util/helpers --- src/util/helpers.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/util/helpers.ts b/src/util/helpers.ts index 55698cac6..3a758f616 100644 --- a/src/util/helpers.ts +++ b/src/util/helpers.ts @@ -3,7 +3,7 @@ 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; @@ -40,6 +40,13 @@ export const mergeObjects = (defaults: any, input: any) => { return merged; }; +export const buildErrorMessage = + (classInstanceName: string) => (params: ErrorFormatParams) => { + const {scope, message} = params; + + return `${classInstanceName}${scope ? `(${scope})` : ''}: ${message}.`; + }; + /** * Promise version of Node's `exec` from `child-process`. */ From fa0cbd7fc2afdf0fc79e2112043fec5d0b3a1c04 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Mon, 20 May 2024 14:09:13 +0100 Subject: [PATCH 6/8] test(lib): add unit tests for subtract --- src/__tests__/unit/builtins/subtract.test.ts | 102 +++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/__tests__/unit/builtins/subtract.test.ts diff --git a/src/__tests__/unit/builtins/subtract.test.ts b/src/__tests__/unit/builtins/subtract.test.ts new file mode 100644 index 000000000..7ac717142 --- /dev/null +++ b/src/__tests__/unit/builtins/subtract.test.ts @@ -0,0 +1,102 @@ +import {Subtract} from '../../../builtins/subtract'; + +import {ERRORS} from '../../../util/errors'; + +const {InputValidationError} = ERRORS; + +describe('lib/subtract: ', () => { + describe('Subtract: ', () => { + const globalConfig = { + 'input-parameters': ['cpu/energy', 'network/energy', 'memory/energy'], + 'output-parameter': 'energy/diff', + }; + const subtract = Subtract(globalConfig); + + describe('init: ', () => { + it('successfully initalized.', () => { + expect(subtract).toHaveProperty('metadata'); + expect(subtract).toHaveProperty('execute'); + }); + }); + + describe('execute(): ', () => { + it('successfully applies Subtract strategy to given input.', async () => { + expect.assertions(1); + + const expectedResult = [ + { + duration: 3600, + 'cpu/energy': 4, + 'network/energy': 2, + 'memory/energy': 1, + 'energy/diff': 1, + timestamp: '2021-01-01T00:00:00Z', + }, + ]; + + const result = await subtract.execute([ + { + duration: 3600, + 'cpu/energy': 4, + 'network/energy': 2, + 'memory/energy': 1, + timestamp: '2021-01-01T00:00:00Z', + }, + ]); + + expect(result).toStrictEqual(expectedResult); + }); + + it('throws an error on missing params in input.', async () => { + const expectedMessage = + 'Subtract: cpu/energy is missing from the input array.'; + + expect.assertions(1); + + try { + await subtract.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-diff', + }; + const subtract = Subtract(newConfig); + + const data = [ + { + duration: 3600, + timestamp: '2021-01-01T00:00:00Z', + carbon: 3, + 'other-carbon': 2, + }, + ]; + const response = await subtract.execute(data); + + const expectedResult = [ + { + duration: 3600, + carbon: 3, + 'other-carbon': 2, + 'carbon-diff': 1, + timestamp: '2021-01-01T00:00:00Z', + }, + ]; + + expect(response).toEqual(expectedResult); + }); + }); + }); +}); From ae31b61d2977b3e6a898f559d1294bc411200d81 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Mon, 20 May 2024 14:09:45 +0100 Subject: [PATCH 7/8] feat(lib): add readme for subtract --- src/builtins/subtract/README.md | 94 +++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/builtins/subtract/README.md diff --git a/src/builtins/subtract/README.md b/src/builtins/subtract/README.md new file mode 100644 index 000000000..f576ef1a6 --- /dev/null +++ b/src/builtins/subtract/README.md @@ -0,0 +1,94 @@ +# Subtract + +`subtract` is a generic plugin for doing arithmetic subtractions of two or more values in an `input` array. + +You provide the names of the values you want to subtract, and a name to use to add the subtraction to the output array. + +For example, you could subtract `cpu/energy` and `network/energy` and name the result `offset/energy`. `offset/energy` would then be added to every observation in your input array as the diff 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 result of the diff to the output array. + +### Inputs + +All of `input-parameters` must be available in the input array. + +## Returns + +- `output-parameter`: the subtraction 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 `Subtract`. Then, you can call `execute()`. + +```typescript +import {Subtract} from 'builtins'; + +const config = { + inputParameters: ['cpu/energy', 'network/energy'], + outputParameter: 'offset/energy', +}; + +const subtract = Subtract(config); +const result = subtract subtract.execute([ + { + duration: 3600, + timestamp: '2021-01-01T00:00:00Z', + 'cpu/energy': 0.005, + 'memory/energy': 0.0001, + }, +]); +``` + +## 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 and does not have to be done explicitly by the user. The following is an example manifest that calls `subtract`: + +```yaml +name: subtract demo +description: +tags: +initialize: + outputs: + - yaml + plugins: + subtract: + method: Subtract + path: 'builtins' + global-config: + input-parameters: ['cpu/energy', 'network/energy'] + output-parameter: 'energy/diff' +tree: + children: + child: + pipeline: + - subtract + config: + subtract: + inputs: + - timestamp: 2023-08-06T00:00 + duration: 3600 + cpu/energy: 0.003 + network/energy: 0.001 +``` + +You can run this example by saving it as `./examples/manifests/test/subrtact.yml` and executing the following command from the project root: + +```sh +npm i -g @grnsft/if +ie --manifest /manifests/plugins/subtract.yml --output manifests/outputs/subtract.yml +``` + +The results will be saved to a new `yaml` file in `manifests/outputs`. From 546d7bd992b8507c639006d2c47d9372baecea2b Mon Sep 17 00:00:00 2001 From: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> Date: Tue, 21 May 2024 11:37:38 +0100 Subject: [PATCH 8/8] Update src/builtins/subtract/README.md Signed-off-by: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> --- src/builtins/subtract/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builtins/subtract/README.md b/src/builtins/subtract/README.md index f576ef1a6..9279220b8 100644 --- a/src/builtins/subtract/README.md +++ b/src/builtins/subtract/README.md @@ -66,7 +66,7 @@ initialize: plugins: subtract: method: Subtract - path: 'builtins' + path: 'builtin' global-config: input-parameters: ['cpu/energy', 'network/energy'] output-parameter: 'energy/diff'