mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
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:
parent
257e52eb84
commit
1d5945aa90
19 changed files with 461 additions and 106 deletions
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -8,5 +8,5 @@
|
|||
import { asNumber } from './as_number';
|
||||
|
||||
export function asCost(value: number, unit: string = '$') {
|
||||
return `${asNumber(value)}${unit}`;
|
||||
return `${unit}${asNumber(value)}`;
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -50,7 +50,7 @@ export function FlameGraphsView({ children }: { children: React.ReactElement })
|
|||
];
|
||||
|
||||
return (
|
||||
<ProfilingAppPageTemplate tabs={tabs} hideSearchBar={true}>
|
||||
<ProfilingAppPageTemplate tabs={tabs} hideSearchBar={isDifferentialView}>
|
||||
{children}
|
||||
</ProfilingAppPageTemplate>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue