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:
parent
d710507bc5
commit
23e0f85ef9
@ -18,7 +18,7 @@
|
||||
// THIS SOFTWARE.
|
||||
import { css } from '@emotion/css';
|
||||
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 { CoreApp, createTheme, DataFrame, FieldType, getDisplayProcessor } from '@grafana/data';
|
||||
@ -113,67 +113,62 @@ const FlameGraph = ({
|
||||
return foundLabels;
|
||||
}, [ufuzzy, search, uniqueLabels]);
|
||||
|
||||
const render = useCallback(
|
||||
(pixelsPerTick: number) => {
|
||||
if (!levels.length) {
|
||||
return;
|
||||
useEffect(() => {
|
||||
if (!levels.length) {
|
||||
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!;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
levels,
|
||||
wrapperWidth,
|
||||
valueField,
|
||||
totalTicks,
|
||||
rangeMin,
|
||||
rangeMax,
|
||||
search,
|
||||
topLevelIndex,
|
||||
foundLabels,
|
||||
getLabelValue,
|
||||
]
|
||||
);
|
||||
}
|
||||
}, [
|
||||
levels,
|
||||
wrapperWidth,
|
||||
valueField,
|
||||
totalTicks,
|
||||
rangeMin,
|
||||
rangeMax,
|
||||
search,
|
||||
topLevelIndex,
|
||||
foundLabels,
|
||||
getLabelValue,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (graphRef.current) {
|
||||
const pixelsPerTick = (wrapperWidth * window.devicePixelRatio) / totalTicks / (rangeMax - rangeMin);
|
||||
render(pixelsPerTick);
|
||||
|
||||
graphRef.current.onclick = (e) => {
|
||||
setTooltipData(undefined);
|
||||
const pixelsPerTick = graphRef.current!.clientWidth / totalTicks / (rangeMax - rangeMin);
|
||||
@ -222,7 +217,6 @@ const FlameGraph = ({
|
||||
};
|
||||
}
|
||||
}, [
|
||||
render,
|
||||
levels,
|
||||
rangeMin,
|
||||
rangeMax,
|
||||
|
@ -2,8 +2,8 @@ import { css } from '@emotion/css';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useMeasure } from 'react-use';
|
||||
|
||||
import { DataFrame, DataFrameView, CoreApp, getEnumDisplayProcessor, createTheme } from '@grafana/data';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { DataFrame, DataFrameView, CoreApp, getEnumDisplayProcessor } from '@grafana/data';
|
||||
import { useStyles2, useTheme2 } from '@grafana/ui';
|
||||
|
||||
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 theme = useTheme2();
|
||||
|
||||
// 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
|
||||
// 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) => {
|
||||
const enumConfig = labelField?.config?.type?.enum;
|
||||
if (enumConfig) {
|
||||
return getEnumDisplayProcessor(createTheme(), enumConfig)(label).text;
|
||||
return getEnumDisplayProcessor(theme, enumConfig)(label).text;
|
||||
} else {
|
||||
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
|
||||
|
@ -2,8 +2,8 @@ import { css } from '@emotion/css';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
|
||||
import { CoreApp, createTheme, DataFrame, Field, FieldType, getDisplayProcessor } from '@grafana/data';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { CoreApp, DataFrame, Field, FieldType, getDisplayProcessor } from '@grafana/data';
|
||||
import { useStyles2, useTheme2 } from '@grafana/ui';
|
||||
|
||||
import { PIXELS_PER_LEVEL } from '../../constants';
|
||||
import { SampleUnit, SelectedView, TableData, TopTableData } from '../types';
|
||||
@ -38,6 +38,7 @@ const FlameGraphTopTableContainer = ({
|
||||
getLabelValue,
|
||||
}: Props) => {
|
||||
const styles = useStyles2(() => getStyles(selectedView, app));
|
||||
const theme = useTheme2();
|
||||
const [topTable, setTopTable] = useState<TopTableData[]>();
|
||||
const valueField =
|
||||
data.fields.find((f) => f.name === 'value') ?? data.fields.find((f) => f.type === FieldType.number);
|
||||
@ -67,26 +68,29 @@ const FlameGraphTopTableContainer = ({
|
||||
return table;
|
||||
}, [getLabelValue, selfField, valueField, labelsField]);
|
||||
|
||||
const getTopTableData = (field: Field, value: number) => {
|
||||
const processor = getDisplayProcessor({ field, theme: createTheme() /* theme does not matter for us here */ });
|
||||
const displayValue = processor(value);
|
||||
let unitValue = displayValue.text + displayValue.suffix;
|
||||
const getTopTableData = useCallback(
|
||||
(field: Field, value: number) => {
|
||||
const processor = getDisplayProcessor({ field, theme });
|
||||
const displayValue = processor(value);
|
||||
let unitValue = displayValue.text + displayValue.suffix;
|
||||
|
||||
switch (field.config.unit) {
|
||||
case SampleUnit.Bytes:
|
||||
break;
|
||||
case SampleUnit.Nanoseconds:
|
||||
break;
|
||||
default:
|
||||
if (!displayValue.suffix) {
|
||||
// Makes sure we don't show 123undefined or something like that if suffix isn't defined
|
||||
unitValue = displayValue.text;
|
||||
}
|
||||
break;
|
||||
}
|
||||
switch (field.config.unit) {
|
||||
case SampleUnit.Bytes:
|
||||
break;
|
||||
case SampleUnit.Nanoseconds:
|
||||
break;
|
||||
default:
|
||||
if (!displayValue.suffix) {
|
||||
// Makes sure we don't show 123undefined or something like that if suffix isn't defined
|
||||
unitValue = displayValue.text;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return unitValue;
|
||||
};
|
||||
return unitValue;
|
||||
},
|
||||
[theme]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const table = sortLevelsIntoTable();
|
||||
@ -104,7 +108,7 @@ const FlameGraphTopTableContainer = ({
|
||||
}
|
||||
|
||||
setTopTable(topTable);
|
||||
}, [data.fields, selfField, sortLevelsIntoTable, valueField]);
|
||||
}, [data.fields, selfField, sortLevelsIntoTable, valueField, getTopTableData]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
Loading…
Reference in New Issue
Block a user