Skip to content

Commit 998f3f6

Browse files
obecnydyladan
andauthored
chore: adding metric observable to be able to support async upda… (open-telemetry#964)
* chore: adding metric observable to be able to support async update * chore: reviews * chore: reviews Co-authored-by: Daniel Dyla <[email protected]>
1 parent 60132b9 commit 998f3f6

File tree

12 files changed

+191
-34
lines changed

12 files changed

+191
-34
lines changed

examples/metrics/metrics/observer.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const { MeterProvider } = require('@opentelemetry/metrics');
3+
const { MeterProvider, MetricObservable } = require('@opentelemetry/metrics');
44
const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus');
55

66
const exporter = new PrometheusExporter(
@@ -14,7 +14,7 @@ const exporter = new PrometheusExporter(
1414

1515
const meter = new MeterProvider({
1616
exporter,
17-
interval: 1000,
17+
interval: 2000,
1818
}).getMeter('example-observer');
1919

2020
const otelCpuUsage = meter.createObserver('metric_observer', {
@@ -27,9 +27,16 @@ function getCpuUsage() {
2727
return Math.random();
2828
}
2929

30+
const observable = new MetricObservable();
31+
32+
setInterval(() => {
33+
observable.next(getCpuUsage());
34+
}, 5000);
35+
3036
otelCpuUsage.setCallback((observerResult) => {
3137
observerResult.observe(getCpuUsage, { pid: process.pid, core: '1' });
3238
observerResult.observe(getCpuUsage, { pid: process.pid, core: '2' });
3339
observerResult.observe(getCpuUsage, { pid: process.pid, core: '3' });
3440
observerResult.observe(getCpuUsage, { pid: process.pid, core: '4' });
41+
observerResult.observe(observable, { pid: process.pid, core: '5' });
3542
});

examples/metrics/metrics/observer.png

-149 KB
Loading

packages/opentelemetry-api/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export * from './metrics/BoundInstrument';
2626
export * from './metrics/Meter';
2727
export * from './metrics/MeterProvider';
2828
export * from './metrics/Metric';
29+
export * from './metrics/MetricObservable';
2930
export * from './metrics/NoopMeter';
3031
export * from './metrics/NoopMeterProvider';
3132
export * from './metrics/ObserverResult';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*!
2+
* Copyright 2020, OpenTelemetry 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+
* https://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+
/**
18+
* Metric Observable class to handle asynchronous metrics
19+
*/
20+
export interface MetricObservable {
21+
/**
22+
* Sets the next value for observable metric
23+
* @param value
24+
*/
25+
next: (value: number) => void;
26+
/**
27+
* Subscribes for every value change
28+
* @param callback
29+
*/
30+
subscribe: (callback: (value: number) => void) => void;
31+
/**
32+
* Removes the subscriber
33+
* @param [callback]
34+
*/
35+
unsubscribe: (callback?: (value: number) => void) => void;
36+
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515
*/
1616

1717
import { Labels } from './Metric';
18+
import { MetricObservable } from './MetricObservable';
1819

1920
/**
2021
* Interface that is being used in function setCallback for Observer Metric
2122
*/
2223
export interface ObserverResult {
23-
observers: Map<Labels, Function>;
24-
observe(callback: Function, labels: Labels): void;
24+
observe(callback: Function | MetricObservable, labels: Labels): void;
2525
}

packages/opentelemetry-exporter-collector/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
"ts-node": "^8.6.2",
7878
"tslint-consistent-codestyle": "^1.16.0",
7979
"tslint-microsoft-contrib": "^6.2.0",
80-
"typescript": "3.6.4",
80+
"typescript": "3.7.2",
8181
"webpack": "^4.35.2",
8282
"webpack-cli": "^3.3.9",
8383
"webpack-merge": "^4.2.2"

packages/opentelemetry-metrics/README.md

+37-2
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,50 @@ const { MeterProvider } = require('@opentelemetry/metrics');
2525
const meter = new MeterProvider().getMeter('your-meter-name');
2626

2727
const counter = meter.createCounter('metric_name', {
28-
labelKeys: ["pid"],
29-
description: "Example of a counter"
28+
labelKeys: ['pid'],
29+
description: 'Example of a counter'
3030
});
3131

3232
const labels = { pid: process.pid };
3333

3434
// Create a BoundInstrument associated with specified label values.
3535
const boundCounter = counter.bind(labels);
3636
boundCounter.add(10);
37+
38+
```
39+
40+
### Observable
41+
Choose this kind of metric when only last value is important without worry about aggregation
42+
43+
```js
44+
const { MeterProvider, MetricObservable } = require('@opentelemetry/metrics');
45+
46+
// Initialize the Meter to capture measurements in various ways.
47+
const meter = new MeterProvider().getMeter('your-meter-name');
48+
49+
const observer = meter.createObserver('metric_name', {
50+
labelKeys: ['pid', 'core'],
51+
description: 'Example of a observer'
52+
});
53+
54+
function getCpuUsage() {
55+
return Math.random();
56+
}
57+
58+
const metricObservable = new MetricObservable();
59+
60+
observer.setCallback((observerResult) => {
61+
// synchronous callback
62+
observerResult.observe(getCpuUsage, { pid: process.pid, core: '1' });
63+
// asynchronous callback
64+
observerResult.observe(metricObservable, { pid: process.pid, core: '2' });
65+
});
66+
67+
// simulate asynchronous operation
68+
setInterval(()=> {
69+
metricObservable.next(getCpuUsage());
70+
}, 2000)
71+
3772
```
3873

3974
See [examples/prometheus](https://github.com/open-telemetry/opentelemetry-js/tree/master/examples/prometheus) for a short example.

packages/opentelemetry-metrics/src/Metric.ts

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

17-
import * as types from '@opentelemetry/api';
17+
import * as api from '@opentelemetry/api';
1818
import { Resource } from '@opentelemetry/resources';
1919
import {
2020
BoundCounter,
@@ -30,11 +30,11 @@ import { hashLabels } from './Utils';
3030

3131
/** This is a SDK implementation of {@link Metric} interface. */
3232
export abstract class Metric<T extends BaseBoundInstrument>
33-
implements types.Metric<T> {
33+
implements api.Metric<T> {
3434
protected readonly _monotonic: boolean;
3535
protected readonly _disabled: boolean;
36-
protected readonly _valueType: types.ValueType;
37-
protected readonly _logger: types.Logger;
36+
protected readonly _valueType: api.ValueType;
37+
protected readonly _logger: api.Logger;
3838
private readonly _descriptor: MetricDescriptor;
3939
private readonly _instruments: Map<string, T> = new Map();
4040

@@ -58,7 +58,7 @@ export abstract class Metric<T extends BaseBoundInstrument>
5858
* @param labels key-values pairs that are associated with a specific metric
5959
* that you want to record.
6060
*/
61-
bind(labels: types.Labels): T {
61+
bind(labels: api.Labels): T {
6262
const hash = hashLabels(labels);
6363
if (this._instruments.has(hash)) return this._instruments.get(hash)!;
6464

@@ -71,7 +71,7 @@ export abstract class Metric<T extends BaseBoundInstrument>
7171
* Removes the Instrument from the metric, if it is present.
7272
* @param labels key-values pairs that are associated with a specific metric.
7373
*/
74-
unbind(labels: types.Labels): void {
74+
unbind(labels: api.Labels): void {
7575
this._instruments.delete(hashLabels(labels));
7676
}
7777

@@ -102,12 +102,12 @@ export abstract class Metric<T extends BaseBoundInstrument>
102102
};
103103
}
104104

105-
protected abstract _makeInstrument(labels: types.Labels): T;
105+
protected abstract _makeInstrument(labels: api.Labels): T;
106106
}
107107

108108
/** This is a SDK implementation of Counter Metric. */
109109
export class CounterMetric extends Metric<BoundCounter>
110-
implements Pick<types.MetricUtils, 'add'> {
110+
implements Pick<api.MetricUtils, 'add'> {
111111
constructor(
112112
name: string,
113113
options: MetricOptions,
@@ -116,7 +116,7 @@ export class CounterMetric extends Metric<BoundCounter>
116116
) {
117117
super(name, options, MetricKind.COUNTER, resource);
118118
}
119-
protected _makeInstrument(labels: types.Labels): BoundCounter {
119+
protected _makeInstrument(labels: api.Labels): BoundCounter {
120120
return new BoundCounter(
121121
labels,
122122
this._disabled,
@@ -134,13 +134,13 @@ export class CounterMetric extends Metric<BoundCounter>
134134
* @param labels key-values pairs that are associated with a specific metric
135135
* that you want to record.
136136
*/
137-
add(value: number, labels: types.Labels) {
137+
add(value: number, labels: api.Labels) {
138138
this.bind(labels).add(value);
139139
}
140140
}
141141

142142
export class MeasureMetric extends Metric<BoundMeasure>
143-
implements Pick<types.MetricUtils, 'record'> {
143+
implements Pick<api.MetricUtils, 'record'> {
144144
protected readonly _absolute: boolean;
145145

146146
constructor(
@@ -153,7 +153,7 @@ export class MeasureMetric extends Metric<BoundMeasure>
153153

154154
this._absolute = options.absolute !== undefined ? options.absolute : true; // Absolute default is true
155155
}
156-
protected _makeInstrument(labels: types.Labels): BoundMeasure {
156+
protected _makeInstrument(labels: api.Labels): BoundMeasure {
157157
return new BoundMeasure(
158158
labels,
159159
this._disabled,
@@ -165,15 +165,15 @@ export class MeasureMetric extends Metric<BoundMeasure>
165165
);
166166
}
167167

168-
record(value: number, labels: types.Labels) {
168+
record(value: number, labels: api.Labels) {
169169
this.bind(labels).record(value);
170170
}
171171
}
172172

173173
/** This is a SDK implementation of Observer Metric. */
174174
export class ObserverMetric extends Metric<BoundObserver>
175-
implements Pick<types.MetricUtils, 'setCallback'> {
176-
private _observerResult: types.ObserverResult = new ObserverResult();
175+
implements Pick<api.MetricUtils, 'setCallback'> {
176+
private _observerResult = new ObserverResult();
177177

178178
constructor(
179179
name: string,
@@ -184,7 +184,7 @@ export class ObserverMetric extends Metric<BoundObserver>
184184
super(name, options, MetricKind.OBSERVER, resource);
185185
}
186186

187-
protected _makeInstrument(labels: types.Labels): BoundObserver {
187+
protected _makeInstrument(labels: api.Labels): BoundObserver {
188188
return new BoundObserver(
189189
labels,
190190
this._disabled,
@@ -196,7 +196,7 @@ export class ObserverMetric extends Metric<BoundObserver>
196196
}
197197

198198
getMetricRecord(): MetricRecord[] {
199-
this._observerResult.observers.forEach((callback, labels) => {
199+
this._observerResult.callbackObservers.forEach((callback, labels) => {
200200
const instrument = this.bind(labels);
201201
instrument.update(callback());
202202
});
@@ -207,7 +207,13 @@ export class ObserverMetric extends Metric<BoundObserver>
207207
* Sets a callback where user can observe value for certain labels
208208
* @param callback
209209
*/
210-
setCallback(callback: (observerResult: types.ObserverResult) => void): void {
210+
setCallback(callback: (observerResult: api.ObserverResult) => void): void {
211211
callback(this._observerResult);
212+
this._observerResult.observers.forEach((observer, labels) => {
213+
observer.subscribe(value => {
214+
const instrument = this.bind(labels);
215+
instrument.update(value);
216+
});
217+
});
212218
}
213219
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*!
2+
* Copyright 2020, OpenTelemetry 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+
* https://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 * as api from '@opentelemetry/api';
18+
19+
type Subscriber = (value?: number) => void;
20+
21+
/**
22+
* Implements the Metric Observable pattern
23+
*/
24+
export class MetricObservable implements api.MetricObservable {
25+
private _subscribers: Subscriber[] = [];
26+
27+
next(value: number) {
28+
for (const subscriber of this._subscribers) {
29+
subscriber(value);
30+
}
31+
}
32+
33+
subscribe(subscriber: Function) {
34+
if (typeof subscriber === 'function') {
35+
this._subscribers.push(subscriber as Subscriber);
36+
}
37+
}
38+
39+
unsubscribe(subscriber?: Function) {
40+
if (typeof subscriber === 'function') {
41+
for (let i = 0, j = this._subscribers.length; i < j; i++) {
42+
if (this._subscribers[i] === subscriber) {
43+
this._subscribers.splice(i, 1);
44+
break;
45+
}
46+
}
47+
} else {
48+
this._subscribers = [];
49+
}
50+
}
51+
}

packages/opentelemetry-metrics/src/ObserverResult.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import {
18+
MetricObservable,
1819
ObserverResult as TypeObserverResult,
1920
Labels,
2021
} from '@opentelemetry/api';
@@ -23,8 +24,17 @@ import {
2324
* Implementation of {@link TypeObserverResult}
2425
*/
2526
export class ObserverResult implements TypeObserverResult {
26-
observers = new Map<Labels, Function>();
27-
observe(callback: any, labels: Labels): void {
28-
this.observers.set(labels, callback);
27+
callbackObservers: Map<Labels, Function> = new Map<Labels, Function>();
28+
observers: Map<Labels, MetricObservable> = new Map<
29+
Labels,
30+
MetricObservable
31+
>();
32+
33+
observe(callback: Function | MetricObservable, labels: Labels): void {
34+
if (typeof callback === 'function') {
35+
this.callbackObservers.set(labels, callback);
36+
} else {
37+
this.observers.set(labels, callback);
38+
}
2939
}
3040
}

packages/opentelemetry-metrics/src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616

1717
export * from './BoundInstrument';
1818
export * from './Meter';
19-
export * from './Metric';
2019
export * from './MeterProvider';
20+
export * from './Metric';
21+
export * from './MetricObservable';
2122
export * from './export/aggregators';
2223
export * from './export/ConsoleMetricExporter';
2324
export * from './export/types';

0 commit comments

Comments
 (0)