UNPKG

@grnsft/if-eco-ci-plugin

Version:

EcoCI plugin for Impact Framework.

163 lines (137 loc) 4.54 kB
import {z} from 'zod'; import moment from 'moment-timezone'; import {PluginParams, ConfigParams} from '@grnsft/if-core/types'; import {PluginFactory} from '@grnsft/if-core/interfaces'; import {ERRORS, validate} from '@grnsft/if-core/utils'; import {EcoCiAPI} from './api'; import {EcoCiParams} from './types'; const {ConfigError} = ERRORS; export const EcoCI = PluginFactory({ metadata: { inputs: {}, outputs: { carbon: { description: 'the used carbon in running the workflow', unit: 'gCO2eq', 'aggregation-method': {time: 'sum', component: 'sum'}, }, energy: { description: 'the used energy in running the workflow', unit: 'kWh', 'aggregation-method': {time: 'sum', component: 'sum'}, }, }, }, implementation: async (inputs: PluginParams[], config: ConfigParams) => { const result = await getRepoMetrics(config, inputs); return inputs.map(input => { const {energy, carbon} = calculateMetrics(result, {input, config}); return { ...input, energy, carbon, }; }); }, configValidation: (config: ConfigParams) => { if (!config || !Object.keys(config)?.length) { throw new ConfigError('Config is not provided.'); } const schema = z.object({ repo: z.string().regex(/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+$/), branch: z.string(), workflow: z.number(), 'start-date': z.string().or(z.date()).optional(), 'end-date': z.string().or(z.date()).optional(), }); return validate<z.infer<typeof schema>>(schema, config); }, inputValidation: ( input: PluginParams, _config: ConfigParams, index: number | undefined ) => { const schema = z.object({ timestamp: z.string().or(z.date()), duration: z.number().or(z.string()), }); return validate<z.infer<typeof schema>>(schema, input, index); }, }); /** * Gets metrics for the specified repository. */ const getRepoMetrics = async (config: ConfigParams, inputs: PluginParams[]) => { const {repo, branch, workflow, 'start-date': start, 'end-date': end} = config; const firstTimestamp = start || inputs[0].timestamp; const endTimestamp = start && !end ? start : end || inputs[inputs.length - 1].timestamp; const evaledDuration = eval(inputs[inputs.length - 1]?.duration); const {startDate, endDate} = getOnlyDates( firstTimestamp, endTimestamp, evaledDuration ); const params: EcoCiParams = { repo, branch, workflow, start_date: startDate || firstTimestamp, end_date: endDate, }; return await EcoCiAPI().getRepoMetrics(params); }; /** * Calculates the energy and carbon metrics. * Converts energy from `mJ` to `kWh`. */ const calculateMetrics = ( metrics: [], {input, config}: {input: PluginParams; config: ConfigParams} ) => { const kWhForJ = 2.78e-8; const {'start-date': startDate, 'end-date': endDate} = config; const data = metrics.reduce( (acc: {energy: number; carbon: number}, item: number[]) => { const dateInMilliseconds = moment .tz(item[2].toString(), 'UTC') .toDate() .getTime(); const startRange = moment.utc(startDate || input.timestamp).valueOf(); const endRange = endDate ? moment.utc(endDate).valueOf() : startRange + eval(input.duration) * 1000; if (dateInMilliseconds >= startRange && dateInMilliseconds < endRange) { acc.energy += item[0]; acc.carbon += parseFloat(item[item.length - 1]?.toString()); return acc; } return {energy: acc.energy, carbon: acc.carbon}; }, {energy: 0, carbon: 0} ); // Convert MJ to Joules (1 MJ = 1,000,000 J) and then // convert Joules to kWh (1 kWh = 3,600,000 J) data.energy = (data.energy / 1000000) * kWhForJ; // Convert micro-grams to grams to get gCO2eq unit data.carbon = data.carbon * 1e-6; return data; }; /** * Drops time part and returns only dates. */ const getOnlyDates = (startDate: string, endDate: string, duration: number) => { const startTimestampSeconds = new Date(startDate).getTime() / 1000; const endTimestampSeconds = new Date(endDate).getTime() / 1000; const range = endTimestampSeconds - startTimestampSeconds; if (range === 0) { const endDateInMilliseconds = (duration + endTimestampSeconds) * 1000; endDate = new Date( endDateInMilliseconds - new Date().getTimezoneOffset() * 60000 ).toISOString(); } return { startDate, endDate, }; };