mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Flamegraph: Add diff mode color legend (#87319)
This commit is contained in:
parent
526be4fa2b
commit
9c254c7e1e
@ -82,7 +82,7 @@ const FlameGraphMetadata = React.memo(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>{<div className={styles.metadata}>{parts}</div>}</>;
|
return <div className={styles.metadata}>{parts}</div>;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -65,9 +65,9 @@ export function getBarColorByPackage(label: string, theme: GrafanaTheme2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// green to red
|
// 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%)`;
|
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 const diffColorBlindGradient = `linear-gradient(90deg, ${diffColorBlindColors[0]} 0%, ${diffColorBlindColors[1]} 50%, ${diffColorBlindColors[2]} 100%)`;
|
||||||
|
|
||||||
export function getBarColorByDiff(
|
export function getBarColorByDiff(
|
||||||
|
@ -192,6 +192,7 @@ const FlameGraphContainer = ({
|
|||||||
onSandwich={setSandwichItem}
|
onSandwich={setSandwichItem}
|
||||||
onSearch={setSearch}
|
onSearch={setSearch}
|
||||||
onTableSort={onTableSort}
|
onTableSort={onTableSort}
|
||||||
|
colorScheme={colorScheme}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -129,15 +129,6 @@ function ColorSchemeButton(props: ColorSchemeButtonProps) {
|
|||||||
</Menu>
|
</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
|
// Show a bit different gradient as a way to indicate selected value
|
||||||
const colorDotStyle =
|
const colorDotStyle =
|
||||||
{
|
{
|
||||||
@ -147,6 +138,25 @@ function ColorSchemeButton(props: ColorSchemeButtonProps) {
|
|||||||
[ColorSchemeDiff.Default]: styles.colorDotDiffDefault,
|
[ColorSchemeDiff.Default]: styles.colorDotDiffDefault,
|
||||||
}[props.value] || styles.colorDotByValue;
|
}[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 (
|
return (
|
||||||
<Dropdown overlay={menu}>
|
<Dropdown overlay={menu}>
|
||||||
<Button
|
<Button
|
||||||
@ -158,7 +168,7 @@ function ColorSchemeButton(props: ColorSchemeButtonProps) {
|
|||||||
className={styles.buttonSpacing}
|
className={styles.buttonSpacing}
|
||||||
aria-label={'Change color scheme'}
|
aria-label={'Change color scheme'}
|
||||||
>
|
>
|
||||||
<span className={cx(styles.colorDot, colorDotStyle)} />
|
{contents}
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
@ -221,17 +231,16 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
top: 0,
|
top: 0,
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
marginTop: theme.spacing(1),
|
||||||
}),
|
}),
|
||||||
stickyHeader: css({
|
stickyHeader: css({
|
||||||
zIndex: theme.zIndex.navbarFixed,
|
zIndex: theme.zIndex.navbarFixed,
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
paddingBottom: theme.spacing(1),
|
|
||||||
paddingTop: theme.spacing(1),
|
|
||||||
background: theme.colors.background.primary,
|
background: theme.colors.background.primary,
|
||||||
}),
|
}),
|
||||||
inputContainer: css({
|
inputContainer: css({
|
||||||
label: 'inputContainer',
|
label: 'inputContainer',
|
||||||
marginRight: '20px',
|
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
minWidth: '150px',
|
minWidth: '150px',
|
||||||
maxWidth: '350px',
|
maxWidth: '350px',
|
||||||
@ -264,6 +273,21 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
// eslint-disable-next-line @grafana/no-border-radius-literal
|
// eslint-disable-next-line @grafana/no-border-radius-literal
|
||||||
borderRadius: '50%',
|
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({
|
colorDotByValue: css({
|
||||||
label: 'colorDotByValue',
|
label: 'colorDotByValue',
|
||||||
background: byValueGradient,
|
background: byValueGradient,
|
||||||
|
@ -6,6 +6,7 @@ import { createDataFrame } from '@grafana/data';
|
|||||||
|
|
||||||
import { FlameGraphDataContainer } from '../FlameGraph/dataTransform';
|
import { FlameGraphDataContainer } from '../FlameGraph/dataTransform';
|
||||||
import { data } from '../FlameGraph/testData/dataNestedSet';
|
import { data } from '../FlameGraph/testData/dataNestedSet';
|
||||||
|
import { ColorScheme } from '../types';
|
||||||
|
|
||||||
import FlameGraphTopTableContainer from './FlameGraphTopTableContainer';
|
import FlameGraphTopTableContainer from './FlameGraphTopTableContainer';
|
||||||
|
|
||||||
@ -22,6 +23,7 @@ describe('FlameGraphTopTableContainer', () => {
|
|||||||
onSymbolClick={jest.fn()}
|
onSymbolClick={jest.fn()}
|
||||||
onSearch={onSearch}
|
onSearch={onSearch}
|
||||||
onSandwich={onSandwich}
|
onSandwich={onSandwich}
|
||||||
|
colorScheme={ColorScheme.ValueBased}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -22,9 +22,10 @@ import {
|
|||||||
useTheme2,
|
useTheme2,
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
|
|
||||||
|
import { diffColorBlindColors, diffDefaultColors } from '../FlameGraph/colors';
|
||||||
import { FlameGraphDataContainer } from '../FlameGraph/dataTransform';
|
import { FlameGraphDataContainer } from '../FlameGraph/dataTransform';
|
||||||
import { TOP_TABLE_COLUMN_WIDTH } from '../constants';
|
import { TOP_TABLE_COLUMN_WIDTH } from '../constants';
|
||||||
import { TableData } from '../types';
|
import { ColorScheme, ColorSchemeDiff, TableData } from '../types';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
data: FlameGraphDataContainer;
|
data: FlameGraphDataContainer;
|
||||||
@ -37,10 +38,21 @@ type Props = {
|
|||||||
onSearch: (str: string) => void;
|
onSearch: (str: string) => void;
|
||||||
onSandwich: (str?: string) => void;
|
onSandwich: (str?: string) => void;
|
||||||
onTableSort?: (sort: string) => void;
|
onTableSort?: (sort: string) => void;
|
||||||
|
colorScheme: ColorScheme | ColorSchemeDiff;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FlameGraphTopTableContainer = React.memo(
|
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(() => {
|
const table = useMemo(() => {
|
||||||
// Group the data by label, we show only one row per label and sum the values
|
// Group the data by label, we show only one row per label and sum the values
|
||||||
// TODO: should be by filename + funcName + linenumber?
|
// TODO: should be by filename + funcName + linenumber?
|
||||||
@ -51,7 +63,7 @@ const FlameGraphTopTableContainer = React.memo(
|
|||||||
const self = data.getSelf(i);
|
const self = data.getSelf(i);
|
||||||
const label = data.getLabel(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)) {
|
if (!matchedLabels || matchedLabels.has(label)) {
|
||||||
filteredTable[label] = filteredTable[label] || {};
|
filteredTable[label] = filteredTable[label] || {};
|
||||||
filteredTable[label].self = filteredTable[label].self ? filteredTable[label].self + self : self;
|
filteredTable[label].self = filteredTable[label].self ? filteredTable[label].self + self : self;
|
||||||
@ -85,6 +97,7 @@ const FlameGraphTopTableContainer = React.memo(
|
|||||||
onSearch,
|
onSearch,
|
||||||
onSandwich,
|
onSandwich,
|
||||||
theme,
|
theme,
|
||||||
|
colorScheme,
|
||||||
search,
|
search,
|
||||||
sandwichItem
|
sandwichItem
|
||||||
);
|
);
|
||||||
@ -119,6 +132,7 @@ function buildTableDataFrame(
|
|||||||
onSearch: (str: string) => void,
|
onSearch: (str: string) => void,
|
||||||
onSandwich: (str?: string) => void,
|
onSandwich: (str?: string) => void,
|
||||||
theme: GrafanaTheme2,
|
theme: GrafanaTheme2,
|
||||||
|
colorScheme: ColorScheme | ColorSchemeDiff,
|
||||||
search?: string,
|
search?: string,
|
||||||
sandwichItem?: string
|
sandwichItem?: string
|
||||||
): DataFrame {
|
): DataFrame {
|
||||||
@ -153,11 +167,17 @@ function buildTableDataFrame(
|
|||||||
const comparisonField = createNumberField('Comparison', 'percent');
|
const comparisonField = createNumberField('Comparison', 'percent');
|
||||||
const diffField = createNumberField('Diff', 'percent');
|
const diffField = createNumberField('Diff', 'percent');
|
||||||
diffField.config.custom.cellOptions.type = TableCellDisplayMode.ColorText;
|
diffField.config.custom.cellOptions.type = TableCellDisplayMode.ColorText;
|
||||||
|
|
||||||
|
const [removeColor, addColor] =
|
||||||
|
colorScheme === ColorSchemeDiff.DiffColorBlind
|
||||||
|
? [diffColorBlindColors[0], diffColorBlindColors[2]]
|
||||||
|
: [diffDefaultColors[0], diffDefaultColors[2]];
|
||||||
|
|
||||||
diffField.config.mappings = [
|
diffField.config.mappings = [
|
||||||
{ type: MappingType.ValueToText, options: { [Infinity]: { text: 'new', color: 'red' } } },
|
{ type: MappingType.ValueToText, options: { [Infinity]: { text: 'new', color: addColor } } },
|
||||||
{ type: MappingType.ValueToText, options: { [-100]: { text: 'removed', color: 'green' } } },
|
{ type: MappingType.ValueToText, options: { [-100]: { text: 'removed', color: removeColor } } },
|
||||||
{ type: MappingType.RangeToText, options: { from: 0, to: Infinity, result: { color: 'red' } } },
|
{ type: MappingType.RangeToText, options: { from: 0, to: Infinity, result: { color: addColor } } },
|
||||||
{ type: MappingType.RangeToText, options: { from: -Infinity, to: 0, result: { color: 'green' } } },
|
{ 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.
|
// For this we don't really consider sandwich view even though you can switch it on.
|
||||||
|
Loading…
Reference in New Issue
Block a user