Skip to content

Commit

Permalink
Improve human-readable duration formatting (#647)
Browse files Browse the repository at this point in the history
* Add more units to duration formatting

Expands the number of units that the formatDuration function displays.
Previously the function only used milliseconds and seconds to display
durations, now it will support all units up to days. This is to make
it easier to read very long durations in the UI that would have
previously only been displayed in seconds.

As a side effect of this change, there are a few changes to the way existing
durations are displayed:
* 0ms -> 0μs
* 1.5μs
* 1.5ms
* 1.5s
* 1m 30s
* 1h 30m
* 1d 12h

Resolves #319

Signed-off-by: James Ferguson <jamesferguson497@gmail.com>

* Remove inputUnit parameter of formatDuration

The inputUnit parameter was not used anywhere and significantly
complicated the implementation of the formatDuration function.
Now, all inputs are assumed to be in microseconds which is consistent
with a few other methods in the date.tsx file.

Signed-off-by: James Ferguson <jamesferguson497@gmail.com>

* Reduce date format units to 2

Reduces the number of units displayed by the dateFormat function to
a maximum of 2. So "2d 5h 20m" will now be "2d 5h".

Signed-off-by: James Ferguson <jamesferguson497@gmail.com>

* Use decimals when formating μs, ms and s durations

This change will display durations less than a minute using decimals
as 1.3s is easier to comprehend than 1s 300ms as it was being
displayed before. Durations larger than a minute will still use multiple
units. Eg. 5m 27s

Signed-off-by: James Ferguson <jamesferguson497@gmail.com>

* Fix date.test.js licence

Signed-off-by: James Ferguson <jamesferguson497@gmail.com>
  • Loading branch information
jamesfer authored Oct 7, 2020
1 parent 6827f70 commit 6afb01e
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ exports[`ResultItemTitle renders as expected 1`] = `
<span
className="ub-right ub-relative"
>
0.15ms
150μs
</span>
<h3
className="ResultItemTitle--title"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ exports[`TraceHeader Attrs Attrs renders as expected when missing props 1`] = `
Duration:
</span>
<strong>
0ms
0μs
</strong>
</li>
<li
Expand Down Expand Up @@ -67,7 +67,7 @@ exports[`TraceHeader Attrs renders as expected when provided props 1`] = `
Duration:
</span>
<strong>
0.7ms
700μs
</strong>
</li>
<li
Expand Down
61 changes: 61 additions & 0 deletions packages/jaeger-ui/src/utils/date.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) 2020 The Jaeger Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { formatDuration, ONE_MILLISECOND, ONE_SECOND, ONE_MINUTE, ONE_HOUR, ONE_DAY } from './date.tsx';

describe('formatDuration', () => {
it('keeps microseconds the same', () => {
expect(formatDuration(1)).toBe('1μs');
});

it('displays a maximum of 2 units and rounds the last one', () => {
const input = 10 * ONE_DAY + 13 * ONE_HOUR + 30 * ONE_MINUTE;
expect(formatDuration(input)).toBe('10d 14h');
});

it('skips units that are empty', () => {
const input = 2 * ONE_DAY + 5 * ONE_MINUTE;
expect(formatDuration(input)).toBe('2d');
});

it('displays milliseconds in decimals', () => {
const input = 2 * ONE_MILLISECOND + 357;
expect(formatDuration(input)).toBe('2.36ms');
});

it('displays seconds in decimals', () => {
const input = 2 * ONE_SECOND + 357 * ONE_MILLISECOND;
expect(formatDuration(input)).toBe('2.36s');
});

it('displays minutes in split units', () => {
const input = 2 * ONE_MINUTE + 30 * ONE_SECOND + 555 * ONE_MILLISECOND;
expect(formatDuration(input)).toBe('2m 31s');
});

it('displays hours in split units', () => {
const input = 2 * ONE_HOUR + 30 * ONE_MINUTE + 30 * ONE_SECOND;
expect(formatDuration(input)).toBe('2h 31m');
});

it('displays times less than a μs', () => {
const input = 0.1;
expect(formatDuration(input)).toBe('0.1μs');
});

it('displays times of 0', () => {
const input = 0;
expect(formatDuration(input)).toBe('0μs');
});
});
45 changes: 34 additions & 11 deletions packages/jaeger-ui/src/utils/date.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

import moment from 'moment';
import _dropWhile from 'lodash/dropWhile';
import _round from 'lodash/round';

import { toFloatPrecision } from './number';
Expand All @@ -25,8 +26,20 @@ export const STANDARD_TIME_FORMAT = 'HH:mm';
export const STANDARD_DATETIME_FORMAT = 'MMMM D YYYY, HH:mm:ss.SSS';
export const ONE_MILLISECOND = 1000;
export const ONE_SECOND = 1000 * ONE_MILLISECOND;
export const ONE_MINUTE = 60 * ONE_SECOND;
export const ONE_HOUR = 60 * ONE_MINUTE;
export const ONE_DAY = 24 * ONE_HOUR;
export const DEFAULT_MS_PRECISION = Math.log10(ONE_MILLISECOND);

const UNIT_STEPS: { unit: string; microseconds: number; ofPrevious: number }[] = [
{ unit: 'd', microseconds: ONE_DAY, ofPrevious: 24 },
{ unit: 'h', microseconds: ONE_HOUR, ofPrevious: 60 },
{ unit: 'm', microseconds: ONE_MINUTE, ofPrevious: 60 },
{ unit: 's', microseconds: ONE_SECOND, ofPrevious: 1000 },
{ unit: 'ms', microseconds: ONE_MILLISECOND, ofPrevious: 1000 },
{ unit: 'μs', microseconds: 1, ofPrevious: 1000 },
];

/**
* @param {number} timestamp
* @param {number} initialTimestamp
Expand Down Expand Up @@ -83,23 +96,33 @@ export function formatSecondTime(duration: number) {
}

/**
* Humanizes the duration based on the inputUnit
* Humanizes the duration for display.
*
* Example:
* 5000ms => 5s
* 1000μs => 1ms
* 183840s => 2d 3h
*
* @param {number} duration (in microseconds)
* @return {string} formatted duration
*/
export function formatDuration(duration: number, inputUnit: string = 'microseconds'): string {
let d = duration;
if (inputUnit === 'microseconds') {
d = duration / 1000;
}
let units = 'ms';
if (d >= 1000) {
units = 's';
d /= 1000;
export function formatDuration(duration: number): string {
// Drop all units that are too large except the last one
const [primaryUnit, secondaryUnit] = _dropWhile(
UNIT_STEPS,
({ microseconds }, index) => index < UNIT_STEPS.length - 1 && microseconds > duration
);

if (primaryUnit.ofPrevious === 1000) {
// If the unit is decimal based, display as a decimal
return `${_round(duration / primaryUnit.microseconds, 2)}${primaryUnit.unit}`;
}
return _round(d, 2) + units;

const primaryValue = Math.floor(duration / primaryUnit.microseconds);
const primaryUnitString = `${primaryValue}${primaryUnit.unit}`;
const secondaryValue = Math.round((duration / secondaryUnit.microseconds) % primaryUnit.ofPrevious);
const secondaryUnitString = `${secondaryValue}${secondaryUnit.unit}`;
return secondaryValue === 0 ? primaryUnitString : `${primaryUnitString} ${secondaryUnitString}`;
}

export function formatRelativeDate(value: any, fullMonthName: boolean = false) {
Expand Down

0 comments on commit 6afb01e

Please sign in to comment.