Flamegraph: Fix rendering on contextMenu click and improve rendering perf (#64742)

pull/64645/head
Andrej Ocenas 2 years ago committed by GitHub
parent d710507bc5
commit 23e0f85ef9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 112
      public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraph.tsx
  2. 10
      public/app/plugins/panel/flamegraph/components/FlameGraphContainer.tsx
  3. 48
      public/app/plugins/panel/flamegraph/components/TopTable/FlameGraphTopTableContainer.tsx

@ -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;
}
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);
}
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);
}
},
[
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;
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;
}
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;
}
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…
Cancel
Save