Skip to content

Commit 3eb3934

Browse files
authored
feat(perf): Improve MetricReadout behaviour with small numbers (#69307)
Percentages and rates are susceptible to bad rounding errors. e.g., if a percentage is very small like `0.0000123`, the rendered percentage will be `"0%"` but that's wrong! It's not _really_ 0, is it? A `minimumValue` option makes it so that if the number is small, it shows up as `<0.01%` which is closer to the truth.
1 parent d759688 commit 3eb3934

File tree

4 files changed

+47
-3
lines changed

4 files changed

+47
-3
lines changed

static/app/utils/formatters.spec.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,14 @@ describe('formatPercentage()', function () {
340340
expect(formatPercentage(0.10513494, 3)).toBe('10.513%');
341341
expect(formatPercentage(0.10513494, 4)).toBe('10.5135%');
342342
});
343+
344+
it('obeys a minimum value option', () => {
345+
expect(formatPercentage(0.0101, 0, {minimumValue: 0.01})).toBe('1%');
346+
expect(formatPercentage(0.01, 0, {minimumValue: 0.001})).toBe('1%');
347+
expect(formatPercentage(0.0001, 0, {minimumValue: 0.001})).toBe('<0.1%');
348+
expect(formatPercentage(-0.0001, 0, {minimumValue: 0.001})).toBe('<0.1%');
349+
expect(formatPercentage(0.00000234, 0, {minimumValue: 0.0001})).toBe('<0.01%');
350+
});
343351
});
344352

345353
describe('userDisplayName', function () {

static/app/utils/formatters.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,10 +373,23 @@ export function formatFloat(number: number, places: number) {
373373
/**
374374
* Format a value between 0 and 1 as a percentage
375375
*/
376-
export function formatPercentage(value: number, places: number = 2) {
376+
export function formatPercentage(
377+
value: number,
378+
places: number = 2,
379+
options: {
380+
minimumValue?: number;
381+
} = {}
382+
) {
377383
if (value === 0) {
378384
return '0%';
379385
}
386+
387+
const minimumValue = options.minimumValue ?? 0;
388+
389+
if (Math.abs(value) <= minimumValue) {
390+
return `<${minimumValue * 100}%`;
391+
}
392+
380393
return (
381394
round(value * 100, places).toLocaleString(undefined, {
382395
maximumFractionDigits: places,

static/app/views/performance/metricReadout.spec.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ describe('MetricReadout', function () {
4141
expect(screen.getByText('17.8/min')).toBeInTheDocument();
4242
});
4343

44+
it('limits smallest rate', () => {
45+
render(<MetricReadout title="Rate" unit={RateUnit.PER_MINUTE} value={0.0002441} />);
46+
47+
expect(screen.getByRole('heading', {name: 'Rate'})).toBeInTheDocument();
48+
expect(screen.getByText('<0.01/min')).toBeInTheDocument();
49+
});
50+
4451
it('renders milliseconds', () => {
4552
render(
4653
<MetricReadout title="Duration" unit={DurationUnit.MILLISECOND} value={223142123} />
@@ -64,6 +71,13 @@ describe('MetricReadout', function () {
6471
expect(screen.getByText('23.52%')).toBeInTheDocument();
6572
});
6673

74+
it('limits smallest percentage', () => {
75+
render(<MetricReadout title="Percentage" unit="percentage" value={0.000022317} />);
76+
77+
expect(screen.getByRole('heading', {name: 'Percentage'})).toBeInTheDocument();
78+
expect(screen.getByText('<0.01%')).toBeInTheDocument();
79+
});
80+
6781
it('renders counts', () => {
6882
render(<MetricReadout title="Count" unit="count" value={7800123} />);
6983

static/app/views/performance/metricReadout.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ function ReadoutContent({unit, value, tooltip, align = 'right', isLoading}: Prop
5858
if (isARateUnit(unit)) {
5959
renderedValue = (
6060
<NumberContainer align={align}>
61-
{formatRate(typeof value === 'string' ? parseFloat(value) : value, unit)}
61+
{formatRate(typeof value === 'string' ? parseFloat(value) : value, unit, {
62+
minimumValue: MINIMUM_RATE_VALUE,
63+
})}
6264
</NumberContainer>
6365
);
6466
}
@@ -96,7 +98,11 @@ function ReadoutContent({unit, value, tooltip, align = 'right', isLoading}: Prop
9698
if (unit === 'percentage') {
9799
renderedValue = (
98100
<NumberContainer align={align}>
99-
{formatPercentage(typeof value === 'string' ? parseFloat(value) : value)}
101+
{formatPercentage(
102+
typeof value === 'string' ? parseFloat(value) : value,
103+
undefined,
104+
{minimumValue: MINIMUM_PERCENTAGE_VALUE}
105+
)}
100106
</NumberContainer>
101107
);
102108
}
@@ -114,6 +120,9 @@ function ReadoutContent({unit, value, tooltip, align = 'right', isLoading}: Prop
114120
return <NumberContainer align={align}>{renderedValue}</NumberContainer>;
115121
}
116122

123+
const MINIMUM_RATE_VALUE = 0.01;
124+
const MINIMUM_PERCENTAGE_VALUE = 0.0001; // 0.01%
125+
117126
const NumberContainer = styled('div')<{align: 'left' | 'right'}>`
118127
text-align: ${p => p.align};
119128
font-variant-numeric: tabular-nums;

0 commit comments

Comments
 (0)