Skip to content

Commit 87236cc

Browse files
mayurkale22dyladan
andauthored
Metrics: Add lastUpdateTimestamp associated with point (#893)
* Metrics: Add lastUpdateTimestamp associated with point * update lastUpdateTime in MeasureExactAggregator * add tests Co-authored-by: Daniel Dyla <[email protected]>
1 parent 47212de commit 87236cc

File tree

5 files changed

+119
-57
lines changed

5 files changed

+119
-57
lines changed

packages/opentelemetry-exporter-prometheus/src/prometheus.ts

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

1717
import { ExportResult } from '@opentelemetry/base';
18-
import { NoopLogger } from '@opentelemetry/core';
18+
import { NoopLogger, hrTimeToMilliseconds } from '@opentelemetry/core';
1919
import {
2020
CounterSumAggregator,
2121
LastValue,
@@ -124,27 +124,30 @@ export class PrometheusExporter implements MetricExporter {
124124
if (!metric) return;
125125

126126
const labelKeys = record.descriptor.labelKeys;
127-
const value = record.aggregator.value();
127+
const point = record.aggregator.toPoint();
128128

129129
if (metric instanceof Counter) {
130130
// Prometheus counter saves internal state and increments by given value.
131131
// MetricRecord value is the current state, not the delta to be incremented by.
132132
// Currently, _registerMetric creates a new counter every time the value changes,
133133
// so the increment here behaves as a set value (increment from 0)
134-
metric.inc(this._getLabelValues(labelKeys, record.labels), value as Sum);
134+
metric.inc(
135+
this._getLabelValues(labelKeys, record.labels),
136+
point.value as Sum
137+
);
135138
}
136139

137140
if (metric instanceof Gauge) {
138141
if (record.aggregator instanceof CounterSumAggregator) {
139142
metric.set(
140143
this._getLabelValues(labelKeys, record.labels),
141-
value as Sum
144+
point.value as Sum
142145
);
143146
} else if (record.aggregator instanceof ObserverAggregator) {
144147
metric.set(
145148
this._getLabelValues(labelKeys, record.labels),
146-
value as LastValue,
147-
new Date()
149+
point.value as LastValue,
150+
hrTimeToMilliseconds(point.timestamp)
148151
);
149152
}
150153
}

packages/opentelemetry-metrics/src/export/Aggregator.ts

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

17-
import { Aggregator, Distribution, LastValue, Sum } from './types';
17+
import { Aggregator, Distribution, Point } from './types';
18+
import { HrTime } from '@opentelemetry/api';
19+
import { hrTime } from '@opentelemetry/core';
1820

1921
/** Basic aggregator which calculates a Sum from individual measurements. */
2022
export class CounterSumAggregator implements Aggregator {
2123
private _current: number = 0;
24+
private _lastUpdateTime: HrTime = [0, 0];
2225

2326
update(value: number): void {
2427
this._current += value;
28+
this._lastUpdateTime = hrTime();
2529
}
2630

27-
value(): Sum {
28-
return this._current;
31+
toPoint(): Point {
32+
return {
33+
value: this._current,
34+
timestamp: this._lastUpdateTime,
35+
};
2936
}
3037
}
3138

3239
/** Basic aggregator for Observer which keeps the last recorded value. */
3340
export class ObserverAggregator implements Aggregator {
3441
private _current: number = 0;
42+
private _lastUpdateTime: HrTime = [0, 0];
3543

3644
update(value: number): void {
3745
this._current = value;
46+
this._lastUpdateTime = hrTime();
3847
}
3948

40-
value(): LastValue {
41-
return this._current;
49+
toPoint(): Point {
50+
return {
51+
value: this._current,
52+
timestamp: this._lastUpdateTime,
53+
};
4254
}
4355
}
4456

4557
/** Basic aggregator keeping all raw values (events, sum, max and min). */
4658
export class MeasureExactAggregator implements Aggregator {
4759
private _distribution: Distribution;
60+
private _lastUpdateTime: HrTime = [0, 0];
4861

4962
constructor() {
5063
this._distribution = {
@@ -60,9 +73,13 @@ export class MeasureExactAggregator implements Aggregator {
6073
this._distribution.sum += value;
6174
this._distribution.min = Math.min(this._distribution.min, value);
6275
this._distribution.max = Math.max(this._distribution.max, value);
76+
this._lastUpdateTime = hrTime();
6377
}
6478

65-
value(): Distribution {
66-
return this._distribution;
79+
toPoint(): Point {
80+
return {
81+
value: this._distribution,
82+
timestamp: this._lastUpdateTime,
83+
};
6784
}
6885
}

packages/opentelemetry-metrics/src/export/ConsoleMetricExporter.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ export class ConsoleMetricExporter implements MetricExporter {
3737
console.log(metric.labels.labels);
3838
switch (metric.descriptor.metricKind) {
3939
case MetricKind.COUNTER:
40-
const sum = metric.aggregator.value() as Sum;
40+
const sum = metric.aggregator.toPoint().value as Sum;
4141
console.log('value: ' + sum);
4242
break;
4343
default:
44-
const distribution = metric.aggregator.value() as Distribution;
44+
const distribution = metric.aggregator.toPoint()
45+
.value as Distribution;
4546
console.log(
4647
'min: ' +
4748
distribution.min +

packages/opentelemetry-metrics/src/export/types.ts

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

17-
import { ValueType } from '@opentelemetry/api';
17+
import { ValueType, HrTime } from '@opentelemetry/api';
1818
import { ExportResult } from '@opentelemetry/base';
1919
import { LabelSet } from '../LabelSet';
2020

@@ -76,6 +76,11 @@ export interface Aggregator {
7676
/** Updates the current with the new value. */
7777
update(value: number): void;
7878

79-
/** Returns snapshot of the current value. */
80-
value(): Sum | Distribution;
79+
/** Returns snapshot of the current point (value with timestamp). */
80+
toPoint(): Point;
81+
}
82+
83+
export interface Point {
84+
value: Sum | LastValue | Distribution;
85+
timestamp: HrTime;
8186
}

packages/opentelemetry-metrics/test/Meter.test.ts

Lines changed: 75 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
} from '../src';
3030
import * as types from '@opentelemetry/api';
3131
import { LabelSet } from '../src/LabelSet';
32-
import { NoopLogger } from '@opentelemetry/core';
32+
import { NoopLogger, hrTime, hrTimeToNanoseconds } from '@opentelemetry/core';
3333
import {
3434
CounterSumAggregator,
3535
ObserverAggregator,
@@ -63,6 +63,8 @@ describe('Meter', () => {
6363
});
6464

6565
describe('#counter', () => {
66+
const performanceTimeOrigin = hrTime();
67+
6668
it('should create a counter', () => {
6769
const counter = meter.createCounter('name');
6870
assert.ok(counter instanceof Metric);
@@ -84,9 +86,19 @@ describe('Meter', () => {
8486
meter.collect();
8587
const [record1] = meter.getBatcher().checkPointSet();
8688

87-
assert.strictEqual(record1.aggregator.value(), 10);
89+
assert.strictEqual(record1.aggregator.toPoint().value, 10);
90+
const lastTimestamp = record1.aggregator.toPoint().timestamp;
91+
assert.ok(
92+
hrTimeToNanoseconds(lastTimestamp) >
93+
hrTimeToNanoseconds(performanceTimeOrigin)
94+
);
8895
counter.add(10, labelSet);
89-
assert.strictEqual(record1.aggregator.value(), 20);
96+
assert.strictEqual(record1.aggregator.toPoint().value, 20);
97+
98+
assert.ok(
99+
hrTimeToNanoseconds(record1.aggregator.toPoint().timestamp) >
100+
hrTimeToNanoseconds(lastTimestamp)
101+
);
90102
});
91103

92104
it('should return counter with resource', () => {
@@ -102,9 +114,9 @@ describe('Meter', () => {
102114
meter.collect();
103115
const [record1] = meter.getBatcher().checkPointSet();
104116

105-
assert.strictEqual(record1.aggregator.value(), 10);
117+
assert.strictEqual(record1.aggregator.toPoint().value, 10);
106118
boundCounter.add(10);
107-
assert.strictEqual(record1.aggregator.value(), 20);
119+
assert.strictEqual(record1.aggregator.toPoint().value, 20);
108120
});
109121

110122
it('should return the aggregator', () => {
@@ -123,9 +135,9 @@ describe('Meter', () => {
123135
meter.collect();
124136
const [record1] = meter.getBatcher().checkPointSet();
125137

126-
assert.strictEqual(record1.aggregator.value(), 10);
138+
assert.strictEqual(record1.aggregator.toPoint().value, 10);
127139
boundCounter.add(-100);
128-
assert.strictEqual(record1.aggregator.value(), 10);
140+
assert.strictEqual(record1.aggregator.toPoint().value, 10);
129141
});
130142

131143
it('should not add the instrument data when disabled', () => {
@@ -136,7 +148,7 @@ describe('Meter', () => {
136148
boundCounter.add(10);
137149
meter.collect();
138150
const [record1] = meter.getBatcher().checkPointSet();
139-
assert.strictEqual(record1.aggregator.value(), 0);
151+
assert.strictEqual(record1.aggregator.toPoint().value, 0);
140152
});
141153

142154
it('should add negative value when monotonic is set to false', () => {
@@ -147,7 +159,7 @@ describe('Meter', () => {
147159
boundCounter.add(-10);
148160
meter.collect();
149161
const [record1] = meter.getBatcher().checkPointSet();
150-
assert.strictEqual(record1.aggregator.value(), -10);
162+
assert.strictEqual(record1.aggregator.toPoint().value, -10);
151163
});
152164

153165
it('should return same instrument on same label values', () => {
@@ -159,7 +171,7 @@ describe('Meter', () => {
159171
meter.collect();
160172
const [record1] = meter.getBatcher().checkPointSet();
161173

162-
assert.strictEqual(record1.aggregator.value(), 20);
174+
assert.strictEqual(record1.aggregator.toPoint().value, 20);
163175
assert.strictEqual(boundCounter, boundCounter1);
164176
});
165177
});
@@ -214,7 +226,7 @@ describe('Meter', () => {
214226
unit: '1',
215227
valueType: ValueType.DOUBLE,
216228
});
217-
assert.strictEqual(record[0].aggregator.value(), 10);
229+
assert.strictEqual(record[0].aggregator.toPoint().value, 10);
218230
});
219231
});
220232

@@ -306,6 +318,8 @@ describe('Meter', () => {
306318
});
307319

308320
describe('.bind()', () => {
321+
const performanceTimeOrigin = hrTime();
322+
309323
it('should create a measure instrument', () => {
310324
const measure = meter.createMeasure('name') as MeasureMetric;
311325
const boundMeasure = measure.bind(labelSet);
@@ -319,12 +333,15 @@ describe('Meter', () => {
319333

320334
meter.collect();
321335
const [record1] = meter.getBatcher().checkPointSet();
322-
assert.deepStrictEqual(record1.aggregator.value() as Distribution, {
323-
count: 0,
324-
max: -Infinity,
325-
min: Infinity,
326-
sum: 0,
327-
});
336+
assert.deepStrictEqual(
337+
record1.aggregator.toPoint().value as Distribution,
338+
{
339+
count: 0,
340+
max: -Infinity,
341+
min: Infinity,
342+
sum: 0,
343+
}
344+
);
328345
});
329346

330347
it('should not set the instrument data when disabled', () => {
@@ -336,12 +353,15 @@ describe('Meter', () => {
336353

337354
meter.collect();
338355
const [record1] = meter.getBatcher().checkPointSet();
339-
assert.deepStrictEqual(record1.aggregator.value() as Distribution, {
340-
count: 0,
341-
max: -Infinity,
342-
min: Infinity,
343-
sum: 0,
344-
});
356+
assert.deepStrictEqual(
357+
record1.aggregator.toPoint().value as Distribution,
358+
{
359+
count: 0,
360+
max: -Infinity,
361+
min: Infinity,
362+
sum: 0,
363+
}
364+
);
345365
});
346366

347367
it('should accept negative (and positive) values when absolute is set to false', () => {
@@ -354,12 +374,19 @@ describe('Meter', () => {
354374

355375
meter.collect();
356376
const [record1] = meter.getBatcher().checkPointSet();
357-
assert.deepStrictEqual(record1.aggregator.value() as Distribution, {
358-
count: 2,
359-
max: 50,
360-
min: -10,
361-
sum: 40,
362-
});
377+
assert.deepStrictEqual(
378+
record1.aggregator.toPoint().value as Distribution,
379+
{
380+
count: 2,
381+
max: 50,
382+
min: -10,
383+
sum: 40,
384+
}
385+
);
386+
assert.ok(
387+
hrTimeToNanoseconds(record1.aggregator.toPoint().timestamp) >
388+
hrTimeToNanoseconds(performanceTimeOrigin)
389+
);
363390
});
364391

365392
it('should return same instrument on same label values', () => {
@@ -370,12 +397,15 @@ describe('Meter', () => {
370397
boundMeasure2.record(100);
371398
meter.collect();
372399
const [record1] = meter.getBatcher().checkPointSet();
373-
assert.deepStrictEqual(record1.aggregator.value() as Distribution, {
374-
count: 2,
375-
max: 100,
376-
min: 10,
377-
sum: 110,
378-
});
400+
assert.deepStrictEqual(
401+
record1.aggregator.toPoint().value as Distribution,
402+
{
403+
count: 2,
404+
max: 100,
405+
min: 10,
406+
sum: 110,
407+
}
408+
);
379409
assert.strictEqual(boundMeasure1, boundMeasure2);
380410
});
381411
});
@@ -500,7 +530,7 @@ describe('Meter', () => {
500530
labelKeys: ['key'],
501531
});
502532
assert.strictEqual(record[0].labels, labelSet);
503-
const value = record[0].aggregator.value() as Sum;
533+
const value = record[0].aggregator.toPoint().value as Sum;
504534
assert.strictEqual(value, 10.45);
505535
});
506536

@@ -529,16 +559,22 @@ describe('Meter', () => {
529559
labelKeys: ['key'],
530560
});
531561
assert.strictEqual(record[0].labels, labelSet);
532-
const value = record[0].aggregator.value() as Sum;
562+
const value = record[0].aggregator.toPoint().value as Sum;
533563
assert.strictEqual(value, 10);
534564
});
535565
});
536566
});
537567

538568
function ensureMetric(metric: MetricRecord) {
539569
assert.ok(metric.aggregator instanceof ObserverAggregator);
540-
assert.ok(metric.aggregator.value() >= 0 && metric.aggregator.value() <= 1);
541-
assert.ok(metric.aggregator.value() >= 0 && metric.aggregator.value() <= 1);
570+
assert.ok(
571+
metric.aggregator.toPoint().value >= 0 &&
572+
metric.aggregator.toPoint().value <= 1
573+
);
574+
assert.ok(
575+
metric.aggregator.toPoint().value >= 0 &&
576+
metric.aggregator.toPoint().value <= 1
577+
);
542578
const descriptor = metric.descriptor;
543579
assert.strictEqual(descriptor.name, 'name');
544580
assert.strictEqual(descriptor.description, 'desc');

0 commit comments

Comments
 (0)