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

Commit

Permalink
Get Timestamp with process.hrtime and Date.now (#261)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
mayurkale22 committed Jan 10, 2019
1 parent ebbc3e3 commit 47a672e
Show file tree
Hide file tree
Showing 10 changed files with 471 additions and 175 deletions.
67 changes: 67 additions & 0 deletions packages/opencensus-core/src/common/time-util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Copyright 2019, OpenCensus 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 {Timestamp} from '../metrics/export/types';

const MILLIS_PER_SECOND = 1e3;
const NANOS_PER_MILLI = 1e3 * 1e3;
const NANOS_PER_SECOND = 1e3 * 1e3 * 1e3;

let hrtime = process.hrtime;
let hrtimeOrigin: [number, number] = [0, 0];
let hrtimeRefSeconds = 0;
let hrtimeRefNanos = 0;

function setHrtimeReference() {
resetHrtimeFunctionCache();
hrtimeOrigin = hrtime();
const refTime = Date.now();
hrtimeRefSeconds = Math.floor(refTime / MILLIS_PER_SECOND);
hrtimeRefNanos = (refTime % MILLIS_PER_SECOND) * NANOS_PER_MILLI;
}

/**
* This is used to enable tests to mock process.hrtime while still allow us to
* cache it.
*/
function resetHrtimeFunctionCache() {
hrtime = process.hrtime;
}

/**
* Gets the current timestamp with seconds and nanoseconds.
*
* @returns {Timestamp} The Timestamp.
*/
export function getTimestampWithProcessHRTime(): Timestamp {
const [offsetSecs, offsetNanos] = hrtime(hrtimeOrigin);

// determine drift in seconds and nanoseconds
const seconds = hrtimeRefSeconds + offsetSecs;
const nanos = hrtimeRefNanos + offsetNanos;

if (nanos >= NANOS_PER_SECOND) {
return {seconds: seconds + 1, nanos: nanos % NANOS_PER_SECOND};
}
return {seconds, nanos};
}

setHrtimeReference();

export const TEST_ONLY = {
setHrtimeReference,
resetHrtimeFunctionCache
};
9 changes: 4 additions & 5 deletions packages/opencensus-core/src/metrics/gauges/derived-gauge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
* limitations under the License.
*/

import {getTimestampWithProcessHRTime} from '../../common/time-util';
import {validateArrayElementsNotNull, validateNotNull} from '../../common/validations';
import {LabelKey, LabelValue, Metric, MetricDescriptor, MetricDescriptorType, TimeSeries} from '../export/types';
import {LabelKey, LabelValue, Metric, MetricDescriptor, MetricDescriptorType, TimeSeries, Timestamp} from '../export/types';
import * as types from '../gauges/types';
import {hashLabelValues} from '../utils';

Expand Down Expand Up @@ -204,16 +205,14 @@ export class DerivedGauge implements types.Meter {
if (this.registeredPoints.size === 0) {
return null;
}
const [seconds, nanos] = process.hrtime();
const timestamp: Timestamp = getTimestampWithProcessHRTime();
return {
descriptor: this.metricDescriptor,
timeseries: Array.from(
this.registeredPoints,
([_, gaugeEntry]) => ({
labelValues: gaugeEntry.labelValues,
points: [
{value: gaugeEntry.extractor(), timestamp: {seconds, nanos}}
]
points: [{value: gaugeEntry.extractor(), timestamp}]
} as TimeSeries))
};
}
Expand Down
6 changes: 3 additions & 3 deletions packages/opencensus-core/src/metrics/gauges/gauge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

import {getTimestampWithProcessHRTime} from '../../common/time-util';
import {validateArrayElementsNotNull, validateNotNull} from '../../common/validations';
import {LabelKey, LabelValue, Metric, MetricDescriptor, MetricDescriptorType, TimeSeries, Timestamp} from '../export/types';
import * as types from '../gauges/types';
Expand Down Expand Up @@ -129,12 +130,11 @@ export class Gauge implements types.Meter {
if (this.registeredPoints.size === 0) {
return null;
}
const [seconds, nanos] = process.hrtime();
const timestamp: Timestamp = getTimestampWithProcessHRTime();
return {
descriptor: this.metricDescriptor,
timeseries: Array.from(
this.registeredPoints,
([_, point]) => point.getTimeSeries({seconds, nanos}))
this.registeredPoints, ([_, point]) => point.getTimeSeries(timestamp))
};
}
}
Expand Down
7 changes: 3 additions & 4 deletions packages/opencensus-core/src/stats/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

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

Expand Down Expand Up @@ -206,18 +207,16 @@ export class BaseView implements View {
let startTimestamp: Timestamp;

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

switch (type) {
case MetricDescriptorType.GAUGE_INT64:
case MetricDescriptorType.GAUGE_DOUBLE:
startTimestamp = null;
break;
default:
const [seconds, nanos] = process.hrtime();
// TODO (mayurkale): This should be set when create Cumulative view.
startTimestamp = {seconds, nanos};
startTimestamp = getTimestampWithProcessHRTime();
}

const timeseries: TimeSeries[] = [];
Expand Down
56 changes: 46 additions & 10 deletions packages/opencensus-core/test/test-derived-gauge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
*/

import * as assert from 'assert';
import {LabelKey, LabelValue, MetricDescriptorType} from '../src/metrics/export/types';

import {TEST_ONLY} from '../src/common/time-util';
import {LabelKey, LabelValue, MetricDescriptorType, Timestamp} from '../src/metrics/export/types';
import {DerivedGauge} from '../src/metrics/gauges/derived-gauge';

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

describe('DerivedGauge', () => {
const oldProcessHrtime = process.hrtime;
let instance: DerivedGauge;
const realHrtimeFn = process.hrtime;
const realNowFn = Date.now;
const mockedTime: Timestamp = {seconds: 1450000100, nanos: 1e7};
const expectedMetricDescriptor = {
name: METRIC_NAME,
description: METRIC_DESCRIPTION,
Expand All @@ -42,11 +46,18 @@ describe('DerivedGauge', () => {
beforeEach(() => {
instance = new DerivedGauge(
METRIC_NAME, METRIC_DESCRIPTION, UNIT, GAUGE_INT64, LABEL_KEYS);
process.hrtime = () => [1000, 1e7];

process.hrtime = () => [100, 1e7];
Date.now = () => 1450000000000;
// Force the clock to recalibrate the time offset with the mocked time
TEST_ONLY.setHrtimeReference();
});

afterEach(() => {
process.hrtime = oldProcessHrtime;
process.hrtime = realHrtimeFn;
Date.now = realNowFn;
// Reset the hrtime reference so that it uses a real clock again.
TEST_ONLY.resetHrtimeFunctionCache();
});

describe('createTimeSeries()', () => {
Expand All @@ -73,14 +84,19 @@ describe('DerivedGauge', () => {
map.set('key', 'value');
instance.createTimeSeries(LABEL_VALUES_200, map);
map.set('key1', 'value1');

let metric = instance.getMetric();
assert.notEqual(metric, null);
assert.deepStrictEqual(metric.descriptor, expectedMetricDescriptor);
assert.equal(metric.timeseries.length, 1);
assert.deepStrictEqual(
metric.timeseries, [{
labelValues: LABEL_VALUES_200,
points: [{value: 2, timestamp: {nanos: 1e7, seconds: 1000}}]
points: [{
value: 2,
timestamp:
{nanos: mockedTime.nanos, seconds: mockedTime.seconds}
}]
}]);
// add data in collection
map.set('key2', 'value2');
Expand All @@ -98,11 +114,19 @@ describe('DerivedGauge', () => {
assert.deepStrictEqual(metric.timeseries, [
{
labelValues: LABEL_VALUES_200,
points: [{value: 4, timestamp: {nanos: 1e7, seconds: 1000}}]
points: [{
value: 4,
timestamp:
{nanos: mockedTime.nanos, seconds: mockedTime.seconds}
}]
},
{
labelValues: LABEL_VALUES_400,
points: [{value: 5, timestamp: {nanos: 1e7, seconds: 1000}}]
points: [{
value: 5,
timestamp:
{nanos: mockedTime.nanos, seconds: mockedTime.seconds}
}]
}
]);
});
Expand All @@ -121,7 +145,11 @@ describe('DerivedGauge', () => {
assert.deepStrictEqual(
metric.timeseries, [{
labelValues: LABEL_VALUES_200,
points: [{value: 45, timestamp: {nanos: 1e7, seconds: 1000}}]
points: [{
value: 45,
timestamp:
{nanos: mockedTime.nanos, seconds: mockedTime.seconds}
}]
}]);
});
it('should return a Metric (Double) - custom object', () => {
Expand All @@ -147,7 +175,11 @@ describe('DerivedGauge', () => {
assert.deepStrictEqual(
metric.timeseries, [{
labelValues: LABEL_VALUES_200,
points: [{value: 0.7, timestamp: {nanos: 1e7, seconds: 1000}}]
points: [{
value: 0.7,
timestamp:
{nanos: mockedTime.nanos, seconds: mockedTime.seconds}
}]
}]);
});
it('should throw an error when obj is null', () => {
Expand All @@ -166,7 +198,11 @@ describe('DerivedGauge', () => {
assert.deepStrictEqual(
metric.timeseries, [{
labelValues: LABEL_VALUES_200,
points: [{value: 1, timestamp: {nanos: 1e7, seconds: 1000}}]
points: [{
value: 1,
timestamp:
{nanos: mockedTime.nanos, seconds: mockedTime.seconds}
}]
}]);

// create timeseries with same labels.
Expand Down
Loading

0 comments on commit 47a672e

Please sign in to comment.