mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Profiling] Normalize samples by time for differential flamegraph (#152158)
This PR appropriately scales the samples in the differential flamegraph's tooltip when time-normalized mode is selected. Fixes https://github.com/elastic/prodfiler/issues/3038 ### Notes * Respective values for the normalization menu and differential flamegraph are now defined in the parent view so that both elements remain in sync. Previously only the normalization menu had the scaling factors for time-normalized mode, thus, the tooltip in the differential flamegraph was not accurate. * The prior scaling factors for scale-normalized mode are remembered as long as a user is on the differential flamegraph. Thus, a user can update the time ranges, the format (Abs vs Rel), and the normalization mode without losing their previously chosen scale-normalized values. * The time-normalized scaling factors continue to remain immutable. * Due to artifacts related to floating-point division, the adjusted samples may not be whole integers.
This commit is contained in:
parent
da4307e80e
commit
ff02c4e124
2 changed files with 83 additions and 82 deletions
|
@ -15,7 +15,7 @@ import {
|
|||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { pick } from 'lodash';
|
||||
import { get } from 'lodash';
|
||||
import React, { useState } from 'react';
|
||||
import { FlameGraphComparisonMode, FlameGraphNormalizationMode } from '../../../common/flamegraph';
|
||||
import { useProfilingParams } from '../../hooks/use_profiling_params';
|
||||
|
@ -85,9 +85,31 @@ export function FlameGraphsView({ children }: { children: React.ReactElement })
|
|||
const comparisonMode =
|
||||
'comparisonMode' in query ? query.comparisonMode : FlameGraphComparisonMode.Absolute;
|
||||
|
||||
const normalizationMode = 'normalizationMode' in query ? query.normalizationMode : undefined;
|
||||
const baseline = 'baseline' in query ? query.baseline : 1;
|
||||
const comparison = 'comparison' in query ? query.comparison : 1;
|
||||
const normalizationMode: FlameGraphNormalizationMode = get(
|
||||
query,
|
||||
'normalizationMode',
|
||||
FlameGraphNormalizationMode.Time
|
||||
);
|
||||
|
||||
const baselineScale: number = get(query, 'baseline', 1);
|
||||
const comparisonScale: number = get(query, 'comparison', 1);
|
||||
|
||||
const totalSeconds =
|
||||
(new Date(timeRange.end).getTime() - new Date(timeRange.start).getTime()) / 1000;
|
||||
const totalComparisonSeconds =
|
||||
(new Date(comparisonTimeRange.end!).getTime() -
|
||||
new Date(comparisonTimeRange.start!).getTime()) /
|
||||
1000;
|
||||
|
||||
const baselineTime = 1;
|
||||
const comparisonTime = totalSeconds / totalComparisonSeconds;
|
||||
|
||||
const normalizationOptions: FlameGraphNormalizationOptions = {
|
||||
baselineScale,
|
||||
baselineTime,
|
||||
comparisonScale,
|
||||
comparisonTime,
|
||||
};
|
||||
|
||||
const {
|
||||
services: { fetchElasticFlamechart },
|
||||
|
@ -247,33 +269,25 @@ export function FlameGraphsView({ children }: { children: React.ReactElement })
|
|||
<EuiFlexGroup direction="row" gutterSize="m" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<NormalizationMenu
|
||||
onChange={(options) => {
|
||||
onChange={(mode, options) => {
|
||||
profilingRouter.push(routePath, {
|
||||
path: routePath,
|
||||
query: {
|
||||
...query,
|
||||
...pick(options, 'baseline', 'comparison'),
|
||||
normalizationMode: options.mode,
|
||||
},
|
||||
query:
|
||||
mode === FlameGraphNormalizationMode.Scale
|
||||
? {
|
||||
...query,
|
||||
baseline: options.baselineScale,
|
||||
comparison: options.comparisonScale,
|
||||
normalizationMode: mode,
|
||||
}
|
||||
: {
|
||||
...query,
|
||||
normalizationMode: mode,
|
||||
},
|
||||
});
|
||||
}}
|
||||
totalSeconds={
|
||||
(new Date(timeRange.end).getTime() - new Date(timeRange.start).getTime()) / 1000
|
||||
}
|
||||
comparisonTotalSeconds={
|
||||
(new Date(comparisonTimeRange.end!).getTime() -
|
||||
new Date(comparisonTimeRange.start!).getTime()) /
|
||||
1000
|
||||
}
|
||||
options={
|
||||
(normalizationMode === FlameGraphNormalizationMode.Time
|
||||
? { mode: FlameGraphNormalizationMode.Time }
|
||||
: {
|
||||
mode: FlameGraphNormalizationMode.Scale,
|
||||
baseline,
|
||||
comparison,
|
||||
}) as FlameGraphNormalizationOptions
|
||||
}
|
||||
mode={normalizationMode}
|
||||
options={normalizationOptions}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
@ -308,8 +322,16 @@ export function FlameGraphsView({ children }: { children: React.ReactElement })
|
|||
primaryFlamegraph={data?.primaryFlamegraph}
|
||||
comparisonFlamegraph={data?.comparisonFlamegraph}
|
||||
comparisonMode={comparisonMode}
|
||||
baseline={baseline}
|
||||
comparison={comparison}
|
||||
baseline={
|
||||
normalizationMode === FlameGraphNormalizationMode.Time
|
||||
? baselineTime
|
||||
: baselineScale
|
||||
}
|
||||
comparison={
|
||||
normalizationMode === FlameGraphNormalizationMode.Time
|
||||
? comparisonTime
|
||||
: comparisonScale
|
||||
}
|
||||
showInformationWindow={showInformationWindow}
|
||||
onInformationWindowClose={() => {
|
||||
setShowInformationWindow(false);
|
||||
|
|
|
@ -27,19 +27,17 @@ import { i18n } from '@kbn/i18n';
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { FlameGraphNormalizationMode } from '../../../common/flamegraph';
|
||||
|
||||
export type FlameGraphNormalizationOptions =
|
||||
| {
|
||||
mode: FlameGraphNormalizationMode.Scale;
|
||||
baseline: number;
|
||||
comparison: number;
|
||||
}
|
||||
| { mode: FlameGraphNormalizationMode.Time };
|
||||
export interface FlameGraphNormalizationOptions {
|
||||
baselineScale: number;
|
||||
baselineTime: number;
|
||||
comparisonScale: number;
|
||||
comparisonTime: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
mode: FlameGraphNormalizationMode;
|
||||
options: FlameGraphNormalizationOptions;
|
||||
totalSeconds: number;
|
||||
comparisonTotalSeconds: number;
|
||||
onChange: (options: FlameGraphNormalizationOptions) => void;
|
||||
onChange: (mode: FlameGraphNormalizationMode, options: FlameGraphNormalizationOptions) => void;
|
||||
}
|
||||
|
||||
const SCALE_LABEL = i18n.translate('xpack.profiling.flameGraphNormalizationMenu.scale', {
|
||||
|
@ -57,19 +55,6 @@ const NORMALIZE_BY_LABEL = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
function getScaleFactorsBasedOnTime({
|
||||
totalSeconds,
|
||||
comparisonTotalSeconds,
|
||||
}: {
|
||||
totalSeconds: number;
|
||||
comparisonTotalSeconds: number;
|
||||
}) {
|
||||
return {
|
||||
baseline: 1,
|
||||
comparison: totalSeconds / comparisonTotalSeconds,
|
||||
};
|
||||
}
|
||||
|
||||
export function NormalizationMenu(props: Props) {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
|
||||
|
@ -78,26 +63,18 @@ export function NormalizationMenu(props: Props) {
|
|||
const baselineScaleFactorInputId = useGeneratedHtmlId({ prefix: 'baselineScaleFactor' });
|
||||
const comparisonScaleFactorInputId = useGeneratedHtmlId({ prefix: 'comparisonScaleFactor' });
|
||||
|
||||
const [mode, setMode] = useState(props.mode);
|
||||
const [options, setOptions] = useState(props.options);
|
||||
|
||||
useEffect(() => {
|
||||
setMode(props.mode);
|
||||
setOptions(props.options);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
props.options.mode,
|
||||
// @ts-expect-error can't refine because ESLint will complain
|
||||
props.options.baseline,
|
||||
// @ts-expect-error can't refine because ESLint will complain
|
||||
props.options.comparison,
|
||||
]);
|
||||
}, [props.mode, props.options]);
|
||||
|
||||
const { baseline, comparison } =
|
||||
options.mode === FlameGraphNormalizationMode.Time
|
||||
? getScaleFactorsBasedOnTime({
|
||||
comparisonTotalSeconds: props.comparisonTotalSeconds,
|
||||
totalSeconds: props.totalSeconds,
|
||||
})
|
||||
: { comparison: options.comparison, baseline: options.baseline };
|
||||
mode === FlameGraphNormalizationMode.Time
|
||||
? { comparison: options.comparisonTime, baseline: options.baselineTime }
|
||||
: { comparison: options.comparisonScale, baseline: options.baselineScale };
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
|
@ -133,7 +110,7 @@ export function NormalizationMenu(props: Props) {
|
|||
padding: '0 16px',
|
||||
}}
|
||||
>
|
||||
{props.options.mode === FlameGraphNormalizationMode.Scale ? SCALE_LABEL : TIME_LABEL}
|
||||
{props.mode === FlameGraphNormalizationMode.Scale ? SCALE_LABEL : TIME_LABEL}
|
||||
</EuiFlexItem>
|
||||
</EuiFormControlLayout>
|
||||
}
|
||||
|
@ -183,17 +160,12 @@ export function NormalizationMenu(props: Props) {
|
|||
buttonSize="compressed"
|
||||
isFullWidth
|
||||
onChange={(id, value) => {
|
||||
setOptions((prevOptions) => ({
|
||||
...prevOptions,
|
||||
...(id === FlameGraphNormalizationMode.Time
|
||||
? { mode: FlameGraphNormalizationMode.Time }
|
||||
: { mode: FlameGraphNormalizationMode.Scale, baseline: 1, comparison: 1 }),
|
||||
}));
|
||||
setMode(id as FlameGraphNormalizationMode);
|
||||
}}
|
||||
legend={i18n.translate('xpack.profiling.flameGraphNormalizationMode.selectModeLegend', {
|
||||
defaultMessage: 'Select a normalization mode for the flamegraph',
|
||||
})}
|
||||
idSelected={options.mode}
|
||||
idSelected={mode}
|
||||
options={[
|
||||
{
|
||||
id: FlameGraphNormalizationMode.Scale,
|
||||
|
@ -223,9 +195,14 @@ export function NormalizationMenu(props: Props) {
|
|||
id={baselineScaleFactorInputId}
|
||||
value={baseline}
|
||||
onChange={(e) => {
|
||||
setOptions((prevOptions) => ({ ...prevOptions, baseline: e.target.valueAsNumber }));
|
||||
if (mode === FlameGraphNormalizationMode.Scale) {
|
||||
setOptions((prevOptions) => ({
|
||||
...prevOptions,
|
||||
baselineScale: e.target.valueAsNumber,
|
||||
}));
|
||||
}
|
||||
}}
|
||||
disabled={options.mode === FlameGraphNormalizationMode.Time}
|
||||
disabled={mode === FlameGraphNormalizationMode.Time}
|
||||
/>
|
||||
</EuiFormControlLayout>
|
||||
<EuiSpacer size="m" />
|
||||
|
@ -246,18 +223,20 @@ export function NormalizationMenu(props: Props) {
|
|||
id={comparisonScaleFactorInputId}
|
||||
value={comparison}
|
||||
onChange={(e) => {
|
||||
setOptions((prevOptions) => ({
|
||||
...prevOptions,
|
||||
comparison: e.target.valueAsNumber,
|
||||
}));
|
||||
if (mode === FlameGraphNormalizationMode.Scale) {
|
||||
setOptions((prevOptions) => ({
|
||||
...prevOptions,
|
||||
comparisonScale: e.target.valueAsNumber,
|
||||
}));
|
||||
}
|
||||
}}
|
||||
disabled={options.mode === FlameGraphNormalizationMode.Time}
|
||||
disabled={mode === FlameGraphNormalizationMode.Time}
|
||||
/>
|
||||
</EuiFormControlLayout>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButton
|
||||
onClick={() => {
|
||||
props.onChange(options);
|
||||
props.onChange(mode, options);
|
||||
setIsPopoverOpen(false);
|
||||
}}
|
||||
fullWidth
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue