Skip to content

Commit 469b759

Browse files
authored
feat: add the UpDownCounter instrument (open-telemetry#1120)
1 parent 6ab7984 commit 469b759

File tree

15 files changed

+374
-65
lines changed

15 files changed

+374
-65
lines changed

packages/opentelemetry-api/src/metrics/Meter.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { MetricOptions, Counter, ValueRecorder, Observer } from './Metric';
17+
import {
18+
MetricOptions,
19+
Counter,
20+
ValueRecorder,
21+
Observer,
22+
UpDownCounter,
23+
} from './Metric';
1824

1925
/**
2026
* An interface to allow the recording metrics.
@@ -40,6 +46,25 @@ export interface Meter {
4046
*/
4147
createCounter(name: string, options?: MetricOptions): Counter;
4248

49+
/**
50+
* Creates a new `UpDownCounter` metric. UpDownCounter is a synchronous
51+
* instrument and very similar to Counter except that Add(increment)
52+
* supports negative increments. It is generally useful for capturing changes
53+
* in an amount of resources used, or any quantity that rises and falls
54+
* during a request.
55+
* Example uses for UpDownCounter:
56+
* <ol>
57+
* <li> count the number of active requests. </li>
58+
* <li> count memory in use by instrumenting new and delete. </li>
59+
* <li> count queue size by instrumenting enqueue and dequeue. </li>
60+
* <li> count semaphore up and down operations. </li>
61+
* </ol>
62+
*
63+
* @param name the name of the metric.
64+
* @param [options] the metric options.
65+
*/
66+
createUpDownCounter(name: string, options?: MetricOptions): UpDownCounter;
67+
4368
/**
4469
* Creates a new `Observer` metric.
4570
* @param name the name of the metric.

packages/opentelemetry-api/src/metrics/Metric.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,7 @@ export interface MetricOptions {
4848
disabled?: boolean;
4949

5050
/**
51-
* Asserts that this metric may only increase (e.g. time spent).
52-
*/
53-
monotonic?: boolean;
54-
55-
/**
56-
* (ValueRecorder only, default true) Asserts that this metric will only accept
51+
* (Measure only, default true) Asserts that this metric will only accept
5752
* non-negative values (e.g. disk usage).
5853
*/
5954
absolute?: boolean;
@@ -125,6 +120,13 @@ export interface Counter extends UnboundMetric<BoundCounter> {
125120
add(value: number, labels?: Labels): void;
126121
}
127122

123+
export interface UpDownCounter extends UnboundMetric<BoundCounter> {
124+
/**
125+
* Adds the given value to the current value. Values can be negative.
126+
*/
127+
add(value: number, labels?: Labels): void;
128+
}
129+
128130
export interface ValueRecorder extends UnboundMetric<BoundValueRecorder> {
129131
/**
130132
* Records the given value to this value recorder.

packages/opentelemetry-api/src/metrics/NoopMeter.ts

+10
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
Counter,
2323
ValueRecorder,
2424
Observer,
25+
UpDownCounter,
2526
} from './Metric';
2627
import { BoundValueRecorder, BoundCounter } from './BoundInstrument';
2728
import { CorrelationContext } from '../correlation_context/CorrelationContext';
@@ -53,6 +54,15 @@ export class NoopMeter implements Meter {
5354
return NOOP_COUNTER_METRIC;
5455
}
5556

57+
/**
58+
* Returns a constant noop UpDownCounter.
59+
* @param name the name of the metric.
60+
* @param [options] the metric options.
61+
*/
62+
createUpDownCounter(name: string, options?: MetricOptions): UpDownCounter {
63+
return NOOP_COUNTER_METRIC;
64+
}
65+
5666
/**
5767
* Returns constant noop observer.
5868
* @param name the name of the metric.

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

+3-4
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,9 @@ export class PrometheusExporter implements MetricExporter {
191191

192192
switch (record.descriptor.metricKind) {
193193
case MetricKind.COUNTER:
194-
// there is no such thing as a non-monotonic counter in prometheus
195-
return record.descriptor.monotonic
196-
? new Counter(metricObject)
197-
: new Gauge(metricObject);
194+
return new Counter(metricObject);
195+
case MetricKind.UP_DOWN_COUNTER:
196+
return new Gauge(metricObject);
198197
case MetricKind.OBSERVER:
199198
return new Gauge(metricObject);
200199
default:

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -384,10 +384,9 @@ describe('PrometheusExporter', () => {
384384
});
385385
});
386386

387-
it('should export a non-monotonic counter as a gauge', done => {
388-
const counter = meter.createCounter('counter', {
387+
it('should export a UpDownCounter as a gauge', done => {
388+
const counter = meter.createUpDownCounter('counter', {
389389
description: 'a test description',
390-
monotonic: false,
391390
});
392391

393392
counter.bind({ key1: 'labelValue1' }).add(20);

packages/opentelemetry-metrics/src/BoundInstrument.ts

+28-12
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,16 @@ import { Aggregator } from './export/types';
2424
export class BaseBoundInstrument {
2525
protected _labels: api.Labels;
2626
protected _logger: api.Logger;
27-
protected _monotonic: boolean;
2827

2928
constructor(
3029
labels: api.Labels,
3130
logger: api.Logger,
32-
monotonic: boolean,
3331
private readonly _disabled: boolean,
3432
private readonly _valueType: api.ValueType,
3533
private readonly _aggregator: Aggregator
3634
) {
3735
this._labels = labels;
3836
this._logger = logger;
39-
this._monotonic = monotonic;
4037
}
4138

4239
update(value: number): void {
@@ -72,18 +69,17 @@ export class BoundCounter extends BaseBoundInstrument
7269
constructor(
7370
labels: api.Labels,
7471
disabled: boolean,
75-
monotonic: boolean,
7672
valueType: api.ValueType,
7773
logger: api.Logger,
7874
aggregator: Aggregator
7975
) {
80-
super(labels, logger, monotonic, disabled, valueType, aggregator);
76+
super(labels, logger, disabled, valueType, aggregator);
8177
}
8278

8379
add(value: number): void {
84-
if (this._monotonic && value < 0) {
80+
if (value < 0) {
8581
this._logger.error(
86-
`Monotonic counter cannot descend for ${Object.values(this._labels)}`
82+
`Counter cannot descend for ${Object.values(this._labels)}`
8783
);
8884
return;
8985
}
@@ -93,7 +89,29 @@ export class BoundCounter extends BaseBoundInstrument
9389
}
9490

9591
/**
96-
* BoundValueRecorder is an implementation of the {@link BoundValueRecorder} interface.
92+
* BoundUpDownCounter allows the SDK to observe/record a single metric event.
93+
* The value of single instrument in the `UpDownCounter` associated with
94+
* specified Labels.
95+
*/
96+
export class BoundUpDownCounter extends BaseBoundInstrument
97+
implements api.BoundCounter {
98+
constructor(
99+
labels: api.Labels,
100+
disabled: boolean,
101+
valueType: api.ValueType,
102+
logger: api.Logger,
103+
aggregator: Aggregator
104+
) {
105+
super(labels, logger, disabled, valueType, aggregator);
106+
}
107+
108+
add(value: number): void {
109+
this.update(value);
110+
}
111+
}
112+
113+
/**
114+
* BoundMeasure is an implementation of the {@link BoundMeasure} interface.
97115
*/
98116
export class BoundValueRecorder extends BaseBoundInstrument
99117
implements api.BoundValueRecorder {
@@ -102,13 +120,12 @@ export class BoundValueRecorder extends BaseBoundInstrument
102120
constructor(
103121
labels: api.Labels,
104122
disabled: boolean,
105-
monotonic: boolean,
106123
absolute: boolean,
107124
valueType: api.ValueType,
108125
logger: api.Logger,
109126
aggregator: Aggregator
110127
) {
111-
super(labels, logger, monotonic, disabled, valueType, aggregator);
128+
super(labels, logger, disabled, valueType, aggregator);
112129
this._absolute = absolute;
113130
}
114131

@@ -137,11 +154,10 @@ export class BoundObserver extends BaseBoundInstrument {
137154
constructor(
138155
labels: api.Labels,
139156
disabled: boolean,
140-
monotonic: boolean,
141157
valueType: api.ValueType,
142158
logger: api.Logger,
143159
aggregator: Aggregator
144160
) {
145-
super(labels, logger, monotonic, disabled, valueType, aggregator);
161+
super(labels, logger, disabled, valueType, aggregator);
146162
}
147163
}

packages/opentelemetry-metrics/src/Meter.ts

+39-7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import * as api from '@opentelemetry/api';
1818
import { ConsoleLogger } from '@opentelemetry/core';
1919
import { Resource } from '@opentelemetry/resources';
2020
import { BaseBoundInstrument } from './BoundInstrument';
21+
import { UpDownCounterMetric } from './UpDownCounterMetric';
2122
import {
2223
Metric,
2324
CounterMetric,
@@ -72,10 +73,9 @@ export class Meter implements api.Meter {
7273
return api.NOOP_VALUE_RECORDER_METRIC;
7374
}
7475
const opt: MetricOptions = {
75-
absolute: true, // value recorders are defined as absolute by default
76-
monotonic: false, // not applicable to value recorder, set to false
7776
logger: this._logger,
7877
...DEFAULT_METRIC_OPTIONS,
78+
absolute: true, // value recorders are defined as absolute by default
7979
...options,
8080
};
8181

@@ -104,8 +104,6 @@ export class Meter implements api.Meter {
104104
return api.NOOP_COUNTER_METRIC;
105105
}
106106
const opt: MetricOptions = {
107-
monotonic: true, // Counters are defined as monotonic by default
108-
absolute: false, // not applicable to counter, set to false
109107
logger: this._logger,
110108
...DEFAULT_METRIC_OPTIONS,
111109
...options,
@@ -115,6 +113,41 @@ export class Meter implements api.Meter {
115113
return counter;
116114
}
117115

116+
/**
117+
* Creates a new `UpDownCounter` metric. UpDownCounter is a synchronous
118+
* instrument and very similar to Counter except that Add(increment)
119+
* supports negative increments. It is generally useful for capturing changes
120+
* in an amount of resources used, or any quantity that rises and falls
121+
* during a request.
122+
*
123+
* @param name the name of the metric.
124+
* @param [options] the metric options.
125+
*/
126+
createUpDownCounter(
127+
name: string,
128+
options?: api.MetricOptions
129+
): api.UpDownCounter {
130+
if (!this._isValidName(name)) {
131+
this._logger.warn(
132+
`Invalid metric name ${name}. Defaulting to noop metric implementation.`
133+
);
134+
return api.NOOP_COUNTER_METRIC;
135+
}
136+
const opt: MetricOptions = {
137+
logger: this._logger,
138+
...DEFAULT_METRIC_OPTIONS,
139+
...options,
140+
};
141+
const upDownCounter = new UpDownCounterMetric(
142+
name,
143+
opt,
144+
this._batcher,
145+
this._resource
146+
);
147+
this._registerMetric(name, upDownCounter);
148+
return upDownCounter;
149+
}
150+
118151
/**
119152
* Creates a new observer metric.
120153
* @param name the name of the metric.
@@ -128,8 +161,6 @@ export class Meter implements api.Meter {
128161
return api.NOOP_OBSERVER_METRIC;
129162
}
130163
const opt: MetricOptions = {
131-
monotonic: false, // Observers are defined as non-monotonic by default
132-
absolute: false, // not applicable to observer, set to false
133164
logger: this._logger,
134165
...DEFAULT_METRIC_OPTIONS,
135166
...options,
@@ -188,7 +219,8 @@ export class Meter implements api.Meter {
188219
*
189220
* 2. The first character must be non-numeric, non-space, non-punctuation
190221
*
191-
* 3. Subsequent characters must be belong to the alphanumeric characters, '_', '.', and '-'.
222+
* 3. Subsequent characters must be belong to the alphanumeric characters,
223+
* '_', '.', and '-'.
192224
*
193225
* Names are case insensitive
194226
*

packages/opentelemetry-metrics/src/Metric.ts

+2-8
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import { hashLabels } from './Utils';
3131
/** This is a SDK implementation of {@link Metric} interface. */
3232
export abstract class Metric<T extends BaseBoundInstrument>
3333
implements api.UnboundMetric<T> {
34-
protected readonly _monotonic: boolean;
3534
protected readonly _disabled: boolean;
3635
protected readonly _valueType: api.ValueType;
3736
protected readonly _logger: api.Logger;
@@ -44,7 +43,6 @@ export abstract class Metric<T extends BaseBoundInstrument>
4443
private readonly _kind: MetricKind,
4544
readonly resource: Resource
4645
) {
47-
this._monotonic = _options.monotonic;
4846
this._disabled = _options.disabled;
4947
this._valueType = _options.valueType;
5048
this._logger = _options.logger;
@@ -98,7 +96,6 @@ export abstract class Metric<T extends BaseBoundInstrument>
9896
unit: this._options.unit,
9997
metricKind: this._kind,
10098
valueType: this._valueType,
101-
monotonic: this._monotonic,
10299
};
103100
}
104101

@@ -119,7 +116,6 @@ export class CounterMetric extends Metric<BoundCounter> implements api.Counter {
119116
return new BoundCounter(
120117
labels,
121118
this._disabled,
122-
this._monotonic,
123119
this._valueType,
124120
this._logger,
125121
// @todo: consider to set to CounterSumAggregator always.
@@ -130,8 +126,8 @@ export class CounterMetric extends Metric<BoundCounter> implements api.Counter {
130126
/**
131127
* Adds the given value to the current value. Values cannot be negative.
132128
* @param value the value to add.
133-
* @param [labels = {}] key-values pairs that are associated with a specific metric
134-
* that you want to record.
129+
* @param [labels = {}] key-values pairs that are associated with a specific
130+
* metric that you want to record.
135131
*/
136132
add(value: number, labels: api.Labels = {}) {
137133
this.bind(labels).add(value);
@@ -156,7 +152,6 @@ export class ValueRecorderMetric extends Metric<BoundValueRecorder>
156152
return new BoundValueRecorder(
157153
labels,
158154
this._disabled,
159-
this._monotonic,
160155
this._absolute,
161156
this._valueType,
162157
this._logger,
@@ -187,7 +182,6 @@ export class ObserverMetric extends Metric<BoundObserver>
187182
return new BoundObserver(
188183
labels,
189184
this._disabled,
190-
this._monotonic,
191185
this._valueType,
192186
this._logger,
193187
this._batcher.aggregatorFor(this._descriptor)

0 commit comments

Comments
 (0)