feat(cicd-statistics): Added median analysis (alongside average)
Signed-off-by: Gustaf Räntilä <g.rantila@gmail.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FilterStatusType } from '../../apis/types';
|
||||
import { ChartableStageAnalysis, ChartableStageDatapoints } from '../types';
|
||||
|
||||
export function getAnalysis(
|
||||
values: Array<ChartableStageDatapoints>,
|
||||
status: FilterStatusType,
|
||||
): ChartableStageAnalysis {
|
||||
const analysis: ChartableStageAnalysis = {
|
||||
max: 0,
|
||||
min: 0,
|
||||
avg: 0,
|
||||
med: 0,
|
||||
};
|
||||
|
||||
const definedValues = values
|
||||
.filter(value => typeof value[status] !== 'undefined')
|
||||
.map(value => value[status]!)
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
analysis.max = definedValues[definedValues.length - 1] ?? 0;
|
||||
|
||||
analysis.min = definedValues[0] ?? 0;
|
||||
|
||||
analysis.avg =
|
||||
definedValues.length === 0
|
||||
? 0
|
||||
: definedValues.reduce((prev, cur) => prev + cur, 0) / values.length;
|
||||
|
||||
analysis.med = definedValues[Math.ceil(definedValues.length / 2)] ?? 0;
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
export function makeCombinedAnalysis(
|
||||
analysis: Record<FilterStatusType, ChartableStageAnalysis>,
|
||||
allDurations: Array<number>,
|
||||
): ChartableStageAnalysis {
|
||||
if (analysis.succeeded) {
|
||||
// If succeeded is a viewed status, it's probably what's expected to see
|
||||
// overall. Otherwise combine all other.
|
||||
return analysis.succeeded;
|
||||
}
|
||||
|
||||
const analysisValues = Object.values(analysis);
|
||||
|
||||
const max = analysisValues.reduce((prev, cur) => Math.max(prev, cur.max), 0);
|
||||
const min = analysisValues.reduce(
|
||||
(prev, cur) => Math.min(prev, cur.min),
|
||||
max,
|
||||
);
|
||||
const avg = !allDurations.length
|
||||
? 0
|
||||
: allDurations.reduce((prev, cur) => prev + cur, 0) / allDurations.length;
|
||||
allDurations.sort((a, b) => a - b);
|
||||
const med = allDurations[Math.ceil(allDurations.length / 2)] ?? 0;
|
||||
|
||||
return {
|
||||
max,
|
||||
min,
|
||||
avg,
|
||||
med,
|
||||
};
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
import { FilterStatusType, statusTypes } from '../../apis/types';
|
||||
import { Averagify, ChartableStage } from '../types';
|
||||
import { countBuildsPerDay } from './count-builds-per-day';
|
||||
import { getAnalysis } from './get-analysis';
|
||||
import { getAnalysis, makeCombinedAnalysis } from './analysis';
|
||||
import { average } from './utils';
|
||||
|
||||
interface FinalizeStageOptions {
|
||||
@@ -54,7 +54,7 @@ export function finalizeStage(
|
||||
|
||||
countBuildsPerDay(values);
|
||||
|
||||
const avgDuration: [duration: number, count: number] = [0, 0];
|
||||
const allDurations: Array<number> = [];
|
||||
|
||||
statusTypes.forEach(status => {
|
||||
analysis[status] = getAnalysis(values, status);
|
||||
@@ -70,8 +70,7 @@ export function finalizeStage(
|
||||
(duration): duration is number => typeof duration !== 'undefined',
|
||||
);
|
||||
|
||||
avgDuration[0] += durationsDense.reduce((prev, cur) => prev + cur, 0);
|
||||
avgDuration[1] += durationsDense.length;
|
||||
durationsDense.forEach(dur => allDurations.push(dur));
|
||||
|
||||
const averages = durationsDense.map((_, i) =>
|
||||
average(
|
||||
@@ -88,16 +87,7 @@ export function finalizeStage(
|
||||
});
|
||||
});
|
||||
|
||||
const analysisValues = Object.values(analysis);
|
||||
combinedAnalysis.max = analysisValues.reduce(
|
||||
(prev, cur) => Math.max(prev, cur.max),
|
||||
0,
|
||||
);
|
||||
combinedAnalysis.min = analysisValues.reduce(
|
||||
(prev, cur) => Math.min(prev, cur.min),
|
||||
combinedAnalysis.max,
|
||||
);
|
||||
combinedAnalysis.avg = !avgDuration[1] ? 0 : avgDuration[0] / avgDuration[1];
|
||||
Object.assign(combinedAnalysis, makeCombinedAnalysis(analysis, allDurations));
|
||||
|
||||
stage.stages.forEach(subStage => finalizeStage(subStage, options));
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FilterStatusType } from '../../apis/types';
|
||||
import { ChartableStageAnalysis, ChartableStageDatapoints } from '../types';
|
||||
|
||||
export function getAnalysis(
|
||||
values: Array<ChartableStageDatapoints>,
|
||||
status: FilterStatusType,
|
||||
): ChartableStageAnalysis {
|
||||
const analysis: ChartableStageAnalysis = {
|
||||
max: 0,
|
||||
min: 0,
|
||||
avg: 0,
|
||||
};
|
||||
|
||||
const definedValues = values.filter(
|
||||
value => typeof value[status] !== 'undefined',
|
||||
);
|
||||
|
||||
analysis.max = definedValues.reduce(
|
||||
(prev, cur) => Math.max(prev, cur[status]!),
|
||||
0,
|
||||
);
|
||||
analysis.min = definedValues.reduce(
|
||||
(prev, cur) => Math.min(prev, cur[status]!),
|
||||
analysis.max,
|
||||
);
|
||||
analysis.avg =
|
||||
definedValues.length === 0
|
||||
? 0
|
||||
: definedValues.reduce((prev, cur) => prev + cur[status]!, 0) /
|
||||
values.length;
|
||||
|
||||
return analysis;
|
||||
}
|
||||
@@ -38,17 +38,17 @@ export function getOrSetStage(
|
||||
export function makeStage(name: string): ChartableStage {
|
||||
return {
|
||||
analysis: {
|
||||
unknown: { avg: 0, max: 0, min: 0 },
|
||||
enqueued: { avg: 0, max: 0, min: 0 },
|
||||
scheduled: { avg: 0, max: 0, min: 0 },
|
||||
running: { avg: 0, max: 0, min: 0 },
|
||||
aborted: { avg: 0, max: 0, min: 0 },
|
||||
succeeded: { avg: 0, max: 0, min: 0 },
|
||||
failed: { avg: 0, max: 0, min: 0 },
|
||||
stalled: { avg: 0, max: 0, min: 0 },
|
||||
expired: { avg: 0, max: 0, min: 0 },
|
||||
unknown: { avg: 0, med: 0, max: 0, min: 0 },
|
||||
enqueued: { avg: 0, med: 0, max: 0, min: 0 },
|
||||
scheduled: { avg: 0, med: 0, max: 0, min: 0 },
|
||||
running: { avg: 0, med: 0, max: 0, min: 0 },
|
||||
aborted: { avg: 0, med: 0, max: 0, min: 0 },
|
||||
succeeded: { avg: 0, med: 0, max: 0, min: 0 },
|
||||
failed: { avg: 0, med: 0, max: 0, min: 0 },
|
||||
stalled: { avg: 0, med: 0, max: 0, min: 0 },
|
||||
expired: { avg: 0, med: 0, max: 0, min: 0 },
|
||||
},
|
||||
combinedAnalysis: { avg: 0, max: 0, min: 0 },
|
||||
combinedAnalysis: { avg: 0, med: 0, max: 0, min: 0 },
|
||||
statusSet: new Set<FilterStatusType>(),
|
||||
name,
|
||||
values: [],
|
||||
|
||||
@@ -104,7 +104,8 @@ export function StageChart(props: StageChartProps) {
|
||||
>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography>
|
||||
{stage.name} (avg {formatDuration(stage.combinedAnalysis.avg)})
|
||||
{stage.name} (med {formatDuration(stage.combinedAnalysis.med)}, avg{' '}
|
||||
{formatDuration(stage.combinedAnalysis.avg)})
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
@@ -30,9 +30,14 @@ export type ChartableStageDatapoints = {
|
||||
};
|
||||
|
||||
export interface ChartableStageAnalysis {
|
||||
/** Maximum duration */
|
||||
max: number;
|
||||
/** Minimum duration */
|
||||
min: number;
|
||||
/** Average duration */
|
||||
avg: number;
|
||||
/** Median duration */
|
||||
med: number;
|
||||
}
|
||||
|
||||
export interface ChartableStage {
|
||||
|
||||
Reference in New Issue
Block a user