mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Profiling] Show values of highlighted sample in TopN chart (#147431)
Closes https://github.com/elastic/prodfiler/issues/2842
This commit is contained in:
parent
9f54ad46c6
commit
d01b5de252
6 changed files with 106 additions and 41 deletions
|
@ -19,11 +19,13 @@ export const OTHER_BUCKET_LABEL = i18n.translate('xpack.profiling.topn.otherBuck
|
|||
|
||||
export interface CountPerTime {
|
||||
Timestamp: number;
|
||||
Percentage: number;
|
||||
Count: number | null;
|
||||
}
|
||||
|
||||
export interface TopNSample extends CountPerTime {
|
||||
Category: string;
|
||||
Percentage: number;
|
||||
}
|
||||
|
||||
export interface TopNSamples {
|
||||
|
@ -157,6 +159,8 @@ export function createTopNSamples(
|
|||
// from the total
|
||||
const otherFrameCountsByTimestamp = new Map<number, number>();
|
||||
|
||||
const totalFrameCountsByTimestamp = new Map<number, number>();
|
||||
|
||||
let addOtherBucket = false;
|
||||
|
||||
for (let i = 0; i < response.over_time.buckets.length; i++) {
|
||||
|
@ -170,6 +174,8 @@ export function createTopNSamples(
|
|||
}
|
||||
|
||||
otherFrameCountsByTimestamp.set(timestamp, valueForOtherBucket);
|
||||
|
||||
totalFrameCountsByTimestamp.set(timestamp, bucket.count.value ?? 0);
|
||||
}
|
||||
|
||||
// Only add the 'other' bucket if at least one value per timestamp is > 0
|
||||
|
@ -190,10 +196,12 @@ export function createTopNSamples(
|
|||
for (const category of bucketsByCategories.keys()) {
|
||||
const frameCountsByTimestamp = bucketsByCategories.get(category);
|
||||
for (const timestamp of timestamps) {
|
||||
const count = frameCountsByTimestamp.get(timestamp) ?? 0;
|
||||
const sample: TopNSample = {
|
||||
Timestamp: timestamp,
|
||||
Count: frameCountsByTimestamp.get(timestamp) ?? 0,
|
||||
Count: count,
|
||||
Category: category,
|
||||
Percentage: (count / totalFrameCountsByTimestamp.get(timestamp)!) * 100,
|
||||
};
|
||||
samples.push(sample);
|
||||
}
|
||||
|
@ -233,6 +241,7 @@ export function groupSamplesByCategory({
|
|||
}
|
||||
const series = seriesByCategory.get(sample.Category)!;
|
||||
series.push({
|
||||
Percentage: sample.Percentage,
|
||||
Timestamp: sample.Timestamp,
|
||||
Count: sample.Count,
|
||||
});
|
||||
|
|
|
@ -44,6 +44,7 @@ export const ChartGrid: React.FC<ChartGridProps> = ({ limit, charts, showFrames
|
|||
metadata={subchart.Metadata}
|
||||
height={200}
|
||||
data={subchart.Series}
|
||||
sample={null}
|
||||
showAxes
|
||||
onShowMoreClick={() => {
|
||||
setSelectedSubchart(subchart);
|
||||
|
@ -71,6 +72,7 @@ export const ChartGrid: React.FC<ChartGridProps> = ({ limit, charts, showFrames
|
|||
metadata={selectedSubchart.Metadata}
|
||||
height={200}
|
||||
data={selectedSubchart.Series}
|
||||
sample={null}
|
||||
showAxes
|
||||
onShowMoreClick={null}
|
||||
showFrames={showFrames}
|
||||
|
|
|
@ -8,7 +8,7 @@ import { EuiButton, EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiPanel } from '
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useState } from 'react';
|
||||
import { StackTracesDisplayOption, TopNType } from '../../../common/stack_traces';
|
||||
import { groupSamplesByCategory, TopNResponse, TopNSubchart } from '../../../common/topn';
|
||||
import { groupSamplesByCategory, TopNResponse, TopNSample } from '../../../common/topn';
|
||||
import { useProfilingParams } from '../../hooks/use_profiling_params';
|
||||
import { useProfilingRouter } from '../../hooks/use_profiling_router';
|
||||
import { useProfilingRoutePath } from '../../hooks/use_profiling_route_path';
|
||||
|
@ -78,9 +78,12 @@ export function StackTracesView() {
|
|||
[topNType, timeRange.start, timeRange.end, fetchTopN, kuery]
|
||||
);
|
||||
|
||||
const [highlightedSubchart, setHighlightedSubchart] = useState<TopNSubchart | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [highlightedSample, setHighlightedSample] = useState<TopNSample | null>(null);
|
||||
|
||||
const highlightedSubchart =
|
||||
(highlightedSample &&
|
||||
state.data?.charts.find((chart) => chart.Category === highlightedSample?.Category)) ||
|
||||
null;
|
||||
|
||||
const { data } = state;
|
||||
|
||||
|
@ -143,14 +146,13 @@ export function StackTracesView() {
|
|||
},
|
||||
});
|
||||
}}
|
||||
onSampleClick={(sample) => {
|
||||
setHighlightedSubchart(
|
||||
data?.charts.find((subchart) => subchart.Category === sample.Category)
|
||||
);
|
||||
onSampleOver={(sample) => {
|
||||
setHighlightedSample(sample);
|
||||
}}
|
||||
onSampleOut={() => {
|
||||
setHighlightedSubchart(undefined);
|
||||
setHighlightedSample(null);
|
||||
}}
|
||||
highlightedSample={highlightedSample}
|
||||
highlightedSubchart={highlightedSubchart}
|
||||
showFrames={topNType === TopNType.Traces}
|
||||
/>
|
||||
|
|
|
@ -26,10 +26,15 @@ import { useProfilingChartsTheme } from '../hooks/use_profiling_charts_theme';
|
|||
import { asPercentage } from '../utils/formatters/as_percentage';
|
||||
import { SubChart } from './subchart';
|
||||
|
||||
function SubchartTooltip({
|
||||
const SubchartTooltip = ({
|
||||
highlightedSubchart,
|
||||
highlightedSample,
|
||||
showFrames,
|
||||
}: TooltipInfo & { highlightedSubchart: TopNSubchart; showFrames: boolean }) {
|
||||
}: TooltipInfo & {
|
||||
highlightedSubchart: TopNSubchart;
|
||||
highlightedSample: TopNSample | null;
|
||||
showFrames: boolean;
|
||||
}) => {
|
||||
// max tooltip width - 2 * padding (16px)
|
||||
const width = 224;
|
||||
return (
|
||||
|
@ -39,8 +44,9 @@ function SubchartTooltip({
|
|||
color={highlightedSubchart.Color}
|
||||
category={highlightedSubchart.Category}
|
||||
label={highlightedSubchart.Label}
|
||||
percentage={highlightedSubchart.Percentage}
|
||||
data={highlightedSubchart.Series}
|
||||
percentage={highlightedSubchart.Percentage}
|
||||
sample={highlightedSample}
|
||||
showFrames={showFrames}
|
||||
/* we don't show metadata in tooltips */
|
||||
metadata={[]}
|
||||
|
@ -52,15 +58,16 @@ function SubchartTooltip({
|
|||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export interface StackedBarChartProps {
|
||||
height: number;
|
||||
asPercentages: boolean;
|
||||
onBrushEnd: (range: { rangeFrom: string; rangeTo: string }) => void;
|
||||
onSampleClick: (sample: TopNSample) => void;
|
||||
onSampleOver: (sample: TopNSample) => void;
|
||||
onSampleOut: () => void;
|
||||
highlightedSubchart?: TopNSubchart;
|
||||
highlightedSample: TopNSample | null;
|
||||
highlightedSubchart: TopNSubchart | null;
|
||||
charts: TopNSubchart[];
|
||||
showFrames: boolean;
|
||||
}
|
||||
|
@ -69,8 +76,9 @@ export const StackedBarChart: React.FC<StackedBarChartProps> = ({
|
|||
height,
|
||||
asPercentages,
|
||||
onBrushEnd,
|
||||
onSampleClick,
|
||||
onSampleOut,
|
||||
onSampleOver,
|
||||
highlightedSample,
|
||||
highlightedSubchart,
|
||||
charts,
|
||||
showFrames,
|
||||
|
@ -79,6 +87,17 @@ export const StackedBarChart: React.FC<StackedBarChartProps> = ({
|
|||
|
||||
const { chartsBaseTheme, chartsTheme } = useProfilingChartsTheme();
|
||||
|
||||
const customTooltip = highlightedSubchart
|
||||
? (props: TooltipInfo) => (
|
||||
<SubchartTooltip
|
||||
{...props}
|
||||
highlightedSubchart={highlightedSubchart!}
|
||||
highlightedSample={highlightedSample}
|
||||
showFrames={showFrames}
|
||||
/>
|
||||
)
|
||||
: () => <></>;
|
||||
|
||||
return (
|
||||
<Chart size={{ height }}>
|
||||
<Settings
|
||||
|
@ -92,30 +111,15 @@ export const StackedBarChart: React.FC<StackedBarChartProps> = ({
|
|||
}}
|
||||
baseTheme={chartsBaseTheme}
|
||||
theme={chartsTheme}
|
||||
onElementClick={(events) => {
|
||||
onElementOver={(events) => {
|
||||
const [value] = events[0] as XYChartElementEvent;
|
||||
onSampleClick(value.datum as TopNSample);
|
||||
}}
|
||||
onElementOver={() => {
|
||||
onSampleOut();
|
||||
onSampleOver(value.datum as TopNSample);
|
||||
}}
|
||||
onElementOut={() => {
|
||||
onSampleOut();
|
||||
}}
|
||||
/>
|
||||
<Tooltip
|
||||
customTooltip={
|
||||
highlightedSubchart
|
||||
? (props) => (
|
||||
<SubchartTooltip
|
||||
{...props}
|
||||
showFrames={showFrames}
|
||||
highlightedSubchart={highlightedSubchart}
|
||||
/>
|
||||
)
|
||||
: () => <></>
|
||||
}
|
||||
/>
|
||||
<Tooltip customTooltip={customTooltip} />
|
||||
{charts.map((chart) => (
|
||||
<HistogramBarSeries
|
||||
key={chart.Category}
|
||||
|
|
|
@ -6,10 +6,13 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
AnnotationDomainType,
|
||||
AreaSeries,
|
||||
Axis,
|
||||
Chart,
|
||||
CurveType,
|
||||
LineAnnotation,
|
||||
Position,
|
||||
ScaleType,
|
||||
Settings,
|
||||
timeFormatter,
|
||||
|
@ -20,6 +23,7 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiIcon,
|
||||
EuiLink,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
|
@ -29,11 +33,12 @@ import { i18n } from '@kbn/i18n';
|
|||
import React from 'react';
|
||||
import { StackFrameMetadata } from '../../common/profiling';
|
||||
import { getFieldNameForTopNType, TopNType } from '../../common/stack_traces';
|
||||
import { CountPerTime, OTHER_BUCKET_LABEL } from '../../common/topn';
|
||||
import { CountPerTime, OTHER_BUCKET_LABEL, TopNSample } from '../../common/topn';
|
||||
import { useKibanaTimeZoneSetting } from '../hooks/use_kibana_timezone_setting';
|
||||
import { useProfilingChartsTheme } from '../hooks/use_profiling_charts_theme';
|
||||
import { useProfilingParams } from '../hooks/use_profiling_params';
|
||||
import { useProfilingRouter } from '../hooks/use_profiling_router';
|
||||
import { asNumber } from '../utils/formatters/as_number';
|
||||
import { asPercentage } from '../utils/formatters/as_percentage';
|
||||
import { StackFrameSummary } from './stack_frame_summary';
|
||||
|
||||
|
@ -52,6 +57,7 @@ export interface SubChartProps {
|
|||
style?: React.ComponentProps<typeof EuiFlexGroup>['style'];
|
||||
showFrames: boolean;
|
||||
padTitle: boolean;
|
||||
sample: TopNSample | null;
|
||||
}
|
||||
|
||||
const NUM_DISPLAYED_FRAMES = 5;
|
||||
|
@ -71,6 +77,7 @@ export const SubChart: React.FC<SubChartProps> = ({
|
|||
style,
|
||||
showFrames,
|
||||
padTitle,
|
||||
sample,
|
||||
}) => {
|
||||
const theme = useEuiTheme();
|
||||
|
||||
|
@ -196,7 +203,23 @@ export const SubChart: React.FC<SubChartProps> = ({
|
|||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">{asPercentage(percentage / 100)}</EuiText>
|
||||
<EuiFlexGroup direction="column" gutterSize="xs" alignItems="flexEnd">
|
||||
{sample ? (
|
||||
<EuiFlexItem>
|
||||
<EuiText size="m">{asPercentage(sample.Percentage / 100)}</EuiText>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
<EuiFlexItem>
|
||||
<EuiText size={sample ? 'xs' : 's'}>
|
||||
{sample
|
||||
? i18n.translate('xpack.profiling.stackFrames.subChart.avg', {
|
||||
defaultMessage: 'avg. {percentage}',
|
||||
values: { percentage: asPercentage(percentage / 100) },
|
||||
})
|
||||
: asPercentage(percentage / 100)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
@ -220,6 +243,23 @@ export const SubChart: React.FC<SubChartProps> = ({
|
|||
curve={CurveType.CURVE_STEP_AFTER}
|
||||
color={color}
|
||||
/>
|
||||
{sample ? (
|
||||
<LineAnnotation
|
||||
id="highlighted_sample"
|
||||
domainType={AnnotationDomainType.XDomain}
|
||||
dataValues={[{ dataValue: sample.Timestamp }]}
|
||||
style={{
|
||||
line: {
|
||||
strokeWidth: 2,
|
||||
dash: [4, 4],
|
||||
opacity: 0.5,
|
||||
},
|
||||
}}
|
||||
marker={<EuiIcon type="dot" />}
|
||||
markerPosition={Position.Top}
|
||||
hideTooltips
|
||||
/>
|
||||
) : null}
|
||||
{showAxes ? (
|
||||
<Axis
|
||||
id="bottom-axis"
|
||||
|
@ -252,10 +292,12 @@ export const SubChart: React.FC<SubChartProps> = ({
|
|||
backgroundColor: `rgba(255, 255, 255, 0.75)`,
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.profiling.maxValue', {
|
||||
defaultMessage: 'Max: {max}',
|
||||
values: { max: Math.max(...data.map((value) => value.Count ?? 0)) },
|
||||
})}
|
||||
{sample
|
||||
? asNumber(sample.Count!)
|
||||
: i18n.translate('xpack.profiling.maxValue', {
|
||||
defaultMessage: 'Max: {max}',
|
||||
values: { max: asNumber(Math.max(...data.map((value) => value.Count ?? 0))) },
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -5,7 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { NOT_AVAILABLE_LABEL } from '../../../common';
|
||||
|
||||
export function asNumber(value: number): string {
|
||||
if (isNaN(value)) {
|
||||
return NOT_AVAILABLE_LABEL;
|
||||
}
|
||||
|
||||
if (value === 0) {
|
||||
return '0';
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue