From f03b9f86b8ed7d492980c5622b7ee9a43e5c3cdd Mon Sep 17 00:00:00 2001 From: vmarchaud Date: Sat, 23 May 2020 18:44:07 +0200 Subject: [PATCH 1/2] docs(batcher): document how to configure custom aggregators #989 --- doc/batcher-api.md | 143 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 doc/batcher-api.md diff --git a/doc/batcher-api.md b/doc/batcher-api.md new file mode 100644 index 0000000000..28be825cd6 --- /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 From 2921c87b1781b6439caa59286eb96e1ce35f83db Mon Sep 17 00:00:00 2001 From: vmarchaud Date: Thu, 28 May 2020 17:03:13 +0200 Subject: [PATCH 2/2] chore: address PR comments --- doc/batcher-api.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/batcher-api.md b/doc/batcher-api.md index 28be825cd6..d627faca1d 100644 --- a/doc/batcher-api.md +++ b/doc/batcher-api.md @@ -1,12 +1,12 @@ # 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. +[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 a average of the last X values instead of just the last one. +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 a aggregator that does that would looks like: +Here is what an aggregator that does that would look like: ```ts import { Aggregator } from '@opentelemetry/metrics'; import { hrTime } from '@opentelemetry/core';