[Profiling][Flamegraps] persistent normalization mode (#153116)

https://user-images.githubusercontent.com/55978943/224367585-a6623d40-951b-418a-8615-2c3de46e4e6e.mov

Saves the normalization option selected and keeps it when changing the
comparison mode.

I also refactored the Flamegraph component extracting some components
away to simplify.
This commit is contained in:
Cauê Marcondes 2023-03-10 20:52:57 -05:00 committed by GitHub
parent 6552165c57
commit 58c918a061
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 198 additions and 151 deletions

View file

@ -0,0 +1,70 @@
/*
* 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 { EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { FlameGraphComparisonMode } from '../../../common/flamegraph';
interface Props {
comparisonMode: FlameGraphComparisonMode;
onChange: (nextComparisonMode: FlameGraphComparisonMode) => void;
}
export function DifferentialComparisonMode({ comparisonMode, onChange }: Props) {
return (
<EuiFlexItem grow={false}>
<EuiFlexGroup direction="row" gutterSize="m" alignItems="center">
<EuiFlexItem grow={false}>
<EuiTitle size="xxs">
<h3>
{i18n.translate(
'xpack.profiling.flameGraphsView.differentialFlameGraphComparisonModeTitle',
{ defaultMessage: 'Format' }
)}
</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonGroup
legend={i18n.translate(
'xpack.profiling.flameGraphsView.differentialFlameGraphComparisonModeLegend',
{
defaultMessage:
'This switch allows you to switch between an absolute and relative comparison between both graphs',
}
)}
type="single"
buttonSize="s"
idSelected={comparisonMode}
onChange={(nextComparisonMode) => {
onChange(nextComparisonMode as FlameGraphComparisonMode);
}}
options={[
{
id: FlameGraphComparisonMode.Absolute,
label: i18n.translate(
'xpack.profiling.flameGraphsView.differentialFlameGraphComparisonModeAbsoluteButtonLabel',
{
defaultMessage: 'Abs',
}
),
},
{
id: FlameGraphComparisonMode.Relative,
label: i18n.translate(
'xpack.profiling.flameGraphsView.differentialFlameGraphComparisonModeRelativeButtonLabel',
{
defaultMessage: 'Rel',
}
),
},
]}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
);
}

View file

@ -0,0 +1,114 @@
/*
* 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, EuiHorizontalRule, EuiPanel } from '@elastic/eui';
import React from 'react';
import { FlameGraphInformationWindowSwitch } from '.';
import { FlameGraphComparisonMode, FlameGraphNormalizationMode } from '../../../common/flamegraph';
import { useProfilingParams } from '../../hooks/use_profiling_params';
import { useProfilingRouter } from '../../hooks/use_profiling_router';
import { useProfilingRoutePath } from '../../hooks/use_profiling_route_path';
import { PrimaryAndComparisonSearchBar } from '../primary_and_comparison_search_bar';
import { PrimaryProfilingSearchBar } from '../profiling_app_page_template/primary_profiling_search_bar';
import { DifferentialComparisonMode } from './differential_comparison_mode';
import { FlameGraphNormalizationOptions, NormalizationMenu } from './normalization_menu';
interface Props {
isDifferentialView: boolean;
comparisonMode: FlameGraphComparisonMode;
normalizationMode: FlameGraphNormalizationMode;
normalizationOptions: FlameGraphNormalizationOptions;
showInformationWindow: boolean;
onChangeShowInformationWindow: () => void;
}
export function FlameGraphSearchPanel({
comparisonMode,
normalizationMode,
isDifferentialView,
normalizationOptions,
showInformationWindow,
onChangeShowInformationWindow,
}: Props) {
const { path, query } = useProfilingParams('/flamegraphs/*');
const routePath = useProfilingRoutePath();
const profilingRouter = useProfilingRouter();
function onChangeComparisonMode(nextComparisonMode: FlameGraphComparisonMode) {
if (!('comparisonRangeFrom' in query)) {
return;
}
profilingRouter.push(routePath, {
path,
query: {
...query,
...(nextComparisonMode === FlameGraphComparisonMode.Absolute
? {
comparisonMode: FlameGraphComparisonMode.Absolute,
normalizationMode,
}
: { comparisonMode: FlameGraphComparisonMode.Relative }),
},
});
}
function onChangeNormalizationMode(
nextNormalizationMode: FlameGraphNormalizationMode,
options: FlameGraphNormalizationOptions
) {
profilingRouter.push(routePath, {
path: routePath,
query:
nextNormalizationMode === FlameGraphNormalizationMode.Scale
? {
...query,
baseline: options.baselineScale,
comparison: options.comparisonScale,
normalizationMode: nextNormalizationMode,
}
: {
...query,
normalizationMode: nextNormalizationMode,
},
});
}
return (
<EuiPanel hasShadow={false} color="subdued">
{isDifferentialView ? <PrimaryAndComparisonSearchBar /> : <PrimaryProfilingSearchBar />}
<EuiHorizontalRule />
<EuiFlexGroup direction="row">
{isDifferentialView && (
<>
<DifferentialComparisonMode
comparisonMode={comparisonMode}
onChange={onChangeComparisonMode}
/>
{comparisonMode === FlameGraphComparisonMode.Absolute && (
<EuiFlexItem grow={false}>
<EuiFlexGroup direction="row" gutterSize="m" alignItems="center">
<EuiFlexItem grow={false}>
<NormalizationMenu
onChange={onChangeNormalizationMode}
mode={normalizationMode}
options={normalizationOptions}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
)}
</>
)}
<EuiFlexItem grow style={{ alignItems: 'flex-end' }}>
<FlameGraphInformationWindowSwitch
showInformationWindow={showInformationWindow}
onChange={onChangeShowInformationWindow}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
}

View file

@ -4,16 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
EuiButtonGroup,
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiPageHeaderContentProps,
EuiPanel,
EuiSwitch,
EuiTitle,
} from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiPageHeaderContentProps, EuiSwitch } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { get } from 'lodash';
import React, { useState } from 'react';
@ -26,11 +17,10 @@ import { useTimeRangeAsync } from '../../hooks/use_time_range_async';
import { AsyncComponent } from '../async_component';
import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies';
import { FlameGraph } from '../flamegraph';
import { PrimaryAndComparisonSearchBar } from '../primary_and_comparison_search_bar';
import { PrimaryProfilingSearchBar } from '../profiling_app_page_template/primary_profiling_search_bar';
import { ProfilingAppPageTemplate } from '../profiling_app_page_template';
import { RedirectTo } from '../redirect_to';
import { FlameGraphNormalizationOptions, NormalizationMenu } from './normalization_menu';
import { FlameGraphSearchPanel } from './flame_graph_search_panel';
import { FlameGraphNormalizationOptions } from './normalization_menu';
export function FlameGraphInformationWindowSwitch({
showInformationWindow,
@ -50,25 +40,8 @@ export function FlameGraphInformationWindowSwitch({
);
}
export function FlameGraphSearchPanel({
children,
searchBar,
}: {
children: React.ReactNode;
searchBar: JSX.Element;
}) {
return (
<EuiPanel hasShadow={false} color="subdued">
{searchBar}
<EuiHorizontalRule />
<EuiFlexGroup direction="row">{children}</EuiFlexGroup>
</EuiPanel>
);
}
export function FlameGraphsView({ children }: { children: React.ReactElement }) {
const {
path,
query,
query: { rangeFrom, rangeTo, kuery },
} = useProfilingParams('/flamegraphs/*');
@ -184,136 +157,26 @@ export function FlameGraphsView({ children }: { children: React.ReactElement })
];
const [showInformationWindow, setShowInformationWindow] = useState(false);
function toggleShowInformationWindow() {
setShowInformationWindow((prev) => !prev);
}
if (routePath === '/flamegraphs') {
return <RedirectTo pathname="/flamegraphs/flamegraph" />;
}
const searchBar = isDifferentialView ? (
<PrimaryAndComparisonSearchBar />
) : (
<PrimaryProfilingSearchBar />
);
const differentialComparisonMode = (
<EuiFlexItem grow={false}>
<EuiFlexGroup direction="row" gutterSize="m" alignItems="center">
<EuiFlexItem grow={false}>
<EuiTitle size="xxs">
<h3>
{i18n.translate(
'xpack.profiling.flameGraphsView.differentialFlameGraphComparisonModeTitle',
{ defaultMessage: 'Format' }
)}
</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonGroup
legend={i18n.translate(
'xpack.profiling.flameGraphsView.differentialFlameGraphComparisonModeLegend',
{
defaultMessage:
'This switch allows you to switch between an absolute and relative comparison between both graphs',
}
)}
type="single"
buttonSize="s"
idSelected={comparisonMode}
onChange={(nextComparisonMode) => {
if (!('comparisonRangeFrom' in query)) {
return;
}
profilingRouter.push(routePath, {
path,
query: {
...query,
...(nextComparisonMode === FlameGraphComparisonMode.Absolute
? {
comparisonMode: FlameGraphComparisonMode.Absolute,
normalizationMode: FlameGraphNormalizationMode.Time,
}
: { comparisonMode: FlameGraphComparisonMode.Relative }),
},
});
}}
options={[
{
id: FlameGraphComparisonMode.Absolute,
label: i18n.translate(
'xpack.profiling.flameGraphsView.differentialFlameGraphComparisonModeAbsoluteButtonLabel',
{
defaultMessage: 'Abs',
}
),
},
{
id: FlameGraphComparisonMode.Relative,
label: i18n.translate(
'xpack.profiling.flameGraphsView.differentialFlameGraphComparisonModeRelativeButtonLabel',
{
defaultMessage: 'Rel',
}
),
},
]}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
);
const differentialComparisonNormalization = (
<EuiFlexItem grow={false}>
<EuiFlexGroup direction="row" gutterSize="m" alignItems="center">
<EuiFlexItem grow={false}>
<NormalizationMenu
onChange={(mode, options) => {
profilingRouter.push(routePath, {
path: routePath,
query:
mode === FlameGraphNormalizationMode.Scale
? {
...query,
baseline: options.baselineScale,
comparison: options.comparisonScale,
normalizationMode: mode,
}
: {
...query,
normalizationMode: mode,
},
});
}}
mode={normalizationMode}
options={normalizationOptions}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
);
const informationWindowSwitch = (
<EuiFlexItem grow style={{ alignItems: 'flex-end' }}>
<FlameGraphInformationWindowSwitch
showInformationWindow={showInformationWindow}
onChange={() => setShowInformationWindow((prev) => !prev)}
/>
</EuiFlexItem>
);
return (
<ProfilingAppPageTemplate tabs={tabs} hideSearchBar={true}>
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<FlameGraphSearchPanel searchBar={searchBar}>
{isDifferentialView && differentialComparisonMode}
{isDifferentialView &&
comparisonMode === FlameGraphComparisonMode.Absolute &&
differentialComparisonNormalization}
{informationWindowSwitch}
</FlameGraphSearchPanel>
<FlameGraphSearchPanel
isDifferentialView={isDifferentialView}
comparisonMode={comparisonMode}
normalizationMode={normalizationMode}
normalizationOptions={normalizationOptions}
showInformationWindow={showInformationWindow}
onChangeShowInformationWindow={toggleShowInformationWindow}
/>
</EuiFlexItem>
<EuiFlexItem>
<AsyncComponent {...state} style={{ height: '100%' }} size="xl">