HeatmapNG: fix Prometheus frame-per-bucket heatmap response rendering (#46309)

This commit is contained in:
Leon Sorokin
2022-03-07 20:51:11 -06:00
committed by GitHub
parent 5d2f34d8e2
commit a9ee446de4
2 changed files with 60 additions and 43 deletions
@@ -46,12 +46,28 @@ export function sortAscStrInf(aName?: string | null, bName?: string | null) {
}
/** Given existing buckets, create a values style frame */
// Assumes frames have already been sorted ASC and de-accumulated.
export function createHeatmapFromBuckets(frames: DataFrame[]): DataFrame {
frames = frames.slice();
// assumes all Time fields are identical
// TODO: handle null-filling w/ fields[0].config.interval?
const xField = frames[0].fields[0];
const xValues = xField.values.toArray();
const yField = frames[0].fields[1];
// sort ASC by frame.name (Prometheus bucket bound)
// or use frame.fields[1].config.displayNameFromDS ?
frames.sort((a, b) => sortAscStrInf(a.name, b.name));
// similar to initBins() below
const len = xValues.length * frames.length;
const xs = new Array(len);
const ys = new Array(len);
const counts2 = new Array(len);
const counts = frames.map((frame) => frame.fields[1].values.toArray().slice());
// transpose
counts.forEach((bucketCounts, bi) => {
for (let i = 0; i < bucketCounts.length; i++) {
counts2[counts.length * i + bi] = bucketCounts[i];
}
});
const bucketBounds = frames.map((frame, i) => {
return i; // until we have y ordinal scales working for facets/scatter
@@ -70,39 +86,6 @@ export function createHeatmapFromBuckets(frames: DataFrame[]): DataFrame {
*/
});
// assumes all Time fields are identical
// TODO: handle null-filling w/ fields[0].config.interval?
const xField = frames[0].fields[0];
const xValues = xField.values.toArray();
const yField = frames[0].fields[1];
// similar to initBins() below
const len = xValues.length * bucketBounds.length;
const xs = new Array(len);
const ys = new Array(len);
const counts2 = new Array(len);
// cumulative counts
const counts = frames.map((frame) => frame.fields[1].values.toArray().slice());
// de-accumulate
counts.reverse();
counts.forEach((bucketCounts, bi) => {
if (bi < counts.length - 1) {
for (let i = 0; i < bucketCounts.length; i++) {
bucketCounts[i] -= counts[bi + 1][i];
}
}
});
counts.reverse();
// transpose
counts.forEach((bucketCounts, bi) => {
for (let i = 0; i < bucketCounts.length; i++) {
counts2[counts.length * i + bi] = bucketCounts[i];
}
});
// fill flat/repeating array
for (let i = 0, yi = 0, xi = 0; i < len; yi = ++i % bucketBounds.length) {
ys[i] = bucketBounds[yi];
@@ -144,6 +127,41 @@ export function createHeatmapFromBuckets(frames: DataFrame[]): DataFrame {
};
}
// Sorts frames ASC by numeric bucket name and de-accumulates values in each frame's Value field [1]
// similar to Prometheus result_transformer.ts -> transformToHistogramOverTime()
export function prepBucketFrames(frames: DataFrame[]): DataFrame[] {
frames = frames.slice();
// sort ASC by frame.name (Prometheus bucket bound)
// or use frame.fields[1].config.displayNameFromDS ?
frames.sort((a, b) => sortAscStrInf(a.name, b.name));
// cumulative counts
const counts = frames.map((frame) => frame.fields[1].values.toArray().slice());
// de-accumulate
counts.reverse();
counts.forEach((bucketCounts, bi) => {
if (bi < counts.length - 1) {
for (let i = 0; i < bucketCounts.length; i++) {
bucketCounts[i] -= counts[bi + 1][i];
}
}
});
counts.reverse();
return frames.map((frame, i) => ({
...frame,
fields: [
frame.fields[0],
{
...frame.fields[1],
values: new ArrayVector(counts[i]),
},
],
}));
}
export function calculateHeatmapFromData(frames: DataFrame[], options: HeatmapCalculationOptions): DataFrame {
//console.time('calculateHeatmapFromData');
@@ -1,9 +1,5 @@
import { DataFrame, DataFrameType, getDisplayProcessor, GrafanaTheme2 } from '@grafana/data';
import {
calculateHeatmapFromData,
createHeatmapFromBuckets,
sortAscStrInf,
} from 'app/features/transformers/calculateHeatmap/heatmap';
import { calculateHeatmapFromData, createHeatmapFromBuckets } from 'app/features/transformers/calculateHeatmap/heatmap';
import { HeatmapSourceMode, PanelOptions } from './models.gen';
export const enum BucketLayout {
@@ -59,8 +55,11 @@ export function prepareHeatmapData(
// detect a frame-per-bucket heatmap frame
// TODO: improve heuristic? infer from fields[1].labels.le === '+Inf' ?
if (frames[0].meta?.custom?.resultType === 'matrix' && frames.some((f) => f.name?.startsWith('+Inf'))) {
// already done by the Prometheus datasource frontend
//frames = prepBucketFrames(frames);
return {
yAxisValues: frames.map((f) => f.name ?? null).sort(sortAscStrInf),
yAxisValues: frames.map((f) => f.name ?? null),
...getHeatmapData(createHeatmapFromBuckets(frames), theme),
};
}