Skip to content

Commit 2e4136c

Browse files
dyladanmayurkale22
authored andcommitted
fix: prometheus counters can only count up (#561)
* fix: prometheus counters can only count up #553 * chore: review comments * chore: uncomment fixed example * fix: monotonic missing from proto * fix: casing
1 parent 566452e commit 2e4136c

File tree

7 files changed

+67
-27
lines changed

7 files changed

+67
-27
lines changed

examples/prometheus/index.js

+8-10
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,12 @@ const monotonicGauge = meter.createGauge("monotonic_gauge", {
2929
});
3030

3131
// Non-monotonic counters and gauges can be increased or decreased.
32-
33-
// This is commented out until non-monotonic counter export is fixed
34-
// const nonmonotonicCounter = meter.createCounter("non_monotonic_counter", {
35-
// monotonic: false,
36-
// labelKeys: ["pid"],
37-
// description: "Example of a non-monotonic counter"
38-
// });
39-
const nonmonotonicGauge = meter.createGauge("non_monotonic_gauge", {
32+
const nonMonotonicCounter = meter.createCounter("non_monotonic_counter", {
33+
monotonic: false,
34+
labelKeys: ["pid"],
35+
description: "Example of a non-monotonic counter"
36+
});
37+
const nonMonotonicGauge = meter.createGauge("non_monotonic_gauge", {
4038
monotonic: false,
4139
labelKeys: ["pid"],
4240
description: "Example of a non-monotonic gauge"
@@ -49,9 +47,9 @@ setInterval(() => {
4947
currentMonotonicGaugeValue += Math.random();
5048

5149
monotonicCounter.getHandle(labels).add(1);
52-
// nonmonotonicCounter.getHandle(labels).add(Math.random() > 0.5 ? 1 : -1);
50+
nonMonotonicCounter.getHandle(labels).add(Math.random() > 0.5 ? 1 : -1);
5351
monotonicGauge.getHandle(labels).set(currentMonotonicGaugeValue);
54-
nonmonotonicGauge
52+
nonMonotonicGauge
5553
.getHandle(labels)
5654
.set(Math.random() > 0.5 ? Math.random() * 10 : -Math.random() * 10);
5755
}, 1000);

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,10 @@ export class PrometheusExporter implements MetricExporter {
196196
switch (readableMetric.descriptor.type) {
197197
case MetricDescriptorType.COUNTER_DOUBLE:
198198
case MetricDescriptorType.COUNTER_INT64:
199-
return new Counter(metricObject);
199+
// there is no such thing as a non-monotonic counter in prometheus
200+
return readableMetric.descriptor.monotonic
201+
? new Counter(metricObject)
202+
: new Gauge(metricObject);
200203
case MetricDescriptorType.GAUGE_DOUBLE:
201204
case MetricDescriptorType.GAUGE_INT64:
202205
return new Gauge(metricObject);

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

+42-15
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ describe('PrometheusExporter', () => {
3737
() => {
3838
const url = `http://localhost:${port}${endpoint}`;
3939
http.get(url, function(res: any) {
40-
assert.equal(res.statusCode, 200);
40+
assert.strictEqual(res.statusCode, 200);
4141
exporter.shutdown(() => {
4242
return done();
4343
});
@@ -72,7 +72,7 @@ describe('PrometheusExporter', () => {
7272
exporter.startServer(() => {
7373
const url = `http://localhost:${port}${endpoint}`;
7474
http.get(url, function(res: any) {
75-
assert.equal(res.statusCode, 200);
75+
assert.strictEqual(res.statusCode, 200);
7676
exporter.shutdown(() => {
7777
return done();
7878
});
@@ -92,7 +92,7 @@ describe('PrometheusExporter', () => {
9292
exporter.startServer(() => {
9393
const url = `http://localhost:${port}${endpoint}`;
9494
http.get(url, function(res: any) {
95-
assert.equal(res.statusCode, 200);
95+
assert.strictEqual(res.statusCode, 200);
9696
exporter.shutdown(() => {
9797
return done();
9898
});
@@ -112,7 +112,7 @@ describe('PrometheusExporter', () => {
112112
exporter.startServer(() => {
113113
const url = `http://localhost:${port}/metric`;
114114
http.get(url, function(res: any) {
115-
assert.equal(res.statusCode, 200);
115+
assert.strictEqual(res.statusCode, 200);
116116
exporter.shutdown(() => {
117117
const exporter2 = new PrometheusExporter({
118118
port,
@@ -122,7 +122,7 @@ describe('PrometheusExporter', () => {
122122
exporter2.startServer(() => {
123123
const url = `http://localhost:${port}/metric`;
124124
http.get(url, function(res: any) {
125-
assert.equal(res.statusCode, 200);
125+
assert.strictEqual(res.statusCode, 200);
126126
exporter2.stopServer(() => {
127127
return done();
128128
});
@@ -144,7 +144,7 @@ describe('PrometheusExporter', () => {
144144
const url = `http://localhost:${port}/invalid`;
145145

146146
http.get(url, function(res: any) {
147-
assert.equal(res.statusCode, 404);
147+
assert.strictEqual(res.statusCode, 404);
148148
exporter.shutdown(() => {
149149
return done();
150150
});
@@ -199,7 +199,7 @@ describe('PrometheusExporter', () => {
199199
'# HELP counter a test description'
200200
);
201201

202-
assert.deepEqual(lines, [
202+
assert.deepStrictEqual(lines, [
203203
'# HELP counter a test description',
204204
'# TYPE counter counter',
205205
'counter{key1="labelValue1"} 20',
@@ -229,7 +229,7 @@ describe('PrometheusExporter', () => {
229229
const body = chunk.toString();
230230
const lines = body.split('\n');
231231

232-
assert.deepEqual(lines, [
232+
assert.deepStrictEqual(lines, [
233233
'# HELP gauge a test description',
234234
'# TYPE gauge gauge',
235235
'gauge{key1="labelValue1"} 10',
@@ -263,7 +263,7 @@ describe('PrometheusExporter', () => {
263263
const body = chunk.toString();
264264
const lines = body.split('\n');
265265

266-
assert.deepEqual(lines, [
266+
assert.deepStrictEqual(lines, [
267267
'# HELP gauge a test description',
268268
'# TYPE gauge gauge',
269269
'gauge{gaugeKey1="labelValue1"} 10',
@@ -289,7 +289,7 @@ describe('PrometheusExporter', () => {
289289
const body = chunk.toString();
290290
const lines = body.split('\n');
291291

292-
assert.deepEqual(lines, ['# no registered metrics']);
292+
assert.deepStrictEqual(lines, ['# no registered metrics']);
293293

294294
done();
295295
});
@@ -310,7 +310,7 @@ describe('PrometheusExporter', () => {
310310
const body = chunk.toString();
311311
const lines = body.split('\n');
312312

313-
assert.deepEqual(lines, [
313+
assert.deepStrictEqual(lines, [
314314
'# HELP gauge description missing',
315315
'# TYPE gauge gauge',
316316
'gauge 10',
@@ -335,7 +335,7 @@ describe('PrometheusExporter', () => {
335335
const body = chunk.toString();
336336
const lines = body.split('\n');
337337

338-
assert.deepEqual(lines, [
338+
assert.deepStrictEqual(lines, [
339339
'# HELP gauge_bad_name description missing',
340340
'# TYPE gauge_bad_name gauge',
341341
'gauge_bad_name 10',
@@ -348,6 +348,33 @@ describe('PrometheusExporter', () => {
348348
.on('error', errorHandler(done));
349349
});
350350
});
351+
352+
it('should export a non-monotonic counter as a gauge', done => {
353+
const counter = meter.createCounter('counter', {
354+
description: 'a test description',
355+
monotonic: false,
356+
labelKeys: ['key1'],
357+
}) as CounterMetric;
358+
359+
const handle = counter.getHandle(meter.labels({ key1: 'labelValue1' }));
360+
handle.add(20);
361+
exporter.export(meter.getMetrics(), () => {
362+
http
363+
.get('http://localhost:9464/metrics', res => {
364+
res.on('data', chunk => {
365+
assert.deepStrictEqual(chunk.toString().split('\n'), [
366+
'# HELP counter a test description',
367+
'# TYPE counter gauge',
368+
'counter{key1="labelValue1"} 20',
369+
'',
370+
]);
371+
372+
done();
373+
});
374+
})
375+
.on('error', errorHandler(done));
376+
});
377+
});
351378
});
352379

353380
describe('configuration', () => {
@@ -383,7 +410,7 @@ describe('PrometheusExporter', () => {
383410
const body = chunk.toString();
384411
const lines = body.split('\n');
385412

386-
assert.deepEqual(lines, [
413+
assert.deepStrictEqual(lines, [
387414
'# HELP test_prefix_gauge description missing',
388415
'# TYPE test_prefix_gauge gauge',
389416
'test_prefix_gauge 10',
@@ -411,7 +438,7 @@ describe('PrometheusExporter', () => {
411438
const body = chunk.toString();
412439
const lines = body.split('\n');
413440

414-
assert.deepEqual(lines, [
441+
assert.deepStrictEqual(lines, [
415442
'# HELP gauge description missing',
416443
'# TYPE gauge gauge',
417444
'gauge 10',
@@ -439,7 +466,7 @@ describe('PrometheusExporter', () => {
439466
const body = chunk.toString();
440467
const lines = body.split('\n');
441468

442-
assert.deepEqual(lines, [
469+
assert.deepStrictEqual(lines, [
443470
'# HELP gauge description missing',
444471
'# TYPE gauge gauge',
445472
'gauge 10',

packages/opentelemetry-metrics/src/Metric.ts

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export abstract class Metric<T extends BaseHandle> implements types.Metric<T> {
114114
unit: this._options.unit,
115115
labelKeys: this._options.labelKeys,
116116
type: this._type,
117+
monotonic: this._monotonic,
117118
};
118119
}
119120

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

+7
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ export interface MetricDescriptor {
6161
readonly unit: string;
6262
/** MetricDescriptor type */
6363
readonly type: MetricDescriptorType;
64+
/**
65+
* Metric may only increase
66+
*
67+
* This property is not in the .proto file, but is included here because
68+
* it is required for correct export of prometheus metrics
69+
*/
70+
readonly monotonic: boolean;
6471
/** The label keys associated with the metric descriptor. */
6572
readonly labelKeys: string[];
6673
}

packages/opentelemetry-metrics/src/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export interface MetricOptions {
3737
/** Indicates the metric is a verbose metric that is disabled by default */
3838
disabled: boolean;
3939

40-
/** Monotonic allows this metric to accept negative values. */
40+
/** Monotonic metrics may only increase. */
4141
monotonic: boolean;
4242

4343
/** User provided logger. */

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

+4
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ describe('Meter', () => {
398398
const [{ descriptor, timeseries }] = meter.getMetrics();
399399
assert.deepStrictEqual(descriptor, {
400400
name: 'counter',
401+
monotonic: true,
401402
description: 'test',
402403
unit: '1',
403404
type: MetricDescriptorType.COUNTER_DOUBLE,
@@ -430,6 +431,7 @@ describe('Meter', () => {
430431
assert.deepStrictEqual(descriptor, {
431432
name: 'counter',
432433
description: 'test',
434+
monotonic: true,
433435
unit: '1',
434436
type: MetricDescriptorType.COUNTER_INT64,
435437
labelKeys: ['key'],
@@ -460,6 +462,7 @@ describe('Meter', () => {
460462
const [{ descriptor, timeseries }] = meter.getMetrics();
461463
assert.deepStrictEqual(descriptor, {
462464
name: 'gauge',
465+
monotonic: false,
463466
description: '',
464467
unit: 'ms',
465468
type: MetricDescriptorType.GAUGE_DOUBLE,
@@ -502,6 +505,7 @@ describe('Meter', () => {
502505
const [{ descriptor, timeseries }] = meter.getMetrics();
503506
assert.deepStrictEqual(descriptor, {
504507
name: 'gauge',
508+
monotonic: false,
505509
description: '',
506510
unit: 'ms',
507511
type: MetricDescriptorType.GAUGE_INT64,

0 commit comments

Comments
 (0)