The differential topN functions must present a summary of the analysis in an easy-to-consume format (#161577)

<img width="1746" alt="Screenshot 2023-07-18 at 10 43 03 AM"
src="2c6c1531-0d54-476a-8ba1-dfc5c252a2b9">

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Cauê Marcondes 2023-07-20 16:45:52 -03:00 committed by GitHub
parent 257e52eb84
commit 1d5945aa90
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 461 additions and 106 deletions

View file

@ -14,15 +14,16 @@ describe('TopN function operations', () => {
test('1', () => {
const maxTopN = 5;
const totalSamples = sum([...events.values()]);
const topNFunctions = createTopNFunctions(
const topNFunctions = createTopNFunctions({
events,
stackTraces,
stackFrames,
executables,
0,
maxTopN,
1.0
);
startIndex: 0,
endIndex: maxTopN,
samplingRate: 1.0,
totalSeconds: 900,
});
expect(topNFunctions.TotalCount).toEqual(totalSamples);
expect(topNFunctions.TopN.length).toEqual(maxTopN);

View file

@ -5,6 +5,8 @@
* 2.0.
*/
import * as t from 'io-ts';
import { sumBy } from 'lodash';
import { calculateImpactEstimates } from './calculate_impact_estimates';
import { createFrameGroupID, FrameGroupID } from './frame_group';
import {
createStackFrameMetadata,
@ -30,23 +32,42 @@ interface TopNFunctionAndFrameGroup {
type TopNFunction = Pick<
TopNFunctionAndFrameGroup,
'Frame' | 'CountExclusive' | 'CountInclusive'
> & { Id: string; Rank: number };
> & {
Id: string;
Rank: number;
impactEstimates?: ReturnType<typeof calculateImpactEstimates>;
selfCPUPerc: number;
totalCPUPerc: number;
};
export interface TopNFunctions {
TotalCount: number;
TopN: TopNFunction[];
SamplingRate: number;
impactEstimates?: ReturnType<typeof calculateImpactEstimates>;
selfCPUPerc: number;
totalCPUPerc: number;
}
export function createTopNFunctions(
events: Map<StackTraceID, number>,
stackTraces: Map<StackTraceID, StackTrace>,
stackFrames: Map<StackFrameID, StackFrame>,
executables: Map<FileID, Executable>,
startIndex: number,
endIndex: number,
samplingRate: number
): TopNFunctions {
export function createTopNFunctions({
endIndex,
events,
executables,
samplingRate,
stackFrames,
stackTraces,
startIndex,
totalSeconds,
}: {
endIndex: number;
events: Map<StackTraceID, number>;
executables: Map<FileID, Executable>;
samplingRate: number;
stackFrames: Map<StackFrameID, StackFrame>;
stackTraces: Map<StackTraceID, StackTrace>;
startIndex: number;
totalSeconds: number;
}): TopNFunctions {
// The `count` associated with a frame provides the total number of
// traces in which that node has appeared at least once. However, a
// frame may appear multiple times in a trace, and thus to avoid
@ -143,17 +164,55 @@ export function createTopNFunctions(
endIndex = topN.length;
}
const framesAndCountsAndIds = topN.slice(startIndex, endIndex).map((frameAndCount, i) => ({
Rank: i + 1,
Frame: frameAndCount.Frame,
CountExclusive: frameAndCount.CountExclusive,
CountInclusive: frameAndCount.CountInclusive,
Id: frameAndCount.FrameGroupID,
}));
const framesAndCountsAndIds = topN.slice(startIndex, endIndex).map((frameAndCount, i) => {
const countExclusive = frameAndCount.CountExclusive;
const countInclusive = frameAndCount.CountInclusive;
const totalCPUPerc = (countInclusive / totalCount) * 100;
const selfCPUPerc = (countExclusive / totalCount) * 100;
const impactEstimates =
totalSeconds > 0
? calculateImpactEstimates({
countExclusive,
countInclusive,
totalSamples: totalCount,
totalSeconds,
})
: undefined;
return {
Rank: i + 1,
Frame: frameAndCount.Frame,
CountExclusive: countExclusive,
selfCPUPerc,
CountInclusive: countInclusive,
totalCPUPerc,
Id: frameAndCount.FrameGroupID,
impactEstimates,
};
});
const sumSelfCPU = sumBy(framesAndCountsAndIds, 'CountExclusive');
const selfCPUPerc = (sumSelfCPU / totalCount) * 100;
const sumTotalCPU = sumBy(framesAndCountsAndIds, 'CountInclusive');
const totalCPUPerc = (sumTotalCPU / totalCount) * 100;
const impactEstimates =
totalSeconds > 0
? calculateImpactEstimates({
countExclusive: sumSelfCPU,
countInclusive: sumTotalCPU,
totalSamples: totalCount,
totalSeconds,
})
: undefined;
return {
TotalCount: totalCount,
TopN: framesAndCountsAndIds,
SamplingRate: samplingRate,
impactEstimates,
selfCPUPerc,
totalCPUPerc,
};
}

View file

@ -18,7 +18,7 @@ import {
import { i18n } from '@kbn/i18n';
import { isNumber } from 'lodash';
import React from 'react';
import { calculateImpactEstimates } from '../../utils/calculate_impact_estimates';
import { calculateImpactEstimates } from '../../../common/calculate_impact_estimates';
import { asCost } from '../../utils/formatters/as_cost';
import { asPercentage } from '../../utils/formatters/as_percentage';
import { asWeight } from '../../utils/formatters/as_weight';

View file

@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n';
import React from 'react';
import { calculateImpactEstimates } from '../../utils/calculate_impact_estimates';
import { calculateImpactEstimates } from '../../../common/calculate_impact_estimates';
import { asCost } from '../../utils/formatters/as_cost';
import { asDuration } from '../../utils/formatters/as_duration';
import { asNumber } from '../../utils/formatters/as_number';

View file

@ -10,7 +10,9 @@ import {
EuiButton,
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiPageHeaderContentProps,
EuiPanel,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useEffect } from 'react';
@ -97,7 +99,10 @@ export function ProfilingAppPageTemplate({
<EuiFlexGroup direction="column" style={{ maxWidth: '100%' }}>
{!hideSearchBar && (
<EuiFlexItem grow={false}>
<PrimaryProfilingSearchBar />
<EuiPanel hasShadow={false} color="subdued">
<PrimaryProfilingSearchBar />
<EuiHorizontalRule />
</EuiPanel>
</EuiFlexItem>
)}
<EuiFlexItem>{children}</EuiFlexItem>

View file

@ -14,7 +14,6 @@ import {
EuiDataGridRefProps,
EuiDataGridSorting,
EuiScreenReaderOnly,
EuiSpacer,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { last } from 'lodash';
@ -26,7 +25,6 @@ import { CPULabelWithHint } from '../cpu_label_with_hint';
import { FrameInformationTooltip } from '../frame_information_window/frame_information_tooltip';
import { LabelWithHint } from '../label_with_hint';
import { FunctionRow } from './function_row';
import { TotalSamplesStat } from './total_samples_stat';
import { getFunctionsRows, IFunctionRow } from './utils';
interface Props {
@ -90,15 +88,8 @@ export const TopNFunctionsGrid = forwardRef(
comparisonScaleFactor,
comparisonTopNFunctions,
topNFunctions,
totalSeconds,
});
}, [
topNFunctions,
comparisonTopNFunctions,
totalSeconds,
comparisonScaleFactor,
baselineScaleFactor,
]);
}, [topNFunctions, comparisonTopNFunctions, comparisonScaleFactor, baselineScaleFactor]);
const { columns, leadingControlColumns } = useMemo(() => {
const gridColumns: EuiDataGridColumn[] = [
@ -263,13 +254,6 @@ export const TopNFunctionsGrid = forwardRef(
return (
<>
<TotalSamplesStat
baselineTotalSamples={totalCount}
baselineScaleFactor={baselineScaleFactor}
comparisonTotalSamples={comparisonTopNFunctions?.TotalCount}
comparisonScaleFactor={comparisonScaleFactor}
/>
<EuiSpacer size="s" />
<EuiDataGrid
ref={ref}
aria-label="TopN functions"

View file

@ -12,7 +12,7 @@ describe('Top N functions: Utils', () => {
expect(getColorLabel(-10)).toEqual({
color: 'success',
label: '10.00%',
icon: 'sortDown',
icon: 'sortUp',
});
});
@ -28,7 +28,7 @@ describe('Top N functions: Utils', () => {
expect(getColorLabel(10)).toEqual({
color: 'danger',
label: '10.00%',
icon: 'sortUp',
icon: 'sortDown',
});
});
});

View file

@ -7,11 +7,11 @@
import { keyBy } from 'lodash';
import { TopNFunctions } from '../../../common/functions';
import { StackFrameMetadata } from '../../../common/profiling';
import { calculateImpactEstimates } from '../../utils/calculate_impact_estimates';
import { calculateImpactEstimates } from '../../../common/calculate_impact_estimates';
export function getColorLabel(percent: number) {
const color = percent < 0 ? 'success' : 'danger';
const icon = percent < 0 ? 'sortDown' : 'sortUp';
const icon = percent < 0 ? 'sortUp' : 'sortDown';
const isSmallPercent = Math.abs(percent) <= 0.01;
const label = isSmallPercent ? '<0.01' : Math.abs(percent).toFixed(2) + '%';
@ -38,6 +38,7 @@ export interface IFunctionRow {
totalCPU: number;
selfCPUPerc: number;
totalCPUPerc: number;
impactEstimates?: ReturnType<typeof calculateImpactEstimates>;
};
}
@ -46,13 +47,11 @@ export function getFunctionsRows({
comparisonScaleFactor,
comparisonTopNFunctions,
topNFunctions,
totalSeconds,
}: {
baselineScaleFactor?: number;
comparisonScaleFactor?: number;
comparisonTopNFunctions?: TopNFunctions;
topNFunctions?: TopNFunctions;
totalSeconds: number;
}): IFunctionRow[] {
if (!topNFunctions || !topNFunctions.TotalCount || topNFunctions.TotalCount === 0) {
return [];
@ -65,26 +64,11 @@ export function getFunctionsRows({
return topNFunctions.TopN.filter((topN) => topN.CountExclusive > 0).map((topN, i) => {
const comparisonRow = comparisonDataById?.[topN.Id];
const totalSamples = topN.CountExclusive;
const topNCountExclusiveScaled = scaleValue({
value: totalSamples,
value: topN.CountExclusive,
scaleFactor: baselineScaleFactor,
});
const totalCPUPerc = (topN.CountInclusive / topNFunctions.TotalCount) * 100;
const selfCPUPerc = (topN.CountExclusive / topNFunctions.TotalCount) * 100;
const impactEstimates =
totalSeconds > 0
? calculateImpactEstimates({
countExclusive: topN.CountExclusive,
countInclusive: topN.CountInclusive,
totalSamples,
totalSeconds,
})
: undefined;
function calculateDiff() {
if (comparisonTopNFunctions && comparisonRow) {
const comparisonCountExclusiveScaled = scaleValue({
@ -96,12 +80,10 @@ export function getFunctionsRows({
rank: topN.Rank - comparisonRow.Rank,
samples: topNCountExclusiveScaled - comparisonCountExclusiveScaled,
selfCPU: comparisonRow.CountExclusive,
selfCPUPerc:
selfCPUPerc - (comparisonRow.CountExclusive / comparisonTopNFunctions.TotalCount) * 100,
totalCPU: comparisonRow.CountInclusive,
totalCPUPerc:
totalCPUPerc -
(comparisonRow.CountInclusive / comparisonTopNFunctions.TotalCount) * 100,
selfCPUPerc: topN.selfCPUPerc - comparisonRow.selfCPUPerc,
totalCPUPerc: topN.totalCPUPerc - comparisonRow.totalCPUPerc,
impactEstimates: comparisonRow.impactEstimates,
};
}
}
@ -110,12 +92,57 @@ export function getFunctionsRows({
rank: topN.Rank,
frame: topN.Frame,
samples: topNCountExclusiveScaled,
selfCPUPerc: topN.selfCPUPerc,
totalCPUPerc: topN.totalCPUPerc,
selfCPU: topN.CountExclusive,
selfCPUPerc,
totalCPU: topN.CountInclusive,
totalCPUPerc,
impactEstimates,
impactEstimates: topN.impactEstimates,
diff: calculateDiff(),
};
});
}
export function calculateBaseComparisonDiff({
baselineValue,
baselineScaleFactor,
comparisonValue,
comparisonScaleFactor,
formatValue,
}: {
baselineValue: number;
baselineScaleFactor?: number;
comparisonValue: number;
comparisonScaleFactor?: number;
formatValue?: (value: number) => string;
}) {
const scaledBaselineValue = scaleValue({
value: baselineValue,
scaleFactor: baselineScaleFactor,
});
const baseValue = formatValue
? formatValue(scaledBaselineValue)
: scaledBaselineValue.toLocaleString();
if (comparisonValue === 0) {
return { baseValue };
}
const scaledComparisonValue = scaleValue({
value: comparisonValue,
scaleFactor: comparisonScaleFactor,
});
const diffSamples = scaledComparisonValue - scaledBaselineValue;
const percentDiffDelta = (diffSamples / (scaledComparisonValue - diffSamples)) * 100;
const { color, icon, label } = getColorLabel(percentDiffDelta);
return {
baseValue,
comparisonValue: formatValue
? formatValue(scaledComparisonValue)
: scaledComparisonValue.toLocaleString(),
percentDiffDelta,
color,
icon,
label,
};
}

View file

@ -0,0 +1,31 @@
/*
* 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';
interface Props {
value?: string;
diff?: string;
color?: string;
icon?: string;
}
export function ExtraValue({ value, diff, color, icon }: Props) {
return (
<EuiFlexGroup direction="row" gutterSize="none" justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiText color={color} size="s">
{`${value} (${diff})`}
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{icon && <EuiIcon type={icon} size="m" color={color} />}
</EuiFlexItem>
</EuiFlexGroup>
);
}

View file

@ -0,0 +1,123 @@
/*
* 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 } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { TopNFunctions } from '../../../common/functions';
import { asCost } from '../../utils/formatters/as_cost';
import { asWeight } from '../../utils/formatters/as_weight';
import { calculateBaseComparisonDiff } from '../topn_functions/utils';
import { SummaryItem } from './summary_item';
interface Props {
baselineTopNFunctions?: TopNFunctions;
comparisonTopNFunctions?: TopNFunctions;
baselineScaleFactor?: number;
comparisonScaleFactor?: number;
isLoading: boolean;
}
const ESTIMATED_VALUE_LABEL = i18n.translate('xpack.profiling.diffTopNFunctions.estimatedValue', {
defaultMessage: 'Estimated value',
}) as string;
export function TopNFunctionsSummary({
baselineTopNFunctions,
comparisonTopNFunctions,
baselineScaleFactor,
comparisonScaleFactor,
isLoading,
}: Props) {
const totalSamplesDiff = calculateBaseComparisonDiff({
baselineValue: baselineTopNFunctions?.TotalCount || 0,
baselineScaleFactor,
comparisonValue: comparisonTopNFunctions?.TotalCount || 0,
comparisonScaleFactor,
});
const co2EmissionDiff = calculateBaseComparisonDiff({
baselineValue: baselineTopNFunctions?.impactEstimates?.annualizedCo2 || 0,
baselineScaleFactor,
comparisonValue: comparisonTopNFunctions?.impactEstimates?.annualizedCo2 || 0,
comparisonScaleFactor,
formatValue: asWeight,
});
const costImpactDiff = calculateBaseComparisonDiff({
baselineValue: baselineTopNFunctions?.impactEstimates?.annualizedDollarCost || 0,
baselineScaleFactor,
comparisonValue: comparisonTopNFunctions?.impactEstimates?.annualizedDollarCost || 0,
comparisonScaleFactor,
formatValue: asCost,
});
const data = [
{
title: i18n.translate('xpack.profiling.diffTopNFunctions.summary.performance', {
defaultMessage: '{label} overall performance by',
values: {
label:
isLoading || totalSamplesDiff.percentDiffDelta === undefined
? 'Gained/Lost'
: totalSamplesDiff?.percentDiffDelta > 0
? 'Lost'
: 'Gained',
},
}) as string,
baseValue: totalSamplesDiff.label || '',
baseIcon: totalSamplesDiff.icon,
baseColor: totalSamplesDiff.color,
titleHint: ESTIMATED_VALUE_LABEL,
},
{
title: i18n.translate('xpack.profiling.diffTopNFunctions.summary.co2', {
defaultMessage: 'Annualized CO2 emission impact',
}) as string,
baseValue: co2EmissionDiff.baseValue,
comparisonValue: co2EmissionDiff.comparisonValue,
comparisonIcon: co2EmissionDiff.icon,
comparisonColor: co2EmissionDiff.color,
comparisonPerc: co2EmissionDiff.label,
titleHint: ESTIMATED_VALUE_LABEL,
},
{
title: i18n.translate('xpack.profiling.diffTopNFunctions.summary.cost', {
defaultMessage: 'Annualized cost impact',
}) as string,
baseValue: costImpactDiff.baseValue,
comparisonValue: costImpactDiff.comparisonValue,
comparisonIcon: costImpactDiff.icon,
comparisonColor: costImpactDiff.color,
comparisonPerc: costImpactDiff.label,
titleHint: ESTIMATED_VALUE_LABEL,
},
{
title: i18n.translate('xpack.profiling.diffTopNFunctions.summary.samples', {
defaultMessage: 'Total number of samples',
}) as string,
baseValue: totalSamplesDiff.baseValue,
comparisonValue: totalSamplesDiff.comparisonValue,
comparisonIcon: totalSamplesDiff.icon,
comparisonColor: totalSamplesDiff.color,
comparisonPerc: totalSamplesDiff.label,
titleHint: ESTIMATED_VALUE_LABEL,
},
];
return (
<EuiFlexGroup direction="row">
{data.map((item, idx) => {
return (
<EuiFlexItem key={idx}>
<SummaryItem {...item} isLoading={isLoading} />
</EuiFlexItem>
);
})}
</EuiFlexGroup>
);
}

View file

@ -0,0 +1,107 @@
/*
* 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,
EuiPanel,
EuiSpacer,
EuiStat,
EuiText,
EuiTextColor,
EuiToolTip,
} from '@elastic/eui';
import React from 'react';
interface Props {
title: string;
isLoading: boolean;
baseValue: string;
baseIcon?: string;
baseColor?: string;
comparisonValue?: string;
comparisonPerc?: string;
comparisonIcon?: string;
comparisonColor?: string;
titleHint?: string;
}
function Title({ title }: { title: string }) {
return (
<EuiText style={{ fontWeight: 'bold' }} textAlign="left">
{title}
</EuiText>
);
}
function BaseValue({ value, icon, color }: { value: string; icon?: string; color?: string }) {
return (
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd">
{icon ? (
<EuiFlexItem grow={false} style={{ justifyContent: 'center' }}>
<EuiIcon type={icon} color={color} size="l" />
</EuiFlexItem>
) : null}
<EuiFlexItem grow={false}>
<EuiTextColor style={{ fontWeight: 'bold' }} color={color}>
{value}
</EuiTextColor>
</EuiFlexItem>
</EuiFlexGroup>
);
}
export function SummaryItem({
baseValue,
baseIcon,
baseColor,
comparisonValue,
title,
isLoading,
comparisonPerc,
comparisonColor,
comparisonIcon,
titleHint,
}: Props) {
return (
<EuiPanel hasShadow={false}>
<EuiStat
title={<BaseValue value={baseValue} color={baseColor} icon={baseIcon} />}
titleSize="m"
description={
<>
{titleHint ? (
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<Title title={title} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiToolTip content={titleHint}>
<EuiIcon type="questionInCircle" />
</EuiToolTip>
</EuiFlexItem>
</EuiFlexGroup>
) : (
<Title title={title} />
)}
<EuiSpacer />
</>
}
textAlign="right"
isLoading={isLoading}
>
{!isLoading && comparisonValue ? (
<EuiText color={comparisonColor}>
{comparisonIcon ? <EuiIcon type={comparisonIcon} /> : null}
{`${comparisonValue} (${comparisonPerc})`}
</EuiText>
) : null}
</EuiStat>
</EuiPanel>
);
}

View file

@ -8,5 +8,5 @@
import { asNumber } from './as_number';
export function asCost(value: number, unit: string = '$') {
return `${asNumber(value)}${unit}`;
return `${unit}${asNumber(value)}`;
}

View file

@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
import React, { useState } from 'react';
import { AsyncComponent } from '../../../components/async_component';
import { useProfilingDependencies } from '../../../components/contexts/profiling_dependencies/use_profiling_dependencies';
@ -117,11 +117,13 @@ export function DifferentialFlameGraphsView() {
return (
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<DifferentialFlameGraphSearchPanel
comparisonMode={comparisonMode}
normalizationMode={normalizationMode}
normalizationOptions={normalizationOptions}
/>
<EuiPanel hasShadow={false} color="subdued">
<DifferentialFlameGraphSearchPanel
comparisonMode={comparisonMode}
normalizationMode={normalizationMode}
normalizationOptions={normalizationOptions}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<AsyncComponent {...state} style={{ height: '100%' }} size="xl">

View file

@ -4,12 +4,11 @@
* 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React, { useState } from 'react';
import { AsyncComponent } from '../../../components/async_component';
import { useProfilingDependencies } from '../../../components/contexts/profiling_dependencies/use_profiling_dependencies';
import { FlameGraph } from '../../../components/flamegraph';
import { PrimaryProfilingSearchBar } from '../../../components/profiling_app_page_template/primary_profiling_search_bar';
import { useProfilingParams } from '../../../hooks/use_profiling_params';
import { useProfilingRouter } from '../../../hooks/use_profiling_router';
import { useProfilingRoutePath } from '../../../hooks/use_profiling_route_path';
@ -58,12 +57,6 @@ export function FlameGraphView() {
return (
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<EuiPanel hasShadow={false} color="subdued">
<PrimaryProfilingSearchBar />
<EuiHorizontalRule />
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<AsyncComponent {...state} style={{ height: '100%' }} size="xl">
<FlameGraph

View file

@ -50,7 +50,7 @@ export function FlameGraphsView({ children }: { children: React.ReactElement })
];
return (
<ProfilingAppPageTemplate tabs={tabs} hideSearchBar={true}>
<ProfilingAppPageTemplate tabs={tabs} hideSearchBar={isDifferentialView}>
{children}
</ProfilingAppPageTemplate>
);

View file

@ -4,7 +4,15 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiDataGridRefProps, EuiDataGridSorting, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import {
EuiDataGridRefProps,
EuiDataGridSorting,
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiPanel,
EuiSpacer,
} from '@elastic/eui';
import React, { useRef } from 'react';
import { GridOnScrollProps } from 'react-window';
import { TopNFunctionSortField } from '../../../../common/functions';
@ -17,6 +25,8 @@ import {
} from '../../../components/normalization_menu';
import { PrimaryAndComparisonSearchBar } from '../../../components/primary_and_comparison_search_bar';
import { TopNFunctionsGrid } from '../../../components/topn_functions';
import { TopNFunctionsSummary } from '../../../components/topn_functions_summary';
import { AsyncStatus } from '../../../hooks/use_async';
import { useProfilingParams } from '../../../hooks/use_profiling_params';
import { useProfilingRouter } from '../../../hooks/use_profiling_router';
import { useProfilingRoutePath } from '../../../hooks/use_profiling_route_path';
@ -178,15 +188,28 @@ export function DifferentialTopNFunctionsView() {
<>
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<PrimaryAndComparisonSearchBar />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<NormalizationMenu
mode={normalizationMode}
options={normalizationOptions}
onChange={onChangeNormalizationMode}
/>
<EuiPanel hasShadow={false} color="subdued">
<PrimaryAndComparisonSearchBar />
<EuiHorizontalRule />
<NormalizationMenu
mode={normalizationMode}
options={normalizationOptions}
onChange={onChangeNormalizationMode}
/>
<EuiSpacer />
<TopNFunctionsSummary
baselineTopNFunctions={state.data}
comparisonTopNFunctions={comparisonState.data}
baselineScaleFactor={isNormalizedByTime ? baselineTime : baseline}
comparisonScaleFactor={isNormalizedByTime ? comparisonTime : comparison}
isLoading={
state.status === AsyncStatus.Loading ||
comparisonState.status === AsyncStatus.Loading
}
/>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem grow={false} />
<EuiFlexItem>
<EuiFlexGroup direction="row" gutterSize="s">
<EuiFlexItem>

View file

@ -42,7 +42,6 @@ export function registerTopNFunctionsSearchRoute({
async (context, request, response) => {
try {
const { timeFrom, timeTo, startIndex, endIndex, kuery }: QuerySchemaType = request.query;
const targetSampleSize = 20000; // minimum number of samples to get statistically sound results
const esClient = await getClient(context);
const profilingElasticsearchClient = createProfilingEsClient({ request, esClient });
@ -60,15 +59,16 @@ export function registerTopNFunctionsSearchRoute({
});
const topNFunctions = await withProfilingSpan('create_topn_functions', async () => {
return createTopNFunctions(
stackTraceEvents,
stackTraces,
stackFrames,
executables,
startIndex,
return createTopNFunctions({
endIndex,
samplingRate
);
events: stackTraceEvents,
executables,
samplingRate,
stackFrames,
stackTraces,
startIndex,
totalSeconds: timeTo - timeFrom,
});
});
return response.ok({