mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Flamegraph: Fix rendering on contextMenu click and improve rendering perf (#64742)
This commit is contained in:
@@ -18,7 +18,7 @@
|
|||||||
// THIS SOFTWARE.
|
// THIS SOFTWARE.
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import uFuzzy from '@leeoniya/ufuzzy';
|
import uFuzzy from '@leeoniya/ufuzzy';
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useMeasure } from 'react-use';
|
import { useMeasure } from 'react-use';
|
||||||
|
|
||||||
import { CoreApp, createTheme, DataFrame, FieldType, getDisplayProcessor } from '@grafana/data';
|
import { CoreApp, createTheme, DataFrame, FieldType, getDisplayProcessor } from '@grafana/data';
|
||||||
@@ -113,67 +113,62 @@ const FlameGraph = ({
|
|||||||
return foundLabels;
|
return foundLabels;
|
||||||
}, [ufuzzy, search, uniqueLabels]);
|
}, [ufuzzy, search, uniqueLabels]);
|
||||||
|
|
||||||
const render = useCallback(
|
useEffect(() => {
|
||||||
(pixelsPerTick: number) => {
|
if (!levels.length) {
|
||||||
if (!levels.length) {
|
return;
|
||||||
return;
|
}
|
||||||
|
const pixelsPerTick = (wrapperWidth * window.devicePixelRatio) / totalTicks / (rangeMax - rangeMin);
|
||||||
|
const ctx = graphRef.current?.getContext('2d')!;
|
||||||
|
const graph = graphRef.current!;
|
||||||
|
|
||||||
|
const height = PIXELS_PER_LEVEL * levels.length;
|
||||||
|
graph.width = Math.round(wrapperWidth * window.devicePixelRatio);
|
||||||
|
graph.height = Math.round(height * window.devicePixelRatio);
|
||||||
|
graph.style.width = `${wrapperWidth}px`;
|
||||||
|
graph.style.height = `${height}px`;
|
||||||
|
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.font = 12 * window.devicePixelRatio + 'px monospace';
|
||||||
|
ctx.strokeStyle = 'white';
|
||||||
|
|
||||||
|
const processor = getDisplayProcessor({
|
||||||
|
field: valueField,
|
||||||
|
theme: createTheme() /* theme does not matter for us here */,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let levelIndex = 0; levelIndex < levels.length; levelIndex++) {
|
||||||
|
const level = levels[levelIndex];
|
||||||
|
// Get all the dimensions of the rectangles for the level. We do this by level instead of per rectangle, because
|
||||||
|
// sometimes we collapse multiple bars into single rect.
|
||||||
|
const dimensions = getRectDimensionsForLevel(
|
||||||
|
level,
|
||||||
|
levelIndex,
|
||||||
|
totalTicks,
|
||||||
|
rangeMin,
|
||||||
|
pixelsPerTick,
|
||||||
|
processor,
|
||||||
|
getLabelValue
|
||||||
|
);
|
||||||
|
for (const rect of dimensions) {
|
||||||
|
// Render each rectangle based on the computed dimensions
|
||||||
|
renderRect(ctx, rect, totalTicks, rangeMin, rangeMax, search, levelIndex, topLevelIndex, foundLabels);
|
||||||
}
|
}
|
||||||
const ctx = graphRef.current?.getContext('2d')!;
|
}
|
||||||
const graph = graphRef.current!;
|
}, [
|
||||||
|
levels,
|
||||||
const height = PIXELS_PER_LEVEL * levels.length;
|
wrapperWidth,
|
||||||
graph.width = Math.round(wrapperWidth * window.devicePixelRatio);
|
valueField,
|
||||||
graph.height = Math.round(height * window.devicePixelRatio);
|
totalTicks,
|
||||||
graph.style.width = `${wrapperWidth}px`;
|
rangeMin,
|
||||||
graph.style.height = `${height}px`;
|
rangeMax,
|
||||||
|
search,
|
||||||
ctx.textBaseline = 'middle';
|
topLevelIndex,
|
||||||
ctx.font = 12 * window.devicePixelRatio + 'px monospace';
|
foundLabels,
|
||||||
ctx.strokeStyle = 'white';
|
getLabelValue,
|
||||||
|
]);
|
||||||
const processor = getDisplayProcessor({
|
|
||||||
field: valueField,
|
|
||||||
theme: createTheme() /* theme does not matter for us here */,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let levelIndex = 0; levelIndex < levels.length; levelIndex++) {
|
|
||||||
const level = levels[levelIndex];
|
|
||||||
// Get all the dimensions of the rectangles for the level. We do this by level instead of per rectangle, because
|
|
||||||
// sometimes we collapse multiple bars into single rect.
|
|
||||||
const dimensions = getRectDimensionsForLevel(
|
|
||||||
level,
|
|
||||||
levelIndex,
|
|
||||||
totalTicks,
|
|
||||||
rangeMin,
|
|
||||||
pixelsPerTick,
|
|
||||||
processor,
|
|
||||||
getLabelValue
|
|
||||||
);
|
|
||||||
for (const rect of dimensions) {
|
|
||||||
// Render each rectangle based on the computed dimensions
|
|
||||||
renderRect(ctx, rect, totalTicks, rangeMin, rangeMax, search, levelIndex, topLevelIndex, foundLabels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
levels,
|
|
||||||
wrapperWidth,
|
|
||||||
valueField,
|
|
||||||
totalTicks,
|
|
||||||
rangeMin,
|
|
||||||
rangeMax,
|
|
||||||
search,
|
|
||||||
topLevelIndex,
|
|
||||||
foundLabels,
|
|
||||||
getLabelValue,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (graphRef.current) {
|
if (graphRef.current) {
|
||||||
const pixelsPerTick = (wrapperWidth * window.devicePixelRatio) / totalTicks / (rangeMax - rangeMin);
|
|
||||||
render(pixelsPerTick);
|
|
||||||
|
|
||||||
graphRef.current.onclick = (e) => {
|
graphRef.current.onclick = (e) => {
|
||||||
setTooltipData(undefined);
|
setTooltipData(undefined);
|
||||||
const pixelsPerTick = graphRef.current!.clientWidth / totalTicks / (rangeMax - rangeMin);
|
const pixelsPerTick = graphRef.current!.clientWidth / totalTicks / (rangeMax - rangeMin);
|
||||||
@@ -222,7 +217,6 @@ const FlameGraph = ({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
render,
|
|
||||||
levels,
|
levels,
|
||||||
rangeMin,
|
rangeMin,
|
||||||
rangeMax,
|
rangeMax,
|
||||||
|
@@ -2,8 +2,8 @@ import { css } from '@emotion/css';
|
|||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useMeasure } from 'react-use';
|
import { useMeasure } from 'react-use';
|
||||||
|
|
||||||
import { DataFrame, DataFrameView, CoreApp, getEnumDisplayProcessor, createTheme } from '@grafana/data';
|
import { DataFrame, DataFrameView, CoreApp, getEnumDisplayProcessor } from '@grafana/data';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { useStyles2, useTheme2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { MIN_WIDTH_TO_SHOW_BOTH_TOPTABLE_AND_FLAMEGRAPH, PIXELS_PER_LEVEL } from '../constants';
|
import { MIN_WIDTH_TO_SHOW_BOTH_TOPTABLE_AND_FLAMEGRAPH, PIXELS_PER_LEVEL } from '../constants';
|
||||||
|
|
||||||
@@ -33,6 +33,8 @@ const FlameGraphContainer = (props: Props) => {
|
|||||||
|
|
||||||
const labelField = props.data?.fields.find((f) => f.name === 'label');
|
const labelField = props.data?.fields.find((f) => f.name === 'label');
|
||||||
|
|
||||||
|
const theme = useTheme2();
|
||||||
|
|
||||||
// Label can actually be an enum field so depending on that we have to access it through display processor. This is
|
// Label can actually be an enum field so depending on that we have to access it through display processor. This is
|
||||||
// both a backward compatibility but also to allow using a simple dataFrame without enum config. This would allow
|
// both a backward compatibility but also to allow using a simple dataFrame without enum config. This would allow
|
||||||
// users to use this panel with correct query from data sources that do not return profiles natively.
|
// users to use this panel with correct query from data sources that do not return profiles natively.
|
||||||
@@ -40,12 +42,12 @@ const FlameGraphContainer = (props: Props) => {
|
|||||||
(label: string | number) => {
|
(label: string | number) => {
|
||||||
const enumConfig = labelField?.config?.type?.enum;
|
const enumConfig = labelField?.config?.type?.enum;
|
||||||
if (enumConfig) {
|
if (enumConfig) {
|
||||||
return getEnumDisplayProcessor(createTheme(), enumConfig)(label).text;
|
return getEnumDisplayProcessor(theme, enumConfig)(label).text;
|
||||||
} else {
|
} else {
|
||||||
return label.toString();
|
return label.toString();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[labelField]
|
[labelField, theme]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Transform dataFrame with nested set format to array of levels. Each level contains all the bars for a particular
|
// Transform dataFrame with nested set format to array of levels. Each level contains all the bars for a particular
|
||||||
|
@@ -2,8 +2,8 @@ import { css } from '@emotion/css';
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
|
||||||
import { CoreApp, createTheme, DataFrame, Field, FieldType, getDisplayProcessor } from '@grafana/data';
|
import { CoreApp, DataFrame, Field, FieldType, getDisplayProcessor } from '@grafana/data';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { useStyles2, useTheme2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { PIXELS_PER_LEVEL } from '../../constants';
|
import { PIXELS_PER_LEVEL } from '../../constants';
|
||||||
import { SampleUnit, SelectedView, TableData, TopTableData } from '../types';
|
import { SampleUnit, SelectedView, TableData, TopTableData } from '../types';
|
||||||
@@ -38,6 +38,7 @@ const FlameGraphTopTableContainer = ({
|
|||||||
getLabelValue,
|
getLabelValue,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const styles = useStyles2(() => getStyles(selectedView, app));
|
const styles = useStyles2(() => getStyles(selectedView, app));
|
||||||
|
const theme = useTheme2();
|
||||||
const [topTable, setTopTable] = useState<TopTableData[]>();
|
const [topTable, setTopTable] = useState<TopTableData[]>();
|
||||||
const valueField =
|
const valueField =
|
||||||
data.fields.find((f) => f.name === 'value') ?? data.fields.find((f) => f.type === FieldType.number);
|
data.fields.find((f) => f.name === 'value') ?? data.fields.find((f) => f.type === FieldType.number);
|
||||||
@@ -67,26 +68,29 @@ const FlameGraphTopTableContainer = ({
|
|||||||
return table;
|
return table;
|
||||||
}, [getLabelValue, selfField, valueField, labelsField]);
|
}, [getLabelValue, selfField, valueField, labelsField]);
|
||||||
|
|
||||||
const getTopTableData = (field: Field, value: number) => {
|
const getTopTableData = useCallback(
|
||||||
const processor = getDisplayProcessor({ field, theme: createTheme() /* theme does not matter for us here */ });
|
(field: Field, value: number) => {
|
||||||
const displayValue = processor(value);
|
const processor = getDisplayProcessor({ field, theme });
|
||||||
let unitValue = displayValue.text + displayValue.suffix;
|
const displayValue = processor(value);
|
||||||
|
let unitValue = displayValue.text + displayValue.suffix;
|
||||||
|
|
||||||
switch (field.config.unit) {
|
switch (field.config.unit) {
|
||||||
case SampleUnit.Bytes:
|
case SampleUnit.Bytes:
|
||||||
break;
|
break;
|
||||||
case SampleUnit.Nanoseconds:
|
case SampleUnit.Nanoseconds:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (!displayValue.suffix) {
|
if (!displayValue.suffix) {
|
||||||
// Makes sure we don't show 123undefined or something like that if suffix isn't defined
|
// Makes sure we don't show 123undefined or something like that if suffix isn't defined
|
||||||
unitValue = displayValue.text;
|
unitValue = displayValue.text;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return unitValue;
|
return unitValue;
|
||||||
};
|
},
|
||||||
|
[theme]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const table = sortLevelsIntoTable();
|
const table = sortLevelsIntoTable();
|
||||||
@@ -104,7 +108,7 @@ const FlameGraphTopTableContainer = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTopTable(topTable);
|
setTopTable(topTable);
|
||||||
}, [data.fields, selfField, sortLevelsIntoTable, valueField]);
|
}, [data.fields, selfField, sortLevelsIntoTable, valueField, getTopTableData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
Reference in New Issue
Block a user