Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.

Commit 47a672e

Browse files
authored
Get Timestamp with process.hrtime and Date.now (#261)
* Replace process.hrtime with Date.now Although process.hrtime() provides a high-resolution timer, it is from an unknown relative time, not epoch i.e. The time is relative to an arbitrary time in the past (not related to the time of day) and therefore not subject to clock drifts. The whole purpose of hrtime is to gather information on intervals or durations. That's why it doesn't really matter what the point in time was. Also, it is impossible to construct the ISO timestamp from hrtime, which is required when we export metrics data using stackdriver exporter. I am proposing to use Date.now function, as it returns the number of milliseconds elapsed since 1 January 1970 00:00:00 UTC. I have written timestampFromMillis function to construct Timestamp object from the given milliseconds. * Construct Timestamp from Date.now and Process.hrtime * mock time for tests
1 parent ebbc3e3 commit 47a672e

10 files changed

Lines changed: 471 additions & 175 deletions

File tree

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Copyright 2019, OpenCensus Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 the "License";
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {Timestamp} from '../metrics/export/types';
18+
19+
const MILLIS_PER_SECOND = 1e3;
20+
const NANOS_PER_MILLI = 1e3 * 1e3;
21+
const NANOS_PER_SECOND = 1e3 * 1e3 * 1e3;
22+
23+
let hrtime = process.hrtime;
24+
let hrtimeOrigin: [number, number] = [0, 0];
25+
let hrtimeRefSeconds = 0;
26+
let hrtimeRefNanos = 0;
27+
28+
function setHrtimeReference() {
29+
resetHrtimeFunctionCache();
30+
hrtimeOrigin = hrtime();
31+
const refTime = Date.now();
32+
hrtimeRefSeconds = Math.floor(refTime / MILLIS_PER_SECOND);
33+
hrtimeRefNanos = (refTime % MILLIS_PER_SECOND) * NANOS_PER_MILLI;
34+
}
35+
36+
/**
37+
* This is used to enable tests to mock process.hrtime while still allow us to
38+
* cache it.
39+
*/
40+
function resetHrtimeFunctionCache() {
41+
hrtime = process.hrtime;
42+
}
43+
44+
/**
45+
* Gets the current timestamp with seconds and nanoseconds.
46+
*
47+
* @returns {Timestamp} The Timestamp.
48+
*/
49+
export function getTimestampWithProcessHRTime(): Timestamp {
50+
const [offsetSecs, offsetNanos] = hrtime(hrtimeOrigin);
51+
52+
// determine drift in seconds and nanoseconds
53+
const seconds = hrtimeRefSeconds + offsetSecs;
54+
const nanos = hrtimeRefNanos + offsetNanos;
55+
56+
if (nanos >= NANOS_PER_SECOND) {
57+
return {seconds: seconds + 1, nanos: nanos % NANOS_PER_SECOND};
58+
}
59+
return {seconds, nanos};
60+
}
61+
62+
setHrtimeReference();
63+
64+
export const TEST_ONLY = {
65+
setHrtimeReference,
66+
resetHrtimeFunctionCache
67+
};

packages/opencensus-core/src/metrics/gauges/derived-gauge.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17+
import {getTimestampWithProcessHRTime} from '../../common/time-util';
1718
import {validateArrayElementsNotNull, validateNotNull} from '../../common/validations';
18-
import {LabelKey, LabelValue, Metric, MetricDescriptor, MetricDescriptorType, TimeSeries} from '../export/types';
19+
import {LabelKey, LabelValue, Metric, MetricDescriptor, MetricDescriptorType, TimeSeries, Timestamp} from '../export/types';
1920
import * as types from '../gauges/types';
2021
import {hashLabelValues} from '../utils';
2122

@@ -204,16 +205,14 @@ export class DerivedGauge implements types.Meter {
204205
if (this.registeredPoints.size === 0) {
205206
return null;
206207
}
207-
const [seconds, nanos] = process.hrtime();
208+
const timestamp: Timestamp = getTimestampWithProcessHRTime();
208209
return {
209210
descriptor: this.metricDescriptor,
210211
timeseries: Array.from(
211212
this.registeredPoints,
212213
([_, gaugeEntry]) => ({
213214
labelValues: gaugeEntry.labelValues,
214-
points: [
215-
{value: gaugeEntry.extractor(), timestamp: {seconds, nanos}}
216-
]
215+
points: [{value: gaugeEntry.extractor(), timestamp}]
217216
} as TimeSeries))
218217
};
219218
}

packages/opencensus-core/src/metrics/gauges/gauge.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17+
import {getTimestampWithProcessHRTime} from '../../common/time-util';
1718
import {validateArrayElementsNotNull, validateNotNull} from '../../common/validations';
1819
import {LabelKey, LabelValue, Metric, MetricDescriptor, MetricDescriptorType, TimeSeries, Timestamp} from '../export/types';
1920
import * as types from '../gauges/types';
@@ -129,12 +130,11 @@ export class Gauge implements types.Meter {
129130
if (this.registeredPoints.size === 0) {
130131
return null;
131132
}
132-
const [seconds, nanos] = process.hrtime();
133+
const timestamp: Timestamp = getTimestampWithProcessHRTime();
133134
return {
134135
descriptor: this.metricDescriptor,
135136
timeseries: Array.from(
136-
this.registeredPoints,
137-
([_, point]) => point.getTimeSeries({seconds, nanos}))
137+
this.registeredPoints, ([_, point]) => point.getTimeSeries(timestamp))
138138
};
139139
}
140140
}

packages/opencensus-core/src/stats/view.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import * as defaultLogger from '../common/console-logger';
18+
import {getTimestampWithProcessHRTime} from '../common/time-util';
1819
import * as loggerTypes from '../common/types';
1920
import {DistributionValue, LabelValue, Metric, MetricDescriptor, MetricDescriptorType, Point, TimeSeries, Timestamp} from '../metrics/export/types';
2021

@@ -206,18 +207,16 @@ export class BaseView implements View {
206207
let startTimestamp: Timestamp;
207208

208209
// The moment when this point was recorded.
209-
const [currentSeconds, currentNanos] = process.hrtime();
210-
const now: Timestamp = {seconds: currentSeconds, nanos: currentNanos};
210+
const now: Timestamp = getTimestampWithProcessHRTime();
211211

212212
switch (type) {
213213
case MetricDescriptorType.GAUGE_INT64:
214214
case MetricDescriptorType.GAUGE_DOUBLE:
215215
startTimestamp = null;
216216
break;
217217
default:
218-
const [seconds, nanos] = process.hrtime();
219218
// TODO (mayurkale): This should be set when create Cumulative view.
220-
startTimestamp = {seconds, nanos};
219+
startTimestamp = getTimestampWithProcessHRTime();
221220
}
222221

223222
const timeseries: TimeSeries[] = [];

packages/opencensus-core/test/test-derived-gauge.ts

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
*/
1616

1717
import * as assert from 'assert';
18-
import {LabelKey, LabelValue, MetricDescriptorType} from '../src/metrics/export/types';
18+
19+
import {TEST_ONLY} from '../src/common/time-util';
20+
import {LabelKey, LabelValue, MetricDescriptorType, Timestamp} from '../src/metrics/export/types';
1921
import {DerivedGauge} from '../src/metrics/gauges/derived-gauge';
2022

2123
const METRIC_NAME = 'metric-name';
@@ -29,8 +31,10 @@ const LABEL_VALUES_400: LabelValue[] = [{value: '400'}];
2931
const LABEL_VALUES_EXRTA: LabelValue[] = [{value: '200'}, {value: '400'}];
3032

3133
describe('DerivedGauge', () => {
32-
const oldProcessHrtime = process.hrtime;
3334
let instance: DerivedGauge;
35+
const realHrtimeFn = process.hrtime;
36+
const realNowFn = Date.now;
37+
const mockedTime: Timestamp = {seconds: 1450000100, nanos: 1e7};
3438
const expectedMetricDescriptor = {
3539
name: METRIC_NAME,
3640
description: METRIC_DESCRIPTION,
@@ -42,11 +46,18 @@ describe('DerivedGauge', () => {
4246
beforeEach(() => {
4347
instance = new DerivedGauge(
4448
METRIC_NAME, METRIC_DESCRIPTION, UNIT, GAUGE_INT64, LABEL_KEYS);
45-
process.hrtime = () => [1000, 1e7];
49+
50+
process.hrtime = () => [100, 1e7];
51+
Date.now = () => 1450000000000;
52+
// Force the clock to recalibrate the time offset with the mocked time
53+
TEST_ONLY.setHrtimeReference();
4654
});
4755

4856
afterEach(() => {
49-
process.hrtime = oldProcessHrtime;
57+
process.hrtime = realHrtimeFn;
58+
Date.now = realNowFn;
59+
// Reset the hrtime reference so that it uses a real clock again.
60+
TEST_ONLY.resetHrtimeFunctionCache();
5061
});
5162

5263
describe('createTimeSeries()', () => {
@@ -73,14 +84,19 @@ describe('DerivedGauge', () => {
7384
map.set('key', 'value');
7485
instance.createTimeSeries(LABEL_VALUES_200, map);
7586
map.set('key1', 'value1');
87+
7688
let metric = instance.getMetric();
7789
assert.notEqual(metric, null);
7890
assert.deepStrictEqual(metric.descriptor, expectedMetricDescriptor);
7991
assert.equal(metric.timeseries.length, 1);
8092
assert.deepStrictEqual(
8193
metric.timeseries, [{
8294
labelValues: LABEL_VALUES_200,
83-
points: [{value: 2, timestamp: {nanos: 1e7, seconds: 1000}}]
95+
points: [{
96+
value: 2,
97+
timestamp:
98+
{nanos: mockedTime.nanos, seconds: mockedTime.seconds}
99+
}]
84100
}]);
85101
// add data in collection
86102
map.set('key2', 'value2');
@@ -98,11 +114,19 @@ describe('DerivedGauge', () => {
98114
assert.deepStrictEqual(metric.timeseries, [
99115
{
100116
labelValues: LABEL_VALUES_200,
101-
points: [{value: 4, timestamp: {nanos: 1e7, seconds: 1000}}]
117+
points: [{
118+
value: 4,
119+
timestamp:
120+
{nanos: mockedTime.nanos, seconds: mockedTime.seconds}
121+
}]
102122
},
103123
{
104124
labelValues: LABEL_VALUES_400,
105-
points: [{value: 5, timestamp: {nanos: 1e7, seconds: 1000}}]
125+
points: [{
126+
value: 5,
127+
timestamp:
128+
{nanos: mockedTime.nanos, seconds: mockedTime.seconds}
129+
}]
106130
}
107131
]);
108132
});
@@ -121,7 +145,11 @@ describe('DerivedGauge', () => {
121145
assert.deepStrictEqual(
122146
metric.timeseries, [{
123147
labelValues: LABEL_VALUES_200,
124-
points: [{value: 45, timestamp: {nanos: 1e7, seconds: 1000}}]
148+
points: [{
149+
value: 45,
150+
timestamp:
151+
{nanos: mockedTime.nanos, seconds: mockedTime.seconds}
152+
}]
125153
}]);
126154
});
127155
it('should return a Metric (Double) - custom object', () => {
@@ -147,7 +175,11 @@ describe('DerivedGauge', () => {
147175
assert.deepStrictEqual(
148176
metric.timeseries, [{
149177
labelValues: LABEL_VALUES_200,
150-
points: [{value: 0.7, timestamp: {nanos: 1e7, seconds: 1000}}]
178+
points: [{
179+
value: 0.7,
180+
timestamp:
181+
{nanos: mockedTime.nanos, seconds: mockedTime.seconds}
182+
}]
151183
}]);
152184
});
153185
it('should throw an error when obj is null', () => {
@@ -166,7 +198,11 @@ describe('DerivedGauge', () => {
166198
assert.deepStrictEqual(
167199
metric.timeseries, [{
168200
labelValues: LABEL_VALUES_200,
169-
points: [{value: 1, timestamp: {nanos: 1e7, seconds: 1000}}]
201+
points: [{
202+
value: 1,
203+
timestamp:
204+
{nanos: mockedTime.nanos, seconds: mockedTime.seconds}
205+
}]
170206
}]);
171207

172208
// create timeseries with same labels.

0 commit comments

Comments
 (0)