From 8c980e99cbf5d359d55c3e2286afaf38f641f48d Mon Sep 17 00:00:00 2001 From: Paz Barda Date: Mon, 6 May 2024 23:45:50 +0300 Subject: [PATCH] feat(src): substract plugin Signed-off-by: Paz Barda --- README.md | 1 + .../unit/lib/substract/index.test.ts | 102 ++++++++++++++++++ src/lib/index.ts | 1 + src/lib/subtract/README.md | 95 ++++++++++++++++ src/lib/subtract/index.ts | 87 +++++++++++++++ src/lib/subtract/types.ts | 4 + 6 files changed, 290 insertions(+) create mode 100644 src/__tests__/unit/lib/substract/index.test.ts create mode 100644 src/lib/subtract/README.md create mode 100644 src/lib/subtract/index.ts create mode 100644 src/lib/subtract/types.ts diff --git a/README.md b/README.md index 57e57ad..5a9023a 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ - [SCI-O](./src/lib/sci-o/README.md) - [Shell](./src/lib/shell/README.md) - [Sum](./src/lib/sum/README.md) +- [Subtract](./src/lib/subtract/README.md) - [TDP Finder](./src/lib/tdp-finder/README.md) ## Contributing diff --git a/src/__tests__/unit/lib/substract/index.test.ts b/src/__tests__/unit/lib/substract/index.test.ts new file mode 100644 index 0000000..13e4fe6 --- /dev/null +++ b/src/__tests__/unit/lib/substract/index.test.ts @@ -0,0 +1,102 @@ +import {Subtract} from '../../../../lib'; + +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); + }); + }); + }); +}); diff --git a/src/lib/index.ts b/src/lib/index.ts index 44178f2..7f38d89 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -14,3 +14,4 @@ export {Sum} from './sum'; export {Coefficient} from './coefficient'; export {Divide} from './divide'; export {Regex} from './regex'; +export {Subtract} from './subtract'; diff --git a/src/lib/subtract/README.md b/src/lib/subtract/README.md new file mode 100644 index 0000000..dcbcb7d --- /dev/null +++ b/src/lib/subtract/README.md @@ -0,0 +1,95 @@ +# Sum + +`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 summing the input parameters 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 '@grnsft/if-plugins'; + +const config = { + inputParameters: ['cpu/energy', 'network/energy'], + outputParameter: 'offset/energy', +}; + +const sum = Sum(config); +const result = await sum.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 and does not have to be done explicitly by the user. The following is an example manifest that calls `sum`: + +```yaml +name: subtract demo +description: +tags: +initialize: + outputs: + - yaml + plugins: + subtract: + method: Subtract + path: '@grnsft/if-plugins' + global-config: + input-parameters: ['cpu/energy', 'network/energy'] + output-parameter: 'offset/' +tree: + children: + child: + pipeline: + - subtract + config: + sum: + 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 +npm i -g @grnsft/if-plugins +ie --manifest ./examples/manifests/test/subtract.yml --output ./examples/outputs/subtract.yml +``` + +The results will be saved to a new `yaml` file in `./examples/outputs`. diff --git a/src/lib/subtract/index.ts b/src/lib/subtract/index.ts new file mode 100644 index 0000000..64e104f --- /dev/null +++ b/src/lib/subtract/index.ts @@ -0,0 +1,87 @@ +import {z} from 'zod'; + +import {ERRORS} from '../../util/errors'; +import {buildErrorMessage} from '../../util/helpers'; +import {validate} from '../../util/validations'; + +import {PluginInterface} from '../../interfaces'; +import {PluginParams} from '../../types/common'; +import {SubtractConfig} from './types'; + +const {InputValidationError} = ERRORS; + +export const Subtract = (globalConfig: SubtractConfig): PluginInterface => { + 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 => { + if (!input[metricToSubtract]) { + throw new InputValidationError( + errorBuilder({ + message: `${metricToSubtract} is missing from the input array`, + }) + ); + } + }); + + return input; + }; + + /** + * Calculate the diff of each . + */ + const execute = async (inputs: PluginParams[]): Promise => { + const safeGlobalConfig = validateGlobalConfig(); + const inputParameters = safeGlobalConfig['input-parameters']; + const outputParameter = safeGlobalConfig['output-parameter']; + + return inputs.map(input => { + const safeInput = validateSingleInput(input, inputParameters); + + return { + ...safeInput, + [outputParameter]: calculateDiff(safeInput, inputParameters), + }; + }); + }; + + /** + * Calculates the sum of the energy components. + */ + 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, + }; +}; diff --git a/src/lib/subtract/types.ts b/src/lib/subtract/types.ts new file mode 100644 index 0000000..4dc6775 --- /dev/null +++ b/src/lib/subtract/types.ts @@ -0,0 +1,4 @@ +export type SubtractConfig = { + 'input-parameters': string[]; + 'output-parameter': string; +};