From 780f43a33d020aff82b684cbaf8b10be872273b1 Mon Sep 17 00:00:00 2001
From: Joey Tawadrous <90795735+joey-grafana@users.noreply.github.com>
Date: Mon, 30 Jan 2023 14:02:26 +0000
Subject: [PATCH] Flame graph: Add metadata above flame graph (#61921)
* Remove percentTitle
* Flame graph metadata
* Remove comment
* Update test
* Update metadata
---
.../components/FlameGraph/FlameGraph.test.tsx | 8 ++
.../components/FlameGraph/FlameGraph.tsx | 18 ++++-
.../FlameGraph/FlameGraphMetadata.test.ts | 75 +++++++++++++++++++
.../FlameGraph/FlameGraphMetadata.tsx | 70 +++++++++++++++++
.../FlameGraph/FlameGraphTooltip.test.ts | 7 +-
.../FlameGraph/FlameGraphTooltip.tsx | 5 --
.../components/FlameGraphContainer.tsx | 6 ++
.../components/FlameGraphHeader.test.tsx | 1 +
.../components/FlameGraphHeader.tsx | 3 +
.../TopTable/FlameGraphTopTable.tsx | 17 +++--
.../FlameGraphTopTableContainer.test.tsx | 1 +
.../TopTable/FlameGraphTopTableContainer.tsx | 7 +-
.../panel/flamegraph/components/types.ts | 8 +-
13 files changed, 204 insertions(+), 22 deletions(-)
create mode 100644 public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphMetadata.test.ts
create mode 100644 public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphMetadata.tsx
diff --git a/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraph.test.tsx b/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraph.test.tsx
index ff12c0510f9..609ddab52ae 100644
--- a/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraph.test.tsx
+++ b/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraph.test.tsx
@@ -21,6 +21,7 @@ jest.mock('react-use', () => ({
describe('FlameGraph', () => {
const FlameGraphWithProps = () => {
const [topLevelIndex, setTopLevelIndex] = useState(0);
+ const [selectedBarIndex, setSelectedBarIndex] = useState(0);
const [rangeMin, setRangeMin] = useState(0);
const [rangeMax, setRangeMax] = useState(1);
const [search] = useState('');
@@ -36,10 +37,12 @@ describe('FlameGraph', () => {
app={CoreApp.Explore}
levels={levels}
topLevelIndex={topLevelIndex}
+ selectedBarIndex={selectedBarIndex}
rangeMin={rangeMin}
rangeMax={rangeMax}
search={search}
setTopLevelIndex={setTopLevelIndex}
+ setSelectedBarIndex={setSelectedBarIndex}
setRangeMin={setRangeMin}
setRangeMax={setRangeMax}
selectedView={selectedView}
@@ -59,4 +62,9 @@ describe('FlameGraph', () => {
const calls = ctx!.__getDrawCalls();
expect(calls).toMatchSnapshot();
});
+
+ it('should render metadata', async () => {
+ render();
+ expect(screen.getByText('16.5 Bil (100%) of 16,460,000,000 total samples (Count)')).toBeDefined();
+ });
});
diff --git a/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraph.tsx b/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraph.tsx
index c7b3f8e0981..bbf4dd37959 100644
--- a/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraph.tsx
+++ b/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraph.tsx
@@ -26,6 +26,7 @@ import { CoreApp, createTheme, DataFrame, FieldType, getDisplayProcessor } from
import { PIXELS_PER_LEVEL } from '../../constants';
import { TooltipData, SelectedView } from '../types';
+import FlameGraphMetadata from './FlameGraphMetadata';
import FlameGraphTooltip, { getTooltipData } from './FlameGraphTooltip';
import { ItemWithStart } from './dataTransform';
import { getBarX, getRectDimensionsForLevel, renderRect } from './rendering';
@@ -36,10 +37,12 @@ type Props = {
flameGraphHeight?: number;
levels: ItemWithStart[][];
topLevelIndex: number;
+ selectedBarIndex: number;
rangeMin: number;
rangeMax: number;
search: string;
setTopLevelIndex: (level: number) => void;
+ setSelectedBarIndex: (bar: number) => void;
setRangeMin: (range: number) => void;
setRangeMax: (range: number) => void;
selectedView: SelectedView;
@@ -52,10 +55,12 @@ const FlameGraph = ({
flameGraphHeight,
levels,
topLevelIndex,
+ selectedBarIndex,
rangeMin,
rangeMax,
search,
setTopLevelIndex,
+ setSelectedBarIndex,
setRangeMin,
setRangeMax,
selectedView,
@@ -140,6 +145,7 @@ const FlameGraph = ({
if (barIndex !== -1 && !isNaN(levelIndex) && !isNaN(barIndex)) {
setTopLevelIndex(levelIndex);
+ setSelectedBarIndex(barIndex);
setRangeMin(levels[levelIndex][barIndex].start / totalTicks);
setRangeMax((levels[levelIndex][barIndex].start + levels[levelIndex][barIndex].value) / totalTicks);
}
@@ -181,10 +187,18 @@ const FlameGraph = ({
setRangeMax,
selectedView,
valueField,
+ setSelectedBarIndex,
]);
return (
+
@@ -198,8 +212,8 @@ const getStyles = (selectedView: SelectedView, app: CoreApp, flameGraphHeight: n
overflow: scroll;
width: ${selectedView === SelectedView.FlameGraph ? '100%' : '50%'};
${app !== CoreApp.Explore
- ? `height: calc(${flameGraphHeight}px - 44px)`
- : ''}; // 44px to adjust for header pushing content down
+ ? `height: calc(${flameGraphHeight}px - 50px)`
+ : ''}; // 50px to adjust for header pushing content down
`,
});
diff --git a/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphMetadata.test.ts b/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphMetadata.test.ts
new file mode 100644
index 00000000000..9c38490df90
--- /dev/null
+++ b/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphMetadata.test.ts
@@ -0,0 +1,75 @@
+import { ArrayVector, Field, FieldType } from '@grafana/data';
+
+import { getMetadata } from './FlameGraphMetadata';
+
+describe('should get metadata correctly', () => {
+ it('for bytes', () => {
+ const metadata = getMetadata(makeField('bytes'), 1_624_078_250, 8_624_078_250);
+ expect(metadata).toEqual({
+ percentValue: 18.83,
+ unitTitle: 'RAM',
+ unitValue: '1.51 GiB',
+ samples: '8,624,078,250',
+ });
+ });
+
+ it('with default unit', () => {
+ const metadata = getMetadata(makeField('none'), 1_624_078_250, 8_624_078_250);
+ expect(metadata).toEqual({
+ percentValue: 18.83,
+ unitTitle: 'Count',
+ unitValue: '1624078250',
+ samples: '8,624,078,250',
+ });
+ });
+
+ it('without unit', () => {
+ const metadata = getMetadata(
+ {
+ name: 'test',
+ type: FieldType.number,
+ values: new ArrayVector(),
+ config: {},
+ },
+ 1_624_078_250,
+ 8_624_078_250
+ );
+ expect(metadata).toEqual({
+ percentValue: 18.83,
+ unitTitle: 'Count',
+ unitValue: '1624078250',
+ samples: '8,624,078,250',
+ });
+ });
+
+ it('for objects', () => {
+ const metadata = getMetadata(makeField('short'), 1_624_078_250, 8_624_078_250);
+ expect(metadata).toEqual({
+ percentValue: 18.83,
+ unitTitle: 'Count',
+ unitValue: '1.62 Bil',
+ samples: '8,624,078,250',
+ });
+ });
+
+ it('for nanoseconds', () => {
+ const metadata = getMetadata(makeField('ns'), 1_624_078_250, 8_624_078_250);
+ expect(metadata).toEqual({
+ percentValue: 18.83,
+ unitTitle: 'Time',
+ unitValue: '1.62 s',
+ samples: '8,624,078,250',
+ });
+ });
+});
+
+function makeField(unit: string): Field {
+ return {
+ name: 'test',
+ type: FieldType.number,
+ config: {
+ unit,
+ },
+ values: new ArrayVector(),
+ };
+}
diff --git a/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphMetadata.tsx b/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphMetadata.tsx
new file mode 100644
index 00000000000..ca0b6dab9db
--- /dev/null
+++ b/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphMetadata.tsx
@@ -0,0 +1,70 @@
+import { css } from '@emotion/css';
+import React from 'react';
+
+import { createTheme, Field, getDisplayProcessor, Vector } from '@grafana/data';
+import { useStyles2 } from '@grafana/ui';
+
+import { Metadata, SampleUnit } from '../types';
+
+import { ItemWithStart } from './dataTransform';
+
+type Props = {
+ levels: ItemWithStart[][];
+ topLevelIndex: number;
+ selectedBarIndex: number;
+ valueField: Field>;
+ totalTicks: number;
+};
+
+const FlameGraphMetadata = React.memo(({ levels, topLevelIndex, selectedBarIndex, valueField, totalTicks }: Props) => {
+ const styles = useStyles2(getStyles);
+ if (levels[topLevelIndex] && levels[topLevelIndex][selectedBarIndex]) {
+ const bar = levels[topLevelIndex][selectedBarIndex];
+ const metadata = getMetadata(valueField, bar.value, totalTicks);
+ const metadataText = `${metadata?.unitValue} (${metadata?.percentValue}%) of ${metadata?.samples} total samples (${metadata?.unitTitle})`;
+ return <>{{metadataText}
}>;
+ }
+ return <>>;
+});
+
+export const getMetadata = (field: Field, value: number, totalTicks: number): Metadata => {
+ let unitTitle;
+ const processor = getDisplayProcessor({ field, theme: createTheme() /* theme does not matter for us here */ });
+ const displayValue = processor(value);
+ const percentValue = Math.round(10000 * (value / totalTicks)) / 100;
+ let unitValue = displayValue.text + displayValue.suffix;
+
+ switch (field.config.unit) {
+ case SampleUnit.Bytes:
+ unitTitle = 'RAM';
+ break;
+ case SampleUnit.Nanoseconds:
+ unitTitle = 'Time';
+ break;
+ default:
+ unitTitle = 'Count';
+ if (!displayValue.suffix) {
+ // Makes sure we don't show 123undefined or something like that if suffix isn't defined
+ unitValue = displayValue.text;
+ }
+ break;
+ }
+
+ return {
+ percentValue,
+ unitTitle,
+ unitValue,
+ samples: totalTicks.toLocaleString(),
+ };
+};
+
+FlameGraphMetadata.displayName = 'FlameGraphMetadata';
+
+const getStyles = () => ({
+ metadata: css`
+ margin: 8px 0;
+ text-align: center;
+ `,
+});
+
+export default FlameGraphMetadata;
diff --git a/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphTooltip.test.ts b/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphTooltip.test.ts
index cc28a035673..e21266b6ba5 100644
--- a/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphTooltip.test.ts
+++ b/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphTooltip.test.ts
@@ -8,7 +8,6 @@ describe('should get tooltip data correctly', () => {
expect(tooltipData).toEqual({
name: 'total',
percentSelf: 0.01,
- percentTitle: '% of total',
percentValue: 100,
unitTitle: 'RAM',
unitSelf: '955 KiB',
@@ -17,12 +16,11 @@ describe('should get tooltip data correctly', () => {
});
});
- it('with none unit', () => {
+ it('with default unit', () => {
const tooltipData = getTooltipData(makeField('none'), 'total', 8_624_078_250, 978_250, 8_624_078_250);
expect(tooltipData).toEqual({
name: 'total',
percentSelf: 0.01,
- percentTitle: '% of total',
percentValue: 100,
unitSelf: '978250',
unitTitle: 'Count',
@@ -47,7 +45,6 @@ describe('should get tooltip data correctly', () => {
expect(tooltipData).toEqual({
name: 'total',
percentSelf: 0.01,
- percentTitle: '% of total',
percentValue: 100,
unitTitle: 'Count',
unitSelf: '978250',
@@ -61,7 +58,6 @@ describe('should get tooltip data correctly', () => {
expect(tooltipData).toEqual({
name: 'total',
percentSelf: 0.01,
- percentTitle: '% of total',
percentValue: 100,
unitTitle: 'Count',
unitSelf: '978 K',
@@ -75,7 +71,6 @@ describe('should get tooltip data correctly', () => {
expect(tooltipData).toEqual({
name: 'total',
percentSelf: 0.01,
- percentTitle: '% of total time',
percentValue: 100,
unitTitle: 'Time',
unitSelf: '978 µs',
diff --git a/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphTooltip.tsx b/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphTooltip.tsx
index af02ec7a7e9..15a782940df 100644
--- a/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphTooltip.tsx
+++ b/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraphTooltip.tsx
@@ -50,7 +50,6 @@ export const getTooltipData = (
self: number,
totalTicks: number
): TooltipData => {
- let percentTitle;
let unitTitle;
const processor = getDisplayProcessor({ field, theme: createTheme() /* theme does not matter for us here */ });
@@ -64,15 +63,12 @@ export const getTooltipData = (
switch (field.config.unit) {
case SampleUnit.Bytes:
- percentTitle = '% of total';
unitTitle = 'RAM';
break;
case SampleUnit.Nanoseconds:
- percentTitle = '% of total time';
unitTitle = 'Time';
break;
default:
- percentTitle = '% of total';
unitTitle = 'Count';
if (!displayValue.suffix) {
// Makes sure we don't show 123undefined or something like that if suffix isn't defined
@@ -87,7 +83,6 @@ export const getTooltipData = (
return {
name: label,
- percentTitle,
percentValue,
percentSelf,
unitTitle,
diff --git a/public/app/plugins/panel/flamegraph/components/FlameGraphContainer.tsx b/public/app/plugins/panel/flamegraph/components/FlameGraphContainer.tsx
index c7272440ed1..4dbc033929c 100644
--- a/public/app/plugins/panel/flamegraph/components/FlameGraphContainer.tsx
+++ b/public/app/plugins/panel/flamegraph/components/FlameGraphContainer.tsx
@@ -24,6 +24,7 @@ type Props = {
const FlameGraphContainer = (props: Props) => {
const [topLevelIndex, setTopLevelIndex] = useState(0);
+ const [selectedBarIndex, setSelectedBarIndex] = useState(0);
const [rangeMin, setRangeMin] = useState(0);
const [rangeMax, setRangeMax] = useState(1);
const [search, setSearch] = useState('');
@@ -56,6 +57,7 @@ const FlameGraphContainer = (props: Props) => {
useEffect(() => {
setTopLevelIndex(0);
+ setSelectedBarIndex(0);
setRangeMin(0);
setRangeMax(1);
}, [props.data]);
@@ -65,6 +67,7 @@ const FlameGraphContainer = (props: Props) => {
{
search={search}
setSearch={setSearch}
setTopLevelIndex={setTopLevelIndex}
+ setSelectedBarIndex={setSelectedBarIndex}
setRangeMin={setRangeMin}
setRangeMax={setRangeMax}
/>
@@ -95,10 +99,12 @@ const FlameGraphContainer = (props: Props) => {
flameGraphHeight={props.flameGraphHeight}
levels={levels}
topLevelIndex={topLevelIndex}
+ selectedBarIndex={selectedBarIndex}
rangeMin={rangeMin}
rangeMax={rangeMax}
search={search}
setTopLevelIndex={setTopLevelIndex}
+ setSelectedBarIndex={setSelectedBarIndex}
setRangeMin={setRangeMin}
setRangeMax={setRangeMax}
selectedView={selectedView}
diff --git a/public/app/plugins/panel/flamegraph/components/FlameGraphHeader.test.tsx b/public/app/plugins/panel/flamegraph/components/FlameGraphHeader.test.tsx
index b11786c7abe..78b6cefb309 100644
--- a/public/app/plugins/panel/flamegraph/components/FlameGraphHeader.test.tsx
+++ b/public/app/plugins/panel/flamegraph/components/FlameGraphHeader.test.tsx
@@ -19,6 +19,7 @@ describe('FlameGraphHeader', () => {
search={search}
setSearch={setSearch}
setTopLevelIndex={jest.fn()}
+ setSelectedBarIndex={jest.fn()}
setRangeMin={jest.fn()}
setRangeMax={jest.fn()}
selectedView={selectedView}
diff --git a/public/app/plugins/panel/flamegraph/components/FlameGraphHeader.tsx b/public/app/plugins/panel/flamegraph/components/FlameGraphHeader.tsx
index 00ee80cf5e6..c539e8f1ecf 100644
--- a/public/app/plugins/panel/flamegraph/components/FlameGraphHeader.tsx
+++ b/public/app/plugins/panel/flamegraph/components/FlameGraphHeader.tsx
@@ -12,6 +12,7 @@ type Props = {
app: CoreApp;
search: string;
setTopLevelIndex: (level: number) => void;
+ setSelectedBarIndex: (bar: number) => void;
setRangeMin: (range: number) => void;
setRangeMax: (range: number) => void;
setSearch: (search: string) => void;
@@ -24,6 +25,7 @@ const FlameGraphHeader = ({
app,
search,
setTopLevelIndex,
+ setSelectedBarIndex,
setRangeMin,
setRangeMax,
setSearch,
@@ -64,6 +66,7 @@ const FlameGraphHeader = ({
size={'md'}
onClick={() => {
setTopLevelIndex(0);
+ setSelectedBarIndex(0);
setRangeMin(0);
setRangeMax(1);
setSearch('');
diff --git a/public/app/plugins/panel/flamegraph/components/TopTable/FlameGraphTopTable.tsx b/public/app/plugins/panel/flamegraph/components/TopTable/FlameGraphTopTable.tsx
index 8f75fa32eb9..431b1999e65 100644
--- a/public/app/plugins/panel/flamegraph/components/TopTable/FlameGraphTopTable.tsx
+++ b/public/app/plugins/panel/flamegraph/components/TopTable/FlameGraphTopTable.tsx
@@ -16,6 +16,7 @@ type Props = {
search: string;
setSearch: (search: string) => void;
setTopLevelIndex: (level: number) => void;
+ setSelectedBarIndex: (bar: number) => void;
setRangeMin: (range: number) => void;
setRangeMax: (range: number) => void;
};
@@ -27,6 +28,7 @@ const FlameGraphTopTable = ({
search,
setSearch,
setTopLevelIndex,
+ setSelectedBarIndex,
setRangeMin,
setRangeMax,
}: Props) => {
@@ -84,18 +86,19 @@ const FlameGraphTopTable = ({
);
const rowClicked = useCallback(
- (row: string) => {
- if (search === row) {
+ (symbol: string) => {
+ if (search === symbol) {
setSearch('');
} else {
- setSearch(row);
+ setSearch(symbol);
// Reset selected level in flamegraph when selecting row in top table
setTopLevelIndex(0);
+ setSelectedBarIndex(0);
setRangeMin(0);
setRangeMax(1);
}
},
- [search, setRangeMax, setRangeMin, setSearch, setTopLevelIndex]
+ [search, setRangeMax, setRangeMin, setSearch, setTopLevelIndex, setSelectedBarIndex]
);
const { headerGroups, rows, prepareRow } = useTable(options, useSortBy, useAbsoluteLayout);
@@ -105,15 +108,15 @@ const FlameGraphTopTable = ({
let row = rows[index];
prepareRow(row);
- const rowValue = row.values[ColumnTypes.Symbol.toLowerCase()];
- const classNames = cx(rowValue === search && styles.matchedRow, styles.row);
+ const symbol = row.values[ColumnTypes.Symbol.toLowerCase()];
+ const classNames = cx(symbol === search && styles.matchedRow, styles.row);
return (
{
- rowClicked(rowValue);
+ rowClicked(symbol);
}}
>
{row.cells.map((cell) => {
diff --git a/public/app/plugins/panel/flamegraph/components/TopTable/FlameGraphTopTableContainer.test.tsx b/public/app/plugins/panel/flamegraph/components/TopTable/FlameGraphTopTableContainer.test.tsx
index b70a93a0895..0bf006f262c 100644
--- a/public/app/plugins/panel/flamegraph/components/TopTable/FlameGraphTopTableContainer.test.tsx
+++ b/public/app/plugins/panel/flamegraph/components/TopTable/FlameGraphTopTableContainer.test.tsx
@@ -27,6 +27,7 @@ describe('FlameGraphTopTableContainer', () => {
search={search}
setSearch={setSearch}
setTopLevelIndex={jest.fn()}
+ setSelectedBarIndex={jest.fn()}
setRangeMin={jest.fn()}
setRangeMax={jest.fn()}
/>
diff --git a/public/app/plugins/panel/flamegraph/components/TopTable/FlameGraphTopTableContainer.tsx b/public/app/plugins/panel/flamegraph/components/TopTable/FlameGraphTopTableContainer.tsx
index ae89c57c7de..1d3934897ad 100644
--- a/public/app/plugins/panel/flamegraph/components/TopTable/FlameGraphTopTableContainer.tsx
+++ b/public/app/plugins/panel/flamegraph/components/TopTable/FlameGraphTopTableContainer.tsx
@@ -18,6 +18,7 @@ type Props = {
search: string;
setSearch: (search: string) => void;
setTopLevelIndex: (level: number) => void;
+ setSelectedBarIndex: (bar: number) => void;
setRangeMin: (range: number) => void;
setRangeMax: (range: number) => void;
};
@@ -30,6 +31,7 @@ const FlameGraphTopTableContainer = ({
search,
setSearch,
setTopLevelIndex,
+ setSelectedBarIndex,
setRangeMin,
setRangeMax,
}: Props) => {
@@ -113,6 +115,7 @@ const FlameGraphTopTableContainer = ({
search={search}
setSearch={setSearch}
setTopLevelIndex={setTopLevelIndex}
+ setSelectedBarIndex={setSelectedBarIndex}
setRangeMin={setRangeMin}
setRangeMax={setRangeMax}
/>
@@ -133,7 +136,9 @@ const getStyles = (selectedView: SelectedView, app: CoreApp) => {
float: left;
margin-right: ${marginRight};
width: ${selectedView === SelectedView.TopTable ? '100%' : `calc(50% - ${marginRight})`};
- ${app !== CoreApp.Explore ? 'height: calc(100% - 44px)' : ''}; // 44px to adjust for header pushing content down
+ ${app !== CoreApp.Explore
+ ? 'height: calc(100% - 50px)'
+ : 'height: calc(100% + 50px)'}; // 50px to adjust for header pushing content down
`,
};
};
diff --git a/public/app/plugins/panel/flamegraph/components/types.ts b/public/app/plugins/panel/flamegraph/components/types.ts
index 13dbf648742..be1a0900a83 100644
--- a/public/app/plugins/panel/flamegraph/components/types.ts
+++ b/public/app/plugins/panel/flamegraph/components/types.ts
@@ -1,6 +1,5 @@
export type TooltipData = {
name: string;
- percentTitle: string;
percentValue: number;
percentSelf: number;
unitTitle: string;
@@ -9,6 +8,13 @@ export type TooltipData = {
samples: string;
};
+export type Metadata = {
+ percentValue: number;
+ unitTitle: string;
+ unitValue: string;
+ samples: string;
+};
+
export enum SampleUnit {
Bytes = 'bytes',
Short = 'short',