mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Profiling] Flamegraph legend (#147910)
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Closes https://github.com/elastic/prodfiler/issues/2810
This commit is contained in:
parent
ed9987592e
commit
08d85554b9
7 changed files with 252 additions and 59 deletions
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { asPercentage } from '../../utils/formatters/as_percentage';
|
||||
import { Legend, LegendItem } from '../legend';
|
||||
|
||||
export function FlameGraphLegend({
|
||||
legendItems,
|
||||
asScale,
|
||||
}: {
|
||||
legendItems: LegendItem[];
|
||||
asScale: boolean;
|
||||
}) {
|
||||
if (asScale) {
|
||||
return (
|
||||
<EuiFlexGroup direction="column" alignItems="flexStart">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup direction="row">
|
||||
<EuiFlexItem>
|
||||
<EuiText textAlign="center" size="s">
|
||||
{i18n.translate('xpack.profiling.flameGraphLegend.improvement', {
|
||||
defaultMessage: 'Improvement',
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText textAlign="center" size="s">
|
||||
{i18n.translate('xpack.profiling.flameGraphLegend.regression', {
|
||||
defaultMessage: 'Regression',
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="row">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup direction="row" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">+{asPercentage(1)}</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ width: legendItems.length * 20 }}>
|
||||
<EuiFlexGroup direction="row" gutterSize="none">
|
||||
{legendItems.map(({ color, label }) => {
|
||||
return (
|
||||
<EuiFlexItem
|
||||
style={{ backgroundColor: color, justifyContent: 'center' }}
|
||||
>
|
||||
{label ? (
|
||||
<EuiText
|
||||
size="xs"
|
||||
style={{
|
||||
verticalAlign: 'center',
|
||||
whiteSpace: 'nowrap',
|
||||
paddingLeft: 8,
|
||||
paddingRight: 8,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</EuiText>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">{asPercentage(-1)}</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return <Legend legendItems={legendItems} />;
|
||||
}
|
|
@ -24,6 +24,7 @@ import { ElasticFlameGraph, FlameGraphComparisonMode } from '../../common/flameg
|
|||
import { asPercentage } from '../utils/formatters/as_percentage';
|
||||
import { getFlamegraphModel } from '../utils/get_flamegraph_model';
|
||||
import { FlamegraphInformationWindow } from './flame_graphs_view/flamegraph_information_window';
|
||||
import { FlameGraphLegend } from './flame_graphs_view/flame_graph_legend';
|
||||
|
||||
function TooltipRow({
|
||||
value,
|
||||
|
@ -321,6 +322,9 @@ export const FlameGraph: React.FC<FlameGraphProps> = ({
|
|||
) : undefined}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<FlameGraphLegend legendItems={columnarData.legendItems} asScale={!!comparisonFlamegraph} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
|
34
x-pack/plugins/profiling/public/components/legend.tsx
Normal file
34
x-pack/plugins/profiling/public/components/legend.tsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
export interface LegendItem {
|
||||
color: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export function Legend({ legendItems }: { legendItems: LegendItem[] }) {
|
||||
return (
|
||||
<EuiFlexGroup direction="row" gutterSize="m">
|
||||
{legendItems.map(({ color, label }) => {
|
||||
return (
|
||||
<EuiFlexItem key={label} grow={false}>
|
||||
<EuiFlexGroup direction="row" gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="dot" color={color} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="xs">{label}</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -4,10 +4,14 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { ColumnarViewModel } from '@elastic/charts';
|
||||
import d3 from 'd3';
|
||||
import { sum, uniqueId } from 'lodash';
|
||||
import { createColumnarViewModel, rgbToRGBA } from '../../../common/columnar_view_model';
|
||||
import { compact, sum, uniqueId, range } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { createColumnarViewModel } from '../../../common/columnar_view_model';
|
||||
import { ElasticFlameGraph, FlameGraphComparisonMode } from '../../../common/flamegraph';
|
||||
import { FRAME_TYPE_COLOR_MAP, rgbToRGBA } from '../../../common/frame_type_colors';
|
||||
import { describeFrameType, FrameType } from '../../../common/profiling';
|
||||
import { getInterpolationValue } from './get_interpolation_value';
|
||||
|
||||
const nullColumnarViewModel = {
|
||||
|
@ -34,17 +38,72 @@ export function getFlamegraphModel({
|
|||
colorDanger: string;
|
||||
colorNeutral: string;
|
||||
comparisonMode: FlameGraphComparisonMode;
|
||||
}) {
|
||||
}): {
|
||||
key: string;
|
||||
viewModel: ColumnarViewModel;
|
||||
comparisonNodesById: Record<string, { CountInclusive: number; CountExclusive: number }>;
|
||||
legendItems: Array<{ label: string; color: string }>;
|
||||
} {
|
||||
const comparisonNodesById: Record<string, { CountInclusive: number; CountExclusive: number }> =
|
||||
{};
|
||||
|
||||
if (!primaryFlamegraph || !primaryFlamegraph.Label || primaryFlamegraph.Label.length === 0) {
|
||||
return { key: uniqueId(), viewModel: nullColumnarViewModel, comparisonNodesById };
|
||||
return {
|
||||
key: uniqueId(),
|
||||
viewModel: nullColumnarViewModel,
|
||||
comparisonNodesById,
|
||||
legendItems: [],
|
||||
};
|
||||
}
|
||||
|
||||
const viewModel = createColumnarViewModel(primaryFlamegraph, comparisonFlamegraph === undefined);
|
||||
|
||||
if (comparisonFlamegraph) {
|
||||
let legendItems: Array<{ label: string; color: string }>;
|
||||
|
||||
if (!comparisonFlamegraph) {
|
||||
const usedFrameTypes = new Set([...primaryFlamegraph.FrameType]);
|
||||
legendItems = compact(
|
||||
Object.entries(FRAME_TYPE_COLOR_MAP).map(([frameTypeKey, colors]) => {
|
||||
const frameType = Number(frameTypeKey) as FrameType;
|
||||
|
||||
return usedFrameTypes.has(frameType)
|
||||
? {
|
||||
color: `#${colors[0].toString(16)}`,
|
||||
label: describeFrameType(frameType),
|
||||
}
|
||||
: undefined;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const positiveChangeInterpolator = d3.interpolateRgb(colorNeutral, colorSuccess);
|
||||
|
||||
const negativeChangeInterpolator = d3.interpolateRgb(colorNeutral, colorDanger);
|
||||
|
||||
function getColor(interpolationValue: number) {
|
||||
const nodeColor =
|
||||
interpolationValue >= 0
|
||||
? positiveChangeInterpolator(interpolationValue)
|
||||
: negativeChangeInterpolator(Math.abs(interpolationValue));
|
||||
|
||||
return nodeColor;
|
||||
}
|
||||
|
||||
legendItems = range(1, -1, -0.2)
|
||||
.concat(-1)
|
||||
.map((value) => {
|
||||
const rounded = Math.round(value * 100) / 100;
|
||||
const color = getColor(rounded);
|
||||
return {
|
||||
color,
|
||||
label:
|
||||
rounded === 0
|
||||
? i18n.translate('xpack.profiling.flamegraphModel.noChange', {
|
||||
defaultMessage: 'No change',
|
||||
})
|
||||
: '',
|
||||
};
|
||||
});
|
||||
|
||||
comparisonFlamegraph.ID.forEach((nodeID, index) => {
|
||||
comparisonNodesById[nodeID] = {
|
||||
CountInclusive: comparisonFlamegraph.CountInclusive[index],
|
||||
|
@ -52,10 +111,6 @@ export function getFlamegraphModel({
|
|||
};
|
||||
});
|
||||
|
||||
const positiveChangeInterpolator = d3.interpolateRgb(colorNeutral, colorSuccess);
|
||||
|
||||
const negativeChangeInterpolator = d3.interpolateRgb(colorNeutral, colorDanger);
|
||||
|
||||
// per @thomasdullien:
|
||||
// In "relative" mode: Take the percentage of CPU time consumed by block A and subtract
|
||||
// the percentage of CPU time consumed by block B. If the number is positive, linearly
|
||||
|
@ -100,10 +155,7 @@ export function getFlamegraphModel({
|
|||
denominator
|
||||
);
|
||||
|
||||
const nodeColor =
|
||||
interpolationValue >= 0
|
||||
? positiveChangeInterpolator(interpolationValue)
|
||||
: negativeChangeInterpolator(Math.abs(interpolationValue));
|
||||
const nodeColor = getColor(interpolationValue);
|
||||
|
||||
const rgba = rgbToRGBA(Number(nodeColor.replace('#', '0x')));
|
||||
viewModel.color.set(rgba, 4 * index);
|
||||
|
@ -114,5 +166,6 @@ export function getFlamegraphModel({
|
|||
key: uniqueId(),
|
||||
viewModel,
|
||||
comparisonNodesById,
|
||||
legendItems,
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue