Compare commits

...

3 Commits

Author SHA1 Message Date
Abhi kumar
72644a7145 Merge branch 'main' into optimization/uplot-data 2025-12-28 15:17:15 +05:30
Abhi kumar
1d3dbee59c Merge branch 'main' into optimization/uplot-data 2025-12-28 03:48:56 +05:30
Abhi Kumar
8760519bac perf: optimize uplot chart data processing 2025-12-28 03:34:34 +05:30
2 changed files with 93 additions and 57 deletions

View File

@@ -132,11 +132,20 @@ function UplotPanelWrapper({
[selectedGraph, widget?.panelTypes, widget?.stackedBarChart],
);
const chartData = getUPlotChartData(
queryResponse?.data?.payload,
widget.fillSpans,
stackedBarChart,
hiddenGraph,
const chartData = useMemo(
() =>
getUPlotChartData(
queryResponse?.data?.payload,
widget.fillSpans,
stackedBarChart,
hiddenGraph,
),
[
queryResponse?.data?.payload,
widget.fillSpans,
stackedBarChart,
hiddenGraph,
],
);
useEffect(() => {
@@ -293,7 +302,7 @@ function UplotPanelWrapper({
)}
{isFullViewMode && setGraphVisibility && !stackedBarChart && (
<GraphManager
data={getUPlotChartData(queryResponse?.data?.payload, widget.fillSpans)}
data={chartData}
name={widget.id}
options={options}
yAxisUnit={widget.yAxisUnit}

View File

@@ -1,6 +1,6 @@
import { themeColors } from 'constants/theme';
import getLabelName from 'lib/getLabelName';
import { cloneDeep, isUndefined } from 'lodash-es';
import { isUndefined } from 'lodash-es';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { QueryData } from 'types/api/widgets/getQuery';
@@ -8,7 +8,7 @@ import { normalizePlotValue } from './dataUtils';
import { generateColor } from './generateColor';
function getXAxisTimestamps(seriesList: QueryData[]): number[] {
const timestamps = new Set();
const timestamps = new Set<number>();
seriesList.forEach((series: { values?: [number, string][] }) => {
if (series?.values) {
@@ -18,54 +18,71 @@ function getXAxisTimestamps(seriesList: QueryData[]): number[] {
}
});
const timestampsArr: number[] | unknown[] = Array.from(timestamps) || [];
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return timestampsArr.sort((a, b) => a - b);
const timestampsArr = Array.from(timestamps);
timestampsArr.sort((a, b) => a - b);
return timestampsArr;
}
function fillMissingXAxisTimestamps(timestampArr: number[], data: any[]): any {
// eslint-disable-next-line sonarjs/cognitive-complexity
function fillMissingXAxisTimestamps(
timestampArr: number[],
data: Array<{ values?: [number, string][] }>,
): (number | null)[][] {
// Generate a set of all timestamps in the range
const allTimestampsSet = new Set(timestampArr);
const processedData = cloneDeep(data);
const result: (number | null)[][] = [];
// Fill missing timestamps with null values
processedData.forEach((entry: { values: (number | null)[][] }) => {
const existingTimestamps = new Set(
(entry?.values ?? []).map((value) => value[0]),
);
// Process each series entry
for (let i = 0; i < data.length; i++) {
const entry = data[i];
if (!entry?.values) {
result.push([]);
} else {
// Build Set of existing timestamps directly (avoid intermediate array)
const existingTimestamps = new Set<number>();
const valuesMap = new Map<number, number | null>();
const missingTimestamps = Array.from(allTimestampsSet).filter(
(timestamp) => !existingTimestamps.has(timestamp),
);
for (let j = 0; j < entry.values.length; j++) {
const [timestamp, value] = entry.values[j];
existingTimestamps.add(timestamp);
valuesMap.set(timestamp, normalizePlotValue(value));
}
missingTimestamps.forEach((timestamp) => {
const value = null;
// Find missing timestamps by iterating Set directly (avoid Array.from + filter)
const missingTimestamps: number[] = [];
const allTimestampsArray = Array.from(allTimestampsSet);
for (let k = 0; k < allTimestampsArray.length; k++) {
const timestamp = allTimestampsArray[k];
if (!existingTimestamps.has(timestamp)) {
missingTimestamps.push(timestamp);
}
}
entry?.values?.push([timestamp, value]);
});
// Add missing timestamps to map
for (let j = 0; j < missingTimestamps.length; j++) {
valuesMap.set(missingTimestamps[j], null);
}
entry?.values?.forEach((v) => {
// eslint-disable-next-line no-param-reassign
v[1] = normalizePlotValue(v[1]);
});
// Build sorted array of values
const sortedTimestamps = Array.from(valuesMap.keys()).sort((a, b) => a - b);
const yValues = sortedTimestamps.map((timestamp) => {
const value = valuesMap.get(timestamp);
return value !== undefined ? value : null;
});
result.push(yValues);
}
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
entry?.values?.sort((a, b) => a[0] - b[0]);
});
return processedData.map((entry: { values: [number, string][] }) =>
entry?.values?.map((value) => value[1]),
);
return result;
}
function getStackedSeries(val: any): any {
const series = cloneDeep(val) || [];
function getStackedSeries(val: (number | null)[][]): (number | null)[][] {
const series = val ? val.map((row: (number | null)[]) => [...row]) : [];
for (let i = series.length - 2; i >= 0; i--) {
for (let j = 0; j < series[i].length; j++) {
series[i][j] += series[i + 1][j];
series[i][j] = (series[i][j] || 0) + (series[i + 1][j] || 0);
}
}
@@ -110,6 +127,7 @@ const processAnomalyDetectionData = (
queryIndex < anomalyDetectionData.length;
queryIndex++
) {
const queryData = anomalyDetectionData[queryIndex];
const {
series,
predictedSeries,
@@ -117,7 +135,7 @@ const processAnomalyDetectionData = (
lowerBoundSeries,
queryName,
legend,
} = anomalyDetectionData[queryIndex];
} = queryData;
for (let index = 0; index < series?.length; index++) {
const label = getLabelName(
@@ -129,14 +147,30 @@ const processAnomalyDetectionData = (
const objKey =
anomalyDetectionData.length > 1 ? `${queryName}-${label}` : label;
// Single iteration instead of 5 separate map operations
const { values: seriesValues } = series[index];
const { values: predictedValues } = predictedSeries[index];
const { values: upperBoundValues } = upperBoundSeries[index];
const { values: lowerBoundValues } = lowerBoundSeries[index];
// eslint-disable-next-line prefer-destructuring
const length = seriesValues.length;
const timestamps: number[] = new Array(length);
const values: number[] = new Array(length);
const predicted: number[] = new Array(length);
const upperBound: number[] = new Array(length);
const lowerBound: number[] = new Array(length);
for (let i = 0; i < length; i++) {
timestamps[i] = seriesValues[i].timestamp / 1000;
values[i] = seriesValues[i].value;
predicted[i] = predictedValues[i].value;
upperBound[i] = upperBoundValues[i].value;
lowerBound[i] = lowerBoundValues[i].value;
}
processedData[objKey] = {
data: [
series[index].values.map((v: { timestamp: number }) => v.timestamp / 1000),
series[index].values.map((v: { value: number }) => v.value),
predictedSeries[index].values.map((v: { value: number }) => v.value),
upperBoundSeries[index].values.map((v: { value: number }) => v.value),
lowerBoundSeries[index].values.map((v: { value: number }) => v.value),
],
data: [timestamps, values, predicted, upperBound, lowerBound],
color: generateColor(
objKey,
isDarkMode ? themeColors.chartcolors : themeColors.lightModeColor,
@@ -152,14 +186,7 @@ const processAnomalyDetectionData = (
export const getUplotChartDataForAnomalyDetection = (
apiResponse: MetricRangePayloadProps,
isDarkMode: boolean,
): Record<
string,
{
[x: string]: any;
data: number[][];
color: string;
}
> => {
): Record<string, { [x: string]: any; data: number[][]; color: string }> => {
const anomalyDetectionData = apiResponse?.data?.newResult?.data?.result;
return processAnomalyDetectionData(anomalyDetectionData, isDarkMode);
};