Flamegraph: Add diff mode color legend (#87319)

This commit is contained in:
Andrej Ocenas 2024-05-06 12:28:57 +02:00 committed by GitHub
parent 526be4fa2b
commit 9c254c7e1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 70 additions and 23 deletions

View File

@ -82,7 +82,7 @@ const FlameGraphMetadata = React.memo(
);
}
return <>{<div className={styles.metadata}>{parts}</div>}</>;
return <div className={styles.metadata}>{parts}</div>;
}
);

View File

@ -65,9 +65,9 @@ export function getBarColorByPackage(label: string, theme: GrafanaTheme2) {
}
// green to red
const diffDefaultColors = ['rgb(0, 170, 0)', 'rgb(148, 142, 142)', 'rgb(200, 0, 0)'];
export const diffDefaultColors = ['rgb(0, 170, 0)', 'rgb(148, 142, 142)', 'rgb(200, 0, 0)'];
export const diffDefaultGradient = `linear-gradient(90deg, ${diffDefaultColors[0]} 0%, ${diffDefaultColors[1]} 50%, ${diffDefaultColors[2]} 100%)`;
const diffColorBlindColors = ['rgb(26, 133, 255)', 'rgb(148, 142, 142)', 'rgb(220, 50, 32)'];
export const diffColorBlindColors = ['rgb(26, 133, 255)', 'rgb(148, 142, 142)', 'rgb(220, 50, 32)'];
export const diffColorBlindGradient = `linear-gradient(90deg, ${diffColorBlindColors[0]} 0%, ${diffColorBlindColors[1]} 50%, ${diffColorBlindColors[2]} 100%)`;
export function getBarColorByDiff(

View File

@ -192,6 +192,7 @@ const FlameGraphContainer = ({
onSandwich={setSandwichItem}
onSearch={setSearch}
onTableSort={onTableSort}
colorScheme={colorScheme}
/>
);

View File

@ -129,15 +129,6 @@ function ColorSchemeButton(props: ColorSchemeButtonProps) {
</Menu>
);
if (props.isDiffMode) {
menu = (
<Menu>
<Menu.Item label="Default (green to red)" onClick={() => props.onChange(ColorSchemeDiff.Default)} />
<Menu.Item label="Color blind (blue to red)" onClick={() => props.onChange(ColorSchemeDiff.DiffColorBlind)} />
</Menu>
);
}
// Show a bit different gradient as a way to indicate selected value
const colorDotStyle =
{
@ -147,6 +138,25 @@ function ColorSchemeButton(props: ColorSchemeButtonProps) {
[ColorSchemeDiff.Default]: styles.colorDotDiffDefault,
}[props.value] || styles.colorDotByValue;
let contents = <span className={cx(styles.colorDot, colorDotStyle)} />;
if (props.isDiffMode) {
menu = (
<Menu>
<Menu.Item label="Default (green to red)" onClick={() => props.onChange(ColorSchemeDiff.Default)} />
<Menu.Item label="Color blind (blue to red)" onClick={() => props.onChange(ColorSchemeDiff.DiffColorBlind)} />
</Menu>
);
contents = (
<div className={cx(styles.colorDotDiff, colorDotStyle)}>
<div>-100% (removed)</div>
<div>0%</div>
<div>+100% (added)</div>
</div>
);
}
return (
<Dropdown overlay={menu}>
<Button
@ -158,7 +168,7 @@ function ColorSchemeButton(props: ColorSchemeButtonProps) {
className={styles.buttonSpacing}
aria-label={'Change color scheme'}
>
<span className={cx(styles.colorDot, colorDotStyle)} />
{contents}
</Button>
</Dropdown>
);
@ -221,17 +231,16 @@ const getStyles = (theme: GrafanaTheme2) => ({
justifyContent: 'space-between',
width: '100%',
top: 0,
gap: theme.spacing(1),
marginTop: theme.spacing(1),
}),
stickyHeader: css({
zIndex: theme.zIndex.navbarFixed,
position: 'sticky',
paddingBottom: theme.spacing(1),
paddingTop: theme.spacing(1),
background: theme.colors.background.primary,
}),
inputContainer: css({
label: 'inputContainer',
marginRight: '20px',
flexGrow: 1,
minWidth: '150px',
maxWidth: '350px',
@ -264,6 +273,21 @@ const getStyles = (theme: GrafanaTheme2) => ({
// eslint-disable-next-line @grafana/no-border-radius-literal
borderRadius: '50%',
}),
colorDotDiff: css({
label: 'colorDotDiff',
display: 'flex',
width: '200px',
height: '12px',
color: 'white',
fontSize: 9,
lineHeight: 1.3,
fontWeight: 300,
justifyContent: 'space-between',
padding: '0 2px',
// We have a specific sizing for this so probably makes sense to use hardcoded value here
// eslint-disable-next-line @grafana/no-border-radius-literal
borderRadius: '2px',
}),
colorDotByValue: css({
label: 'colorDotByValue',
background: byValueGradient,

View File

@ -6,6 +6,7 @@ import { createDataFrame } from '@grafana/data';
import { FlameGraphDataContainer } from '../FlameGraph/dataTransform';
import { data } from '../FlameGraph/testData/dataNestedSet';
import { ColorScheme } from '../types';
import FlameGraphTopTableContainer from './FlameGraphTopTableContainer';
@ -22,6 +23,7 @@ describe('FlameGraphTopTableContainer', () => {
onSymbolClick={jest.fn()}
onSearch={onSearch}
onSandwich={onSandwich}
colorScheme={ColorScheme.ValueBased}
/>
);

View File

@ -22,9 +22,10 @@ import {
useTheme2,
} from '@grafana/ui';
import { diffColorBlindColors, diffDefaultColors } from '../FlameGraph/colors';
import { FlameGraphDataContainer } from '../FlameGraph/dataTransform';
import { TOP_TABLE_COLUMN_WIDTH } from '../constants';
import { TableData } from '../types';
import { ColorScheme, ColorSchemeDiff, TableData } from '../types';
type Props = {
data: FlameGraphDataContainer;
@ -37,10 +38,21 @@ type Props = {
onSearch: (str: string) => void;
onSandwich: (str?: string) => void;
onTableSort?: (sort: string) => void;
colorScheme: ColorScheme | ColorSchemeDiff;
};
const FlameGraphTopTableContainer = React.memo(
({ data, onSymbolClick, search, matchedLabels, onSearch, sandwichItem, onSandwich, onTableSort }: Props) => {
({
data,
onSymbolClick,
search,
matchedLabels,
onSearch,
sandwichItem,
onSandwich,
onTableSort,
colorScheme,
}: Props) => {
const table = useMemo(() => {
// Group the data by label, we show only one row per label and sum the values
// TODO: should be by filename + funcName + linenumber?
@ -51,7 +63,7 @@ const FlameGraphTopTableContainer = React.memo(
const self = data.getSelf(i);
const label = data.getLabel(i);
// If user is doing text search we filter out labels in the same way we highlight them in flamegraph.
// If user is doing text search we filter out labels in the same way we highlight them in flame graph.
if (!matchedLabels || matchedLabels.has(label)) {
filteredTable[label] = filteredTable[label] || {};
filteredTable[label].self = filteredTable[label].self ? filteredTable[label].self + self : self;
@ -85,6 +97,7 @@ const FlameGraphTopTableContainer = React.memo(
onSearch,
onSandwich,
theme,
colorScheme,
search,
sandwichItem
);
@ -119,6 +132,7 @@ function buildTableDataFrame(
onSearch: (str: string) => void,
onSandwich: (str?: string) => void,
theme: GrafanaTheme2,
colorScheme: ColorScheme | ColorSchemeDiff,
search?: string,
sandwichItem?: string
): DataFrame {
@ -153,11 +167,17 @@ function buildTableDataFrame(
const comparisonField = createNumberField('Comparison', 'percent');
const diffField = createNumberField('Diff', 'percent');
diffField.config.custom.cellOptions.type = TableCellDisplayMode.ColorText;
const [removeColor, addColor] =
colorScheme === ColorSchemeDiff.DiffColorBlind
? [diffColorBlindColors[0], diffColorBlindColors[2]]
: [diffDefaultColors[0], diffDefaultColors[2]];
diffField.config.mappings = [
{ type: MappingType.ValueToText, options: { [Infinity]: { text: 'new', color: 'red' } } },
{ type: MappingType.ValueToText, options: { [-100]: { text: 'removed', color: 'green' } } },
{ type: MappingType.RangeToText, options: { from: 0, to: Infinity, result: { color: 'red' } } },
{ type: MappingType.RangeToText, options: { from: -Infinity, to: 0, result: { color: 'green' } } },
{ type: MappingType.ValueToText, options: { [Infinity]: { text: 'new', color: addColor } } },
{ type: MappingType.ValueToText, options: { [-100]: { text: 'removed', color: removeColor } } },
{ type: MappingType.RangeToText, options: { from: 0, to: Infinity, result: { color: addColor } } },
{ type: MappingType.RangeToText, options: { from: -Infinity, to: 0, result: { color: removeColor } } },
];
// For this we don't really consider sandwich view even though you can switch it on.