[Profiling] Diff topN functions impact estimate fix (#163749)

Context:
A few weeks ago I merged a PR that moved the calculation of the impact
estimate to the server. But by doing so I missed an essential part to
calculate it correct, the scale factor that is defined in the UI. I'm
reverting that change and making the calcs in the client again.

Along with that I fixed a problem and refactored the function that
calculate the impact simplifying it.

Before:
<img width="1313" alt="Screenshot 2023-08-14 at 10 06 39 AM"
src="c4cda975-4659-4570-afbe-4510f4e6c4fa">

After:
<img width="1320" alt="Screenshot 2023-08-14 at 10 07 29 AM"
src="98e20fc3-00bd-4266-8c8d-07fb6662c9e9">

<img width="1704" alt="Screenshot 2023-08-16 at 10 03 52 AM"
src="9dc780b2-b674-4d83-90e3-7865936dbeaf">
This commit is contained in:
Cauê Marcondes 2023-08-16 14:11:46 +01:00 committed by GitHub
parent d725c289af
commit 1efec8e0d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 270 additions and 178 deletions

View file

@ -8,53 +8,79 @@ import { calculateImpactEstimates } from '.';
describe('calculateImpactEstimates', () => {
it('calculates impact when countExclusive is lower than countInclusive', () => {
expect(
calculateImpactEstimates({
countExclusive: 500,
countInclusive: 1000,
totalSamples: 10000,
totalSeconds: 15 * 60, // 15m
})
).toEqual({
const { selfCPU, totalCPU, totalSamples } = calculateImpactEstimates({
countExclusive: 500,
countInclusive: 1000,
totalSamples: 10000,
totalSeconds: 15 * 60, // 15m
});
expect(totalCPU).toEqual({
annualizedCo2: 17.909333333333336,
annualizedCo2NoChildren: 8.954666666666668,
annualizedCoreSeconds: 1752000,
annualizedCoreSecondsNoChildren: 876000,
annualizedDollarCost: 20.683333333333334,
annualizedDollarCostNoChildren: 10.341666666666667,
co2: 0.0005111111111111112,
co2NoChildren: 0.0002555555555555556,
coreSeconds: 50,
coreSecondsNoChildren: 25,
dollarCost: 0.0005902777777777778,
dollarCostNoChildren: 0.0002951388888888889,
percentage: 0.1,
percentageNoChildren: 0.05,
});
expect(selfCPU).toEqual({
annualizedCo2: 8.954666666666668,
annualizedCoreSeconds: 876000,
annualizedDollarCost: 10.341666666666667,
co2: 0.0002555555555555556,
coreSeconds: 25,
dollarCost: 0.0002951388888888889,
percentage: 0.05,
});
expect(totalSamples).toEqual({
percentage: 1,
coreSeconds: 500,
annualizedCoreSeconds: 17520000,
co2: 0.005111111111111111,
annualizedCo2: 179.09333333333333,
dollarCost: 0.0059027777777777785,
annualizedDollarCost: 206.83333333333337,
});
});
it('calculates impact', () => {
expect(
calculateImpactEstimates({
countExclusive: 1000,
countInclusive: 1000,
totalSamples: 10000,
totalSeconds: 15 * 60, // 15m
})
).toEqual({
const { selfCPU, totalCPU, totalSamples } = calculateImpactEstimates({
countExclusive: 1000,
countInclusive: 1000,
totalSamples: 10000,
totalSeconds: 15 * 60, // 15m
});
expect(totalCPU).toEqual({
annualizedCo2: 17.909333333333336,
annualizedCo2NoChildren: 17.909333333333336,
annualizedCoreSeconds: 1752000,
annualizedCoreSecondsNoChildren: 1752000,
annualizedDollarCost: 20.683333333333334,
annualizedDollarCostNoChildren: 20.683333333333334,
co2: 0.0005111111111111112,
co2NoChildren: 0.0005111111111111112,
coreSeconds: 50,
coreSecondsNoChildren: 50,
dollarCost: 0.0005902777777777778,
dollarCostNoChildren: 0.0005902777777777778,
percentage: 0.1,
percentageNoChildren: 0.1,
});
expect(selfCPU).toEqual({
annualizedCo2: 17.909333333333336,
annualizedCoreSeconds: 1752000,
annualizedDollarCost: 20.683333333333334,
co2: 0.0005111111111111112,
coreSeconds: 50,
dollarCost: 0.0005902777777777778,
percentage: 0.1,
});
expect(totalSamples).toEqual({
percentage: 1,
coreSeconds: 500,
annualizedCoreSeconds: 17520000,
co2: 0.005111111111111111,
annualizedCo2: 179.09333333333333,
dollarCost: 0.0059027777777777785,
annualizedDollarCost: 206.83333333333337,
});
});
});

View file

@ -27,40 +27,52 @@ export function calculateImpactEstimates({
totalSamples: number;
totalSeconds: number;
}) {
const annualizedScaleUp = ANNUAL_SECONDS / totalSeconds;
return {
totalSamples: calculateImpact({
samples: totalSamples,
totalSamples,
totalSeconds,
}),
totalCPU: calculateImpact({
samples: countInclusive,
totalSamples,
totalSeconds,
}),
selfCPU: calculateImpact({
samples: countExclusive,
totalSamples,
totalSeconds,
}),
};
}
const percentage = countInclusive / totalSamples;
const percentageNoChildren = countExclusive / totalSamples;
function calculateImpact({
samples,
totalSamples,
totalSeconds,
}: {
samples: number;
totalSamples: number;
totalSeconds: number;
}) {
const annualizedScaleUp = ANNUAL_SECONDS / totalSeconds;
const totalCoreSeconds = totalSamples / 20;
const percentage = samples / totalSamples;
const coreSeconds = totalCoreSeconds * percentage;
const annualizedCoreSeconds = coreSeconds * annualizedScaleUp;
const coreSecondsNoChildren = totalCoreSeconds * percentageNoChildren;
const annualizedCoreSecondsNoChildren = coreSecondsNoChildren * annualizedScaleUp;
const coreHours = coreSeconds / (60 * 60);
const coreHoursNoChildren = coreSecondsNoChildren / (60 * 60);
const co2 = ((PER_CORE_WATT * coreHours) / 1000.0) * CO2_PER_KWH;
const co2NoChildren = ((PER_CORE_WATT * coreHoursNoChildren) / 1000.0) * CO2_PER_KWH;
const annualizedCo2 = co2 * annualizedScaleUp;
const annualizedCo2NoChildren = co2NoChildren * annualizedScaleUp;
const dollarCost = coreHours * CORE_COST_PER_HOUR;
const annualizedDollarCost = dollarCost * annualizedScaleUp;
const dollarCostNoChildren = coreHoursNoChildren * CORE_COST_PER_HOUR;
const annualizedDollarCostNoChildren = dollarCostNoChildren * annualizedScaleUp;
return {
percentage,
percentageNoChildren,
coreSeconds,
annualizedCoreSeconds,
coreSecondsNoChildren,
annualizedCoreSecondsNoChildren,
co2,
co2NoChildren,
annualizedCo2,
annualizedCo2NoChildren,
dollarCost,
annualizedDollarCost,
dollarCostNoChildren,
annualizedDollarCostNoChildren,
};
}

View file

@ -17,7 +17,7 @@ describe('TopN function operations', () => {
const { events, stackTraces, stackFrames, executables, samplingRate } =
decodeStackTraceResponse(response);
describe(`stacktraces from ${seconds} seconds and upsampled by ${upsampledBy}`, () => {
describe(`stacktraces upsampled by ${upsampledBy}`, () => {
const maxTopN = 5;
const topNFunctions = createTopNFunctions({
events,
@ -27,7 +27,6 @@ describe('TopN function operations', () => {
startIndex: 0,
endIndex: maxTopN,
samplingRate,
totalSeconds: seconds,
});
const exclusiveCounts = topNFunctions.TopN.map((value) => value.CountExclusive);

View file

@ -6,7 +6,6 @@
*/
import * as t from 'io-ts';
import { sumBy } from 'lodash';
import { calculateImpactEstimates } from './calculate_impact_estimates';
import { createFrameGroupID, FrameGroupID } from './frame_group';
import {
createStackFrameMetadata,
@ -35,18 +34,14 @@ type TopNFunction = Pick<
> & {
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;
selfCPU: number;
totalCPU: number;
}
export function createTopNFunctions({
@ -57,7 +52,6 @@ export function createTopNFunctions({
stackFrames,
stackTraces,
startIndex,
totalSeconds,
}: {
endIndex: number;
events: Map<StackTraceID, number>;
@ -66,7 +60,6 @@ export function createTopNFunctions({
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
@ -167,52 +160,25 @@ export function createTopNFunctions({
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,
selfCPU: sumSelfCPU,
totalCPU: sumTotalCPU,
};
}

View file

@ -99,8 +99,8 @@ export function FlameGraphTooltip({
labelStyle={{ fontWeight: 'bold' }}
/>
}
value={impactEstimates.percentage}
comparison={comparisonImpactEstimates?.percentage}
value={impactEstimates.totalCPU.percentage}
comparison={comparisonImpactEstimates?.totalCPU.percentage}
formatValue={asPercentage}
showDifference
formatDifferenceAsPercentage
@ -115,8 +115,8 @@ export function FlameGraphTooltip({
labelStyle={{ fontWeight: 'bold' }}
/>
}
value={impactEstimates.percentageNoChildren}
comparison={comparisonImpactEstimates?.percentageNoChildren}
value={impactEstimates.selfCPU.percentage}
comparison={comparisonImpactEstimates?.selfCPU.percentage}
showDifference
formatDifferenceAsPercentage
formatValue={asPercentage}
@ -144,8 +144,8 @@ export function FlameGraphTooltip({
label={i18n.translate('xpack.profiling.flameGraphTooltip.annualizedCo2', {
defaultMessage: `Annualized CO2`,
})}
value={impactEstimates.annualizedCo2}
comparison={comparisonImpactEstimates?.annualizedCo2}
value={impactEstimates.totalCPU.annualizedCo2}
comparison={comparisonImpactEstimates?.totalCPU.annualizedCo2}
formatValue={asWeight}
showDifference
formatDifferenceAsPercentage={false}
@ -155,8 +155,8 @@ export function FlameGraphTooltip({
label={i18n.translate('xpack.profiling.flameGraphTooltip.annualizedDollarCost', {
defaultMessage: `Annualized dollar cost`,
})}
value={impactEstimates.annualizedDollarCost}
comparison={comparisonImpactEstimates?.annualizedDollarCost}
value={impactEstimates.totalCPU.annualizedDollarCost}
comparison={comparisonImpactEstimates?.totalCPU.annualizedDollarCost}
formatValue={asCost}
showDifference
formatDifferenceAsPercentage={false}

View file

@ -28,22 +28,7 @@ export function getImpactRows({
totalSeconds: number;
isApproximate: boolean;
}) {
const {
percentage,
percentageNoChildren,
coreSeconds,
annualizedCoreSeconds,
coreSecondsNoChildren,
co2,
co2NoChildren,
annualizedCo2,
annualizedCo2NoChildren,
dollarCost,
dollarCostNoChildren,
annualizedDollarCost,
annualizedDollarCostNoChildren,
annualizedCoreSecondsNoChildren,
} = calculateImpactEstimates({
const { selfCPU, totalCPU } = calculateImpactEstimates({
countInclusive,
countExclusive,
totalSamples,
@ -53,11 +38,11 @@ export function getImpactRows({
const impactRows = [
{
label: <CPULabelWithHint type="total" labelSize="s" iconSize="s" />,
value: asPercentage(percentage),
value: asPercentage(totalCPU.percentage),
},
{
label: <CPULabelWithHint type="self" labelSize="s" iconSize="s" />,
value: asPercentage(percentageNoChildren),
value: asPercentage(selfCPU.percentage),
},
{
label: i18n.translate('xpack.profiling.flameGraphInformationWindow.samplesInclusiveLabel', {
@ -76,28 +61,28 @@ export function getImpactRows({
'xpack.profiling.flameGraphInformationWindow.coreSecondsInclusiveLabel',
{ defaultMessage: 'Core-seconds' }
),
value: asDuration(coreSeconds),
value: asDuration(totalCPU.coreSeconds),
},
{
label: i18n.translate(
'xpack.profiling.flameGraphInformationWindow.coreSecondsExclusiveLabel',
{ defaultMessage: 'Core-seconds (excl. children)' }
),
value: asDuration(coreSecondsNoChildren),
value: asDuration(selfCPU.coreSeconds),
},
{
label: i18n.translate(
'xpack.profiling.flameGraphInformationWindow.annualizedCoreSecondsInclusiveLabel',
{ defaultMessage: 'Annualized core-seconds' }
),
value: asDuration(annualizedCoreSeconds),
value: asDuration(totalCPU.annualizedCoreSeconds),
},
{
label: i18n.translate(
'xpack.profiling.flameGraphInformationWindow.annualizedCoreSecondsExclusiveLabel',
{ defaultMessage: 'Annualized core-seconds (excl. children)' }
),
value: asDuration(annualizedCoreSecondsNoChildren),
value: asDuration(selfCPU.annualizedCoreSeconds),
},
{
label: i18n.translate(
@ -106,56 +91,56 @@ export function getImpactRows({
defaultMessage: 'CO2 emission',
}
),
value: asWeight(co2),
value: asWeight(totalCPU.co2),
},
{
label: i18n.translate(
'xpack.profiling.flameGraphInformationWindow.co2EmissionExclusiveLabel',
{ defaultMessage: 'CO2 emission (excl. children)' }
),
value: asWeight(co2NoChildren),
value: asWeight(selfCPU.co2),
},
{
label: i18n.translate(
'xpack.profiling.flameGraphInformationWindow.annualizedCo2InclusiveLabel',
{ defaultMessage: 'Annualized CO2' }
),
value: asWeight(annualizedCo2),
value: asWeight(totalCPU.annualizedCo2),
},
{
label: i18n.translate(
'xpack.profiling.flameGraphInformationWindow.annualizedCo2ExclusiveLabel',
{ defaultMessage: 'Annualized CO2 (excl. children)' }
),
value: asWeight(annualizedCo2NoChildren),
value: asWeight(selfCPU.annualizedCo2),
},
{
label: i18n.translate(
'xpack.profiling.flameGraphInformationWindow.dollarCostInclusiveLabel',
{ defaultMessage: 'Dollar cost' }
),
value: asCost(dollarCost),
value: asCost(totalCPU.dollarCost),
},
{
label: i18n.translate(
'xpack.profiling.flameGraphInformationWindow.dollarCostExclusiveLabel',
{ defaultMessage: 'Dollar cost (excl. children)' }
),
value: asCost(dollarCostNoChildren),
value: asCost(selfCPU.dollarCost),
},
{
label: i18n.translate(
'xpack.profiling.flameGraphInformationWindow.annualizedDollarCostInclusiveLabel',
{ defaultMessage: 'Annualized dollar cost' }
),
value: asCost(annualizedDollarCost),
value: asCost(totalCPU.annualizedDollarCost),
},
{
label: i18n.translate(
'xpack.profiling.flameGraphInformationWindow.annualizedDollarCostExclusiveLabel',
{ defaultMessage: 'Annualized dollar cost (excl. children)' }
),
value: asCost(annualizedDollarCostNoChildren),
value: asCost(selfCPU.annualizedDollarCost),
},
];

View file

@ -64,10 +64,10 @@ export function FunctionRow({
return (
<EuiFlexGroup direction="row" gutterSize="xs">
<EuiFlexItem grow={false}>
<EuiText size="s">{functionRow.diff.rank}</EuiText>
<EuiIcon type={functionRow.diff.rank > 0 ? 'sortUp' : 'sortDown'} color={color} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIcon type={functionRow.diff.rank > 0 ? 'sortDown' : 'sortUp'} color={color} />
<EuiText size="s">{Math.abs(functionRow.diff.rank)}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
@ -102,16 +102,16 @@ export function FunctionRow({
if (
columnId === TopNFunctionSortField.AnnualizedCo2 &&
functionRow.impactEstimates?.annualizedCo2
functionRow.impactEstimates?.selfCPU?.annualizedCo2
) {
return <div>{asWeight(functionRow.impactEstimates.annualizedCo2)}</div>;
return <div>{asWeight(functionRow.impactEstimates.selfCPU.annualizedCo2)}</div>;
}
if (
columnId === TopNFunctionSortField.AnnualizedDollarCost &&
functionRow.impactEstimates?.annualizedDollarCost
functionRow.impactEstimates?.selfCPU?.annualizedDollarCost
) {
return <div>{asCost(functionRow.impactEstimates.annualizedDollarCost)}</div>;
return <div>{asCost(functionRow.impactEstimates.selfCPU.annualizedDollarCost)}</div>;
}
return null;

View file

@ -88,8 +88,15 @@ export const TopNFunctionsGrid = forwardRef(
comparisonScaleFactor,
comparisonTopNFunctions,
topNFunctions,
totalSeconds,
});
}, [topNFunctions, comparisonTopNFunctions, comparisonScaleFactor, baselineScaleFactor]);
}, [
baselineScaleFactor,
comparisonScaleFactor,
comparisonTopNFunctions,
topNFunctions,
totalSeconds,
]);
const { columns, leadingControlColumns } = useMemo(() => {
const gridColumns: EuiDataGridColumn[] = [

View file

@ -18,8 +18,8 @@ describe('Top N functions: Utils', () => {
it('returns correct value when percentage is 0', () => {
expect(getColorLabel(0)).toEqual({
color: 'danger',
label: '<0.01',
color: 'text',
label: '0%',
icon: undefined,
});
});
@ -31,5 +31,13 @@ describe('Top N functions: Utils', () => {
icon: 'sortDown',
});
});
it('returns correct value when percentage is Infinity', () => {
expect(getColorLabel(Infinity)).toEqual({
color: 'text',
label: undefined,
icon: undefined,
});
});
});
});

View file

@ -10,12 +10,20 @@ import { StackFrameMetadata } from '../../../common/profiling';
import { calculateImpactEstimates } from '../../../common/calculate_impact_estimates';
export function getColorLabel(percent: number) {
if (percent === 0) {
return { color: 'text', label: `0%`, icon: undefined };
}
const color = percent < 0 ? 'success' : 'danger';
const icon = percent < 0 ? 'sortUp' : 'sortDown';
const isSmallPercent = Math.abs(percent) <= 0.01;
const label = isSmallPercent ? '<0.01' : Math.abs(percent).toFixed(2) + '%';
const value = isSmallPercent ? '<0.01' : Math.abs(percent).toFixed(2);
return { color, label, icon: isSmallPercent ? undefined : icon };
if (isFinite(percent)) {
return { color, label: `${value}%`, icon };
}
return { color: 'text', label: undefined, icon: undefined };
}
export function scaleValue({ value, scaleFactor = 1 }: { value: number; scaleFactor?: number }) {
@ -47,11 +55,13 @@ 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 [];
@ -64,26 +74,43 @@ export function getFunctionsRows({
return topNFunctions.TopN.filter((topN) => topN.CountExclusive > 0).map((topN, i) => {
const comparisonRow = comparisonDataById?.[topN.Id];
const topNCountExclusiveScaled = scaleValue({
const scaledSelfCPU = scaleValue({
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: topNFunctions.TotalCount,
totalSeconds,
})
: undefined;
function calculateDiff() {
if (comparisonTopNFunctions && comparisonRow) {
const comparisonCountExclusiveScaled = scaleValue({
const comparisonScaledSelfCPU = scaleValue({
value: comparisonRow.CountExclusive,
scaleFactor: comparisonScaleFactor,
});
const scaledDiffSamples = scaledSelfCPU - comparisonScaledSelfCPU;
return {
rank: topN.Rank - comparisonRow.Rank,
samples: topNCountExclusiveScaled - comparisonCountExclusiveScaled,
samples: scaledDiffSamples,
selfCPU: comparisonRow.CountExclusive,
totalCPU: comparisonRow.CountInclusive,
selfCPUPerc: topN.selfCPUPerc - comparisonRow.selfCPUPerc,
totalCPUPerc: topN.totalCPUPerc - comparisonRow.totalCPUPerc,
impactEstimates: comparisonRow.impactEstimates,
selfCPUPerc:
selfCPUPerc - (comparisonRow.CountExclusive / comparisonTopNFunctions.TotalCount) * 100,
totalCPUPerc:
totalCPUPerc -
(comparisonRow.CountInclusive / comparisonTopNFunctions.TotalCount) * 100,
};
}
}
@ -91,12 +118,12 @@ export function getFunctionsRows({
return {
rank: topN.Rank,
frame: topN.Frame,
samples: topNCountExclusiveScaled,
selfCPUPerc: topN.selfCPUPerc,
totalCPUPerc: topN.totalCPUPerc,
samples: scaledSelfCPU,
selfCPUPerc,
totalCPUPerc,
selfCPU: topN.CountExclusive,
totalCPU: topN.CountInclusive,
impactEstimates: topN.impactEstimates,
impactEstimates,
diff: calculateDiff(),
};
});

View file

@ -7,7 +7,8 @@
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import React, { useMemo } from 'react';
import { calculateImpactEstimates } from '../../../common/calculate_impact_estimates';
import { TopNFunctions } from '../../../common/functions';
import { asCost } from '../../utils/formatters/as_cost';
import { asWeight } from '../../utils/formatters/as_weight';
@ -20,6 +21,8 @@ interface Props {
baselineScaleFactor?: number;
comparisonScaleFactor?: number;
isLoading: boolean;
baselineDuration: number;
comparisonDuration: number;
}
const ESTIMATED_VALUE_LABEL = i18n.translate('xpack.profiling.diffTopNFunctions.estimatedValue', {
@ -29,32 +32,65 @@ const ESTIMATED_VALUE_LABEL = i18n.translate('xpack.profiling.diffTopNFunctions.
export function TopNFunctionsSummary({
baselineTopNFunctions,
comparisonTopNFunctions,
baselineScaleFactor,
comparisonScaleFactor,
baselineScaleFactor = 1,
comparisonScaleFactor = 1,
isLoading,
baselineDuration,
comparisonDuration,
}: Props) {
const totalSamplesDiff = calculateBaseComparisonDiff({
baselineValue: baselineTopNFunctions?.TotalCount || 0,
baselineScaleFactor,
comparisonValue: comparisonTopNFunctions?.TotalCount || 0,
comparisonScaleFactor,
});
const baselineScaledTotalSamples = baselineTopNFunctions
? baselineTopNFunctions.TotalCount * baselineScaleFactor
: 0;
const co2EmissionDiff = calculateBaseComparisonDiff({
baselineValue: baselineTopNFunctions?.impactEstimates?.annualizedCo2 || 0,
baselineScaleFactor,
comparisonValue: comparisonTopNFunctions?.impactEstimates?.annualizedCo2 || 0,
comparisonScaleFactor,
formatValue: asWeight,
});
const comparisonScaledTotalSamples = comparisonTopNFunctions
? comparisonTopNFunctions.TotalCount * comparisonScaleFactor
: 0;
const costImpactDiff = calculateBaseComparisonDiff({
baselineValue: baselineTopNFunctions?.impactEstimates?.annualizedDollarCost || 0,
baselineScaleFactor,
comparisonValue: comparisonTopNFunctions?.impactEstimates?.annualizedDollarCost || 0,
comparisonScaleFactor,
formatValue: asCost,
});
const { co2EmissionDiff, costImpactDiff, totalSamplesDiff } = useMemo(() => {
const baseImpactEstimates = baselineTopNFunctions
? // Do NOT scale values here. This is intended to show the exact values spent throughout the year
calculateImpactEstimates({
countExclusive: baselineTopNFunctions.selfCPU,
countInclusive: baselineTopNFunctions.totalCPU,
totalSamples: baselineTopNFunctions.TotalCount,
totalSeconds: baselineDuration,
})
: undefined;
const comparisonImpactEstimates = comparisonTopNFunctions
? // Do NOT scale values here. This is intended to show the exact values spent throughout the year
calculateImpactEstimates({
countExclusive: comparisonTopNFunctions.selfCPU,
countInclusive: comparisonTopNFunctions.totalCPU,
totalSamples: comparisonTopNFunctions.TotalCount,
totalSeconds: comparisonDuration,
})
: undefined;
return {
totalSamplesDiff: calculateBaseComparisonDiff({
baselineValue: baselineScaledTotalSamples || 0,
comparisonValue: comparisonScaledTotalSamples || 0,
}),
co2EmissionDiff: calculateBaseComparisonDiff({
baselineValue: baseImpactEstimates?.totalSamples?.annualizedCo2 || 0,
comparisonValue: comparisonImpactEstimates?.totalSamples.annualizedCo2 || 0,
formatValue: asWeight,
}),
costImpactDiff: calculateBaseComparisonDiff({
baselineValue: baseImpactEstimates?.totalSamples.annualizedDollarCost || 0,
comparisonValue: comparisonImpactEstimates?.totalSamples.annualizedDollarCost || 0,
formatValue: asCost,
}),
};
}, [
baselineDuration,
baselineScaledTotalSamples,
baselineTopNFunctions,
comparisonDuration,
comparisonScaledTotalSamples,
comparisonTopNFunctions,
]);
const data = [
{
@ -62,14 +98,16 @@ export function TopNFunctionsSummary({
defaultMessage: '{label} overall performance by',
values: {
label:
isLoading || totalSamplesDiff.percentDiffDelta === undefined
isLoading ||
totalSamplesDiff.percentDiffDelta === undefined ||
totalSamplesDiff.label === undefined
? 'Gained/Lost'
: totalSamplesDiff?.percentDiffDelta > 0
? 'Lost'
: 'Gained',
},
}) as string,
baseValue: totalSamplesDiff.label || '',
baseValue: totalSamplesDiff.label || '0%',
baseIcon: totalSamplesDiff.icon,
baseColor: totalSamplesDiff.color,
titleHint: ESTIMATED_VALUE_LABEL,

View file

@ -0,0 +1,21 @@
/*
* 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 { getValueLable } from './summary_item';
describe('Summary item', () => {
it('returns value and percentage', () => {
expect(getValueLable('10', '1%')).toEqual('10 (1%)');
});
it('returns value', () => {
expect(getValueLable('10', undefined)).toEqual('10');
});
it('returns value when perc is an empty string', () => {
expect(getValueLable('10', '')).toEqual('10');
});
});

View file

@ -56,6 +56,10 @@ function BaseValue({ value, icon, color }: { value: string; icon?: string; color
);
}
export function getValueLable(value: string, perc?: string) {
return perc ? `${value} (${perc})` : `${value}`;
}
export function SummaryItem({
baseValue,
baseIcon,
@ -98,7 +102,7 @@ export function SummaryItem({
{!isLoading && comparisonValue ? (
<EuiText color={comparisonColor}>
{comparisonIcon ? <EuiIcon type={comparisonIcon} /> : null}
{`${comparisonValue} (${comparisonPerc})`}
{getValueLable(comparisonValue, comparisonPerc)}
</EuiText>
) : null}
</EuiStat>

View file

@ -206,6 +206,8 @@ export function DifferentialTopNFunctionsView() {
state.status === AsyncStatus.Loading ||
comparisonState.status === AsyncStatus.Loading
}
baselineDuration={totalSeconds}
comparisonDuration={totalComparisonSeconds}
/>
</EuiPanel>
</EuiFlexItem>
@ -236,13 +238,11 @@ export function DifferentialTopNFunctionsView() {
<TopNFunctionsGrid
ref={comparisonGridRef}
topNFunctions={comparisonState.data}
comparisonTopNFunctions={state.data}
totalSeconds={
comparisonTimeRange.inSeconds.end - comparisonTimeRange.inSeconds.start
}
isDifferentialView={true}
baselineScaleFactor={isNormalizedByTime ? comparisonTime : comparison}
comparisonTopNFunctions={state.data}
comparisonScaleFactor={isNormalizedByTime ? baselineTime : baseline}
totalSeconds={totalSeconds}
isDifferentialView={true}
onFrameClick={handleOnFrameClick}
onScroll={handleComparisonGridScroll}
showDiffColumn

View file

@ -67,7 +67,6 @@ export function registerTopNFunctionsSearchRoute({
stackFrames,
stackTraces,
startIndex,
totalSeconds: timeTo - timeFrom,
});
});