Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(batcher): document how to configure custom aggregators #989 #1100

Merged
merged 3 commits into from
May 28, 2020
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions doc/batcher-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Batcher API Guide

[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.

## Selecting a specific aggregator for metrics

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.

Here is what an aggregator that does that would look like:
```ts
import { Aggregator } from '@opentelemetry/metrics';
import { hrTime } from '@opentelemetry/core';

export class AverageAggregator implements Aggregator {

private _values: number[] = [];
private _limit: number;

constructor (limit?: number) {
this._limit = limit ?? 10;
}

update (value: number) {
this._values.push(value);
if (this._values.length >= this._limit) {
this._values = this._values.slice(0, 10);
}
}

toPoint() {
const sum =this._values.reduce((agg, value) => {
agg += value;
return agg;
}, 0);
return {
value: this._values.length > 0 ? sum / this._values.length : 0,
timestamp: hrTime(),
}
}
}
```

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.

Here the result:
```ts
import {
UngroupedBatcher,
MetricDescriptor,
CounterSumAggregator,
ObserverAggregator,
MeasureExactAggregator,
} from '@opentelemetry/metrics';

export class CustomBatcher extends UngroupedBatcher {
aggregatorFor (metricDescriptor: MetricDescriptor) {
if (metricDescriptor.labels === 'metricToBeAveraged') {
return new AverageAggregator(10);
}
// this is exactly what the "UngroupedBatcher" does, we will re-use it
// to fallback on the default behavior
switch (metricDescriptor.metricKind) {
case MetricKind.COUNTER:
return new CounterSumAggregator();
case MetricKind.OBSERVER:
return new ObserverAggregator();
default:
return new MeasureExactAggregator();
}
}
}
```

Finally, we need to specify to the `MeterProvider` to use our `CustomBatcher` when creating new meter:

```ts
import {
UngroupedBatcher,
MetricDescriptor,
CounterSumAggregator,
ObserverAggregator,
MeasureExactAggregator,
MeterProvider,
Aggregator,
} from '@opentelemetry/metrics';
import { hrTime } from '@opentelemetry/core';

export class AverageAggregator implements Aggregator {

private _values: number[] = [];
private _limit: number;

constructor (limit?: number) {
this._limit = limit ?? 10;
}

update (value: number) {
this._values.push(value);
if (this._values.length >= this._limit) {
this._values = this._values.slice(0, 10);
}
}

toPoint() {
const sum =this._values.reduce((agg, value) => {
agg += value;
return agg;
}, 0);
return {
value: this._values.length > 0 ? sum / this._values.length : 0,
timestamp: hrTime(),
}
}
}

export class CustomBatcher extends UngroupedBatcher {
aggregatorFor (metricDescriptor: MetricDescriptor) {
if (metricDescriptor.name === 'requests') {
return new AverageAggregator(10);
}
// this is exactly what the "UngroupedBatcher" does, we will re-use it
// to fallback on the default behavior
switch (metricDescriptor.metricKind) {
case MetricKind.COUNTER:
return new CounterSumAggregator();
case MetricKind.OBSERVER:
return new ObserverAggregator();
default:
return new MeasureExactAggregator();
}
}
}

const meter = new MeterProvider({
batcher: new CustomBatcher(),
interval: 1000,
}).getMeter('example-custom-batcher');

const requestsLatency = meter.createMeasure('requests', {
monotonic: true,
description: 'Average latency'
});
```