diff --git a/doc/batcher-api.md b/doc/batcher-api.md new file mode 100644 index 00000000000..28be825cd68 --- /dev/null +++ b/doc/batcher-api.md @@ -0,0 +1,143 @@ +# Batcher API Guide + +The batcher has two responsabilities: 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 a average of the last X values instead of just the last one. + +Here is what a aggregator that does that would looks 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' +}); +``` \ No newline at end of file