Skip to content

Commit 2987534

Browse files
docs(batcher): document how to configure custom aggregators #989 (#1100)
* docs(batcher): document how to configure custom aggregators #989 * chore: address PR comments Co-authored-by: Mayur Kale <[email protected]>
1 parent 177f672 commit 2987534

File tree

1 file changed

+143
-0
lines changed

1 file changed

+143
-0
lines changed

doc/batcher-api.md

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Batcher API Guide
2+
3+
[The batcher](https://github.com/open-telemetry/opentelemetry-js/blob/master/packages/opentelemetry-metrics/src/export/Batcher.ts?rgh-link-date=2020-05-25T18%3A43%3A57Z) has two responsibilities: choosing which aggregator to choose for a metric instrument and store the last record for each metric ready to be exported.
4+
5+
## Selecting a specific aggregator for metrics
6+
7+
Sometimes you may want to use a specific aggregator for one of your metric, export an average of the last X values instead of just the last one.
8+
9+
Here is what an aggregator that does that would look like:
10+
```ts
11+
import { Aggregator } from '@opentelemetry/metrics';
12+
import { hrTime } from '@opentelemetry/core';
13+
14+
export class AverageAggregator implements Aggregator {
15+
16+
private _values: number[] = [];
17+
private _limit: number;
18+
19+
constructor (limit?: number) {
20+
this._limit = limit ?? 10;
21+
}
22+
23+
update (value: number) {
24+
this._values.push(value);
25+
if (this._values.length >= this._limit) {
26+
this._values = this._values.slice(0, 10);
27+
}
28+
}
29+
30+
toPoint() {
31+
const sum =this._values.reduce((agg, value) => {
32+
agg += value;
33+
return agg;
34+
}, 0);
35+
return {
36+
value: this._values.length > 0 ? sum / this._values.length : 0,
37+
timestamp: hrTime(),
38+
}
39+
}
40+
}
41+
```
42+
43+
Now we will need to implement our own batcher to configure the sdk to use our new aggregator. To simplify even more, we will just extend the `UngroupedBatcher` (which is the default) to avoid re-implementing the whole `Aggregator` interface.
44+
45+
Here the result:
46+
```ts
47+
import {
48+
UngroupedBatcher,
49+
MetricDescriptor,
50+
CounterSumAggregator,
51+
ObserverAggregator,
52+
MeasureExactAggregator,
53+
} from '@opentelemetry/metrics';
54+
55+
export class CustomBatcher extends UngroupedBatcher {
56+
aggregatorFor (metricDescriptor: MetricDescriptor) {
57+
if (metricDescriptor.labels === 'metricToBeAveraged') {
58+
return new AverageAggregator(10);
59+
}
60+
// this is exactly what the "UngroupedBatcher" does, we will re-use it
61+
// to fallback on the default behavior
62+
switch (metricDescriptor.metricKind) {
63+
case MetricKind.COUNTER:
64+
return new CounterSumAggregator();
65+
case MetricKind.OBSERVER:
66+
return new ObserverAggregator();
67+
default:
68+
return new MeasureExactAggregator();
69+
}
70+
}
71+
}
72+
```
73+
74+
Finally, we need to specify to the `MeterProvider` to use our `CustomBatcher` when creating new meter:
75+
76+
```ts
77+
import {
78+
UngroupedBatcher,
79+
MetricDescriptor,
80+
CounterSumAggregator,
81+
ObserverAggregator,
82+
MeasureExactAggregator,
83+
MeterProvider,
84+
Aggregator,
85+
} from '@opentelemetry/metrics';
86+
import { hrTime } from '@opentelemetry/core';
87+
88+
export class AverageAggregator implements Aggregator {
89+
90+
private _values: number[] = [];
91+
private _limit: number;
92+
93+
constructor (limit?: number) {
94+
this._limit = limit ?? 10;
95+
}
96+
97+
update (value: number) {
98+
this._values.push(value);
99+
if (this._values.length >= this._limit) {
100+
this._values = this._values.slice(0, 10);
101+
}
102+
}
103+
104+
toPoint() {
105+
const sum =this._values.reduce((agg, value) => {
106+
agg += value;
107+
return agg;
108+
}, 0);
109+
return {
110+
value: this._values.length > 0 ? sum / this._values.length : 0,
111+
timestamp: hrTime(),
112+
}
113+
}
114+
}
115+
116+
export class CustomBatcher extends UngroupedBatcher {
117+
aggregatorFor (metricDescriptor: MetricDescriptor) {
118+
if (metricDescriptor.name === 'requests') {
119+
return new AverageAggregator(10);
120+
}
121+
// this is exactly what the "UngroupedBatcher" does, we will re-use it
122+
// to fallback on the default behavior
123+
switch (metricDescriptor.metricKind) {
124+
case MetricKind.COUNTER:
125+
return new CounterSumAggregator();
126+
case MetricKind.OBSERVER:
127+
return new ObserverAggregator();
128+
default:
129+
return new MeasureExactAggregator();
130+
}
131+
}
132+
}
133+
134+
const meter = new MeterProvider({
135+
batcher: new CustomBatcher(),
136+
interval: 1000,
137+
}).getMeter('example-custom-batcher');
138+
139+
const requestsLatency = meter.createMeasure('requests', {
140+
monotonic: true,
141+
description: 'Average latency'
142+
});
143+
```

0 commit comments

Comments
 (0)