Skip to content

Commit

Permalink
Merge pull request #90 from Green-Software-Foundation/generic-substract
Browse files Browse the repository at this point in the history
feat(src): substract plugin
  • Loading branch information
jmcook1186 committed May 20, 2024
2 parents 4a2998a + dd47c0e commit 0236f41
Show file tree
Hide file tree
Showing 6 changed files with 305 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
102 changes: 102 additions & 0 deletions src/__tests__/unit/lib/subtract/index.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
});
1 change: 1 addition & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export {Sum} from './sum';
export {Coefficient} from './coefficient';
export {Divide} from './divide';
export {Regex} from './regex';
export {Subtract} from './subtract';
95 changes: 95 additions & 0 deletions src/lib/subtract/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# 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 '@grnsft/if-plugins';

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: '@grnsft/if-plugins'
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
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`.
102 changes: 102 additions & 0 deletions src/lib/subtract/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
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<z.infer<typeof globalConfigSchema>>(
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 = async (inputs: PluginParams[]): Promise<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,
};
};
4 changes: 4 additions & 0 deletions src/lib/subtract/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type SubtractConfig = {
'input-parameters': string[];
'output-parameter': string;
};

0 comments on commit 0236f41

Please sign in to comment.