[Profiling] Fix calculation/formatting of frame info values (#141909)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Joseph Crail <joseph.crail@elastic.co>
This commit is contained in:
Dario Gieselaar 2022-10-03 20:33:55 +02:00 committed by GitHub
parent e1aa6a6d24
commit e0029ed191
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 123 additions and 44 deletions

View file

@ -25,11 +25,10 @@ interface Props {
exeFileName: string;
functionName: string;
sourceFileName: string;
samples: number;
childSamples: number;
countInclusive: number;
countExclusive: number;
};
sampledTraces: number;
totalTraces: number;
totalSamples: number;
totalSeconds: number;
onClose: () => void;
status: AsyncStatus;
@ -105,8 +104,7 @@ function FlamegraphFrameInformationPanel({
export function FlamegraphInformationWindow({
onClose,
frame,
sampledTraces,
totalTraces,
totalSamples,
totalSeconds,
status,
}: Props) {
@ -122,14 +120,13 @@ export function FlamegraphInformationWindow({
);
}
const { childSamples, exeFileName, samples, functionName, sourceFileName } = frame;
const { exeFileName, functionName, sourceFileName, countInclusive, countExclusive } = frame;
const impactRows = getImpactRows({
samples,
childSamples,
sampledTraces,
countInclusive,
countExclusive,
totalSamples,
totalSeconds,
totalTraces,
});
return (

View file

@ -8,6 +8,7 @@
import { i18n } from '@kbn/i18n';
import { asCost } from '../../utils/formatters/as_cost';
import { asDuration } from '../../utils/formatters/as_duration';
import { asNumber } from '../../utils/formatters/as_number';
import { asPercentage } from '../../utils/formatters/as_percentage';
import { asWeight } from '../../utils/formatters/as_weight';
@ -23,21 +24,19 @@ const CO2_PER_KWH = 0.92;
const CORE_COST_PER_HOUR = 0.0425;
export function getImpactRows({
samples,
childSamples,
sampledTraces,
totalTraces,
countInclusive,
countExclusive,
totalSamples,
totalSeconds,
}: {
samples: number;
childSamples: number;
sampledTraces: number;
totalTraces: number;
countInclusive: number;
countExclusive: number;
totalSamples: number;
totalSeconds: number;
}) {
const percentage = samples / sampledTraces;
const percentageNoChildren = (samples - childSamples) / sampledTraces;
const totalCoreSeconds = totalTraces / 20;
const percentage = countInclusive / totalSamples;
const percentageNoChildren = countExclusive / totalSamples;
const totalCoreSeconds = totalSamples / 20;
const coreSeconds = totalCoreSeconds * percentage;
const coreSecondsNoChildren = totalCoreSeconds * percentageNoChildren;
const coreHours = coreSeconds / (60 * 60);
@ -70,10 +69,16 @@ export function getImpactRows({
value: asPercentage(percentageNoChildren),
},
{
label: i18n.translate('xpack.profiling.flameGraphInformationWindow.samplesLabel', {
label: i18n.translate('xpack.profiling.flameGraphInformationWindow.samplesInclusiveLabel', {
defaultMessage: 'Samples',
}),
value: samples,
value: asNumber(countInclusive),
},
{
label: i18n.translate('xpack.profiling.flameGraphInformationWindow.samplesExclusiveLabel', {
defaultMessage: 'Samples (excl. children)',
}),
value: asNumber(countExclusive),
},
{
label: i18n.translate(

View file

@ -31,11 +31,9 @@ function TooltipRow({
formatAsPercentage: boolean;
showChange: boolean;
}) {
const valueLabel = formatAsPercentage ? asPercentage(value, 2) : value.toString();
const valueLabel = formatAsPercentage ? asPercentage(value) : value.toString();
const comparisonLabel =
formatAsPercentage && isNumber(comparison)
? asPercentage(comparison, 2)
: comparison?.toString();
formatAsPercentage && isNumber(comparison) ? asPercentage(comparison) : comparison?.toString();
const diff = showChange && isNumber(comparison) ? comparison - value : undefined;
@ -46,7 +44,7 @@ function TooltipRow({
defaultMessage: 'no change',
});
} else if (formatAsPercentage && diff !== undefined) {
diffLabel = asPercentage(diff, 2);
diffLabel = asPercentage(diff);
}
return (
@ -226,10 +224,8 @@ export const FlameGraph: React.FC<FlameGraphProps> = ({
exeFileName: highlightedFrame.ExeFileName,
sourceFileName: highlightedFrame.SourceFilename,
functionName: highlightedFrame.FunctionName,
samples: primaryFlamegraph.Samples[highlightedVmIndex],
childSamples:
primaryFlamegraph.Samples[highlightedVmIndex] -
primaryFlamegraph.CountExclusive[highlightedVmIndex],
countInclusive: primaryFlamegraph.Samples[highlightedVmIndex],
countExclusive: primaryFlamegraph.CountExclusive[highlightedVmIndex],
}
: undefined;
@ -315,8 +311,7 @@ export const FlameGraph: React.FC<FlameGraphProps> = ({
frame={selected}
status={highlightedFrameStatus}
totalSeconds={primaryFlamegraph?.TotalSeconds ?? 0}
totalTraces={primaryFlamegraph?.TotalTraces ?? 0}
sampledTraces={primaryFlamegraph?.SampledTraces ?? 0}
totalSamples={totalSamples}
onClose={() => {
setShowInformationWindow(false);
}}

View file

@ -194,7 +194,7 @@ export const SubChart: React.FC<SubChartProps> = ({
)}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">{asPercentage(percentage / 100, 2)}</EuiText>
<EuiText size="s">{asPercentage(percentage / 100)}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>

View file

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

View file

@ -4,9 +4,25 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import moment from 'moment';
moment.relativeTimeRounding((t) => {
const DIGITS = 2; // like: 2.56 minutes
return Math.round(t * Math.pow(10, DIGITS)) / Math.pow(10, DIGITS);
});
moment.relativeTimeThreshold('y', 365);
moment.relativeTimeThreshold('M', 12);
moment.relativeTimeThreshold('w', 4);
moment.relativeTimeThreshold('d', 31);
moment.relativeTimeThreshold('h', 24);
moment.relativeTimeThreshold('m', 60);
moment.relativeTimeThreshold('s', 60);
moment.relativeTimeThreshold('ss', 0);
export function asDuration(valueInSeconds: number) {
if (valueInSeconds === 0) {
return i18n.translate('xpack.profiling.zeroSeconds', { defaultMessage: '0 seconds' });
}
return moment.duration(valueInSeconds * 1000).humanize();
}

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 { asNumber } from './as_number';
describe('asNumber', () => {
it('rounds numbers appropriately', () => {
expect(asNumber(999)).toBe('999');
expect(asNumber(1.11)).toBe('1.11');
expect(asNumber(0.001)).toBe('~0.00');
expect(asNumber(0)).toBe('0');
});
it('adds k/m/b where needed', () => {
expect(asNumber(999.999)).toBe('1k');
expect(asNumber(4.5e5)).toBe('450k');
expect(asNumber(4.5001e5)).toBe('450.01k');
expect(asNumber(2.4991e7)).toBe('24.99m');
expect(asNumber(9e9)).toBe('9b');
});
});

View file

@ -0,0 +1,30 @@
/*
* 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.
*/
export function asNumber(value: number): string {
if (value === 0) {
return '0';
}
value = Math.round(value * 100) / 100;
if (value < 0.01) {
return '~0.00';
}
if (value < 1e3) {
return value.toString();
}
if (value < 1e6) {
return `${asNumber(value / 1e3)}k`;
}
if (value < 1e9) {
return `${asNumber(value / 1e6)}m`;
}
return `${asNumber(value / 1e9)}b`;
}

View file

@ -5,6 +5,8 @@
* 2.0.
*/
export function asPercentage(value: number, precision: number = 0) {
return `${Number(value * 100).toFixed(precision)}%`;
import { asNumber } from './as_number';
export function asPercentage(value: number) {
return `${asNumber(value * 100)}%`;
}

View file

@ -6,12 +6,13 @@
*/
import { i18n } from '@kbn/i18n';
import { asNumber } from './as_number';
const ONE_POUND_TO_A_KILO = 0.45359237;
export function asWeight(valueInPounds: number, precision: number = 2) {
const lbs = valueInPounds.toPrecision(precision);
const kgs = Number(valueInPounds * ONE_POUND_TO_A_KILO).toPrecision(precision);
export function asWeight(valueInPounds: number) {
const lbs = asNumber(valueInPounds);
const kgs = asNumber(Number(valueInPounds * ONE_POUND_TO_A_KILO));
return i18n.translate('xpack.profiling.formatters.weight', {
defaultMessage: `{lbs} lbs / {kgs} kg`,