Skip to content

Commit

Permalink
fix(core): fix precision loss in numberToHrtime (#3480)
Browse files Browse the repository at this point in the history
  • Loading branch information
legendecas authored Jan 3, 2023
1 parent 3fd6fb8 commit c93ab9e
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ For experimental package changes, see the [experimental CHANGELOG](experimental/
* `telemetry.sdk.version`
* fix(selenium-tests): updated webpack version for selenium test issue [#3456](https://github.com/open-telemetry/opentelemetry-js/issues/3456) @SaumyaBhushan
* fix(sdk-metrics): fix duplicated registration of metrics for collectors [#3488](https://github.com/open-telemetry/opentelemetry-js/pull/3488) @legendecas
* fix(core): fix precision loss in numberToHrtime [#3480](https://github.com/open-telemetry/opentelemetry-js/pull/3480) @legendecas

### :books: (Refine Doc)

Expand Down
16 changes: 4 additions & 12 deletions packages/opentelemetry-core/src/common/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,20 @@ import { otperformance as performance } from '../platform';
import { TimeOriginLegacy } from './types';

const NANOSECOND_DIGITS = 9;
const NANOSECOND_DIGITS_IN_MILLIS = 6;
const MILLISECONDS_TO_NANOSECONDS = Math.pow(10, NANOSECOND_DIGITS_IN_MILLIS);
const SECOND_TO_NANOSECONDS = Math.pow(10, NANOSECOND_DIGITS);

/**
* Converts a number to HrTime, HrTime = [number, number].
* The first number is UNIX Epoch time in seconds since 00:00:00 UTC on 1 January 1970.
* The second number represents the partial second elapsed since Unix Epoch time represented by first number in nanoseconds.
* For example, 2021-01-01T12:30:10.150Z in UNIX Epoch time in milliseconds is represented as 1609504210150.
* numberToHrtime calculates the first number by converting and truncating the Epoch time in milliseconds to seconds:
* HrTime[0] = Math.trunc(1609504210150 / 1000) = 1609504210.
* numberToHrtime calculates the second number by converting the digits after the decimal point of the subtraction, (1609504210150 / 1000) - HrTime[0], to nanoseconds:
* HrTime[1] = Number((1609504210.150 - HrTime[0]).toFixed(9)) * SECOND_TO_NANOSECONDS = 150000000.
* This is represented in HrTime format as [1609504210, 150000000].
* Converts a number of milliseconds from epoch to HrTime([seconds, remainder in nanoseconds]).
* @param epochMillis
*/
function numberToHrtime(epochMillis: number): api.HrTime {
const epochSeconds = epochMillis / 1000;
// Decimals only.
const seconds = Math.trunc(epochSeconds);
// Round sub-nanosecond accuracy to nanosecond.
const nanos =
Number((epochSeconds - seconds).toFixed(NANOSECOND_DIGITS)) *
SECOND_TO_NANOSECONDS;
const nanos = Math.round((epochMillis % 1000) * MILLISECONDS_TO_NANOSECONDS);
return [seconds, nanos];
}

Expand Down
18 changes: 17 additions & 1 deletion packages/opentelemetry-core/test/common/time.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ describe('time', () => {
it('should convert Date hrTime', () => {
const timeInput = new Date(1609297640313);
const output = timeInputToHrTime(timeInput);
assert.deepStrictEqual(output, [1609297640, 312999964]);
assert.deepStrictEqual(output, [1609297640, 313000000]);
});

it('should convert epoch milliseconds hrTime', () => {
Expand All @@ -114,6 +114,22 @@ describe('time', () => {
assert.deepStrictEqual(output[0], Math.trunc(timeInput / 1000));
});

it('should convert arbitrary epoch milliseconds (with sub-millis precision) hrTime', () => {
sinon.stub(performance, 'timeOrigin').value(111.5);
const inputs = [
// [ input, expected ]
[1609297640313, [1609297640, 313000000]],
// inevitable precision loss without decimal arithmetics.
[1609297640313.333, [1609297640, 313333008]],
// eslint-disable-next-line @typescript-eslint/no-loss-of-precision
[1609297640313.333333333, [1609297640, 313333252]],
] as const;
for (const [idx, input] of inputs.entries()) {
const output = timeInputToHrTime(input[0]);
assert.deepStrictEqual(output, input[1], `input[${idx}]: ${input}`);
}
});

it('should convert performance.now() hrTime', () => {
sinon.stub(performance, 'timeOrigin').value(111.5);

Expand Down

0 comments on commit c93ab9e

Please sign in to comment.