mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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:
parent
d725c289af
commit
1efec8e0d0
15 changed files with 270 additions and 178 deletions
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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[] = [
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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(),
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -67,7 +67,6 @@ export function registerTopNFunctionsSearchRoute({
|
|||
stackFrames,
|
||||
stackTraces,
|
||||
startIndex,
|
||||
totalSeconds: timeTo - timeFrom,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue