From c960eec1b8b6245fc290d5f7c5bd21eab34f57d7 Mon Sep 17 00:00:00 2001 From: bryan Date: Wed, 8 Jul 2020 08:27:31 -0700 Subject: [PATCH 1/6] feat: added sum observer metric --- .../opentelemetry-api/src/metrics/Metric.ts | 3 + .../src/metrics/NoopMeter.ts | 6 + packages/opentelemetry-metrics/README.md | 39 +++++ packages/opentelemetry-metrics/src/Meter.ts | 29 ++++ .../src/SumObserverMetric.ts | 45 ++++++ .../opentelemetry-metrics/test/Meter.test.ts | 144 ++++++++++++++++++ 6 files changed, 266 insertions(+) create mode 100644 packages/opentelemetry-metrics/src/SumObserverMetric.ts diff --git a/packages/opentelemetry-api/src/metrics/Metric.ts b/packages/opentelemetry-api/src/metrics/Metric.ts index d495762e279..ee7fd29ab44 100644 --- a/packages/opentelemetry-api/src/metrics/Metric.ts +++ b/packages/opentelemetry-api/src/metrics/Metric.ts @@ -179,6 +179,9 @@ export type ValueObserver = BaseObserver; /** Base interface for the UpDownSumObserver metrics. */ export type UpDownSumObserver = BaseObserver; +/** Base interface for the SumObserver metrics. */ +export type SumObserver = BaseObserver; + /** Base interface for the Batch Observer metrics. */ export type BatchObserver = Metric; diff --git a/packages/opentelemetry-api/src/metrics/NoopMeter.ts b/packages/opentelemetry-api/src/metrics/NoopMeter.ts index 5667160372b..47df1ab970d 100644 --- a/packages/opentelemetry-api/src/metrics/NoopMeter.ts +++ b/packages/opentelemetry-api/src/metrics/NoopMeter.ts @@ -202,7 +202,13 @@ export const NOOP_BOUND_BASE_OBSERVER = new NoopBoundBaseObserver(); export const NOOP_VALUE_OBSERVER_METRIC = new NoopBaseObserverMetric( NOOP_BOUND_BASE_OBSERVER ); + export const NOOP_UP_DOWN_SUM_OBSERVER_METRIC = new NoopBaseObserverMetric( NOOP_BOUND_BASE_OBSERVER ); + +export const NOOP_SUM_OBSERVER_METRIC = new NoopBaseObserverMetric( + NOOP_BOUND_BASE_OBSERVER +); + export const NOOP_BATCH_OBSERVER_METRIC = new NoopBatchObserverMetric(); diff --git a/packages/opentelemetry-metrics/README.md b/packages/opentelemetry-metrics/README.md index 2e2f1ad3937..97ac5fe888a 100644 --- a/packages/opentelemetry-metrics/README.md +++ b/packages/opentelemetry-metrics/README.md @@ -153,6 +153,45 @@ function getRandomValue() { ``` +### Sum Observer + +Choose this kind of metric when collecting a sum that never falls over the lifetime of the process. +The callback can be sync or async. + +```js +const { MeterProvider } = require('@opentelemetry/metrics'); + +const meter = new MeterProvider().getMeter('your-meter-name'); + +// async callback in case you need to wait for values +meter.createSumObserver('example_metric', { + description: 'Example of an async sum observer with callback', +}, async (observerResult) => { + const value = await getAsyncValue(); + observerResult.observe(value, { label: '1' }); +}); + +function getAsyncValue() { + return new Promise((resolve) => { + setTimeout(() => { + resolve(Math.random()); + }, 100) + }); +} + +// sync callback in case you don't need to wait for values +meter.createSumObserver('example_metric', { + description: 'Example of a sync sum observer with callback', +}, (observerResult) => { + const value = getRandomValue(); + observerResult.observe(value, { label: '1' }); +}); + +function getRandomValue() { + return Math.random(); +} +``` + ### Batch Observer Choose this kind of metric when you need to update multiple observers with the results of a single async calculation. diff --git a/packages/opentelemetry-metrics/src/Meter.ts b/packages/opentelemetry-metrics/src/Meter.ts index 950296ac1a0..62bdf958e57 100644 --- a/packages/opentelemetry-metrics/src/Meter.ts +++ b/packages/opentelemetry-metrics/src/Meter.ts @@ -25,6 +25,7 @@ import { UpDownSumObserverMetric } from './UpDownSumObserverMetric'; import { ValueRecorderMetric } from './ValueRecorderMetric'; import { Metric } from './Metric'; import { ValueObserverMetric } from './ValueObserverMetric'; +import { SumObserverMetric } from './SumObserverMetric'; import { DEFAULT_METRIC_OPTIONS, DEFAULT_CONFIG, MeterConfig } from './types'; import { Batcher, UngroupedBatcher } from './export/Batcher'; import { PushController } from './export/Controller'; @@ -190,6 +191,34 @@ export class Meter implements api.Meter { return valueObserver; } + createSumObserver( + name: string, + options: api.MetricOptions = {}, + callback?: (observerResult: api.ObserverResult) => unknown + ): api.SumObserver { + if (!this._isValidName(name)) { + this._logger.warn( + `Invalid metric name ${name}. Defaulting to noop metric implementation.` + ); + return api.NOOP_SUM_OBSERVER_METRIC; + } + const opt: api.MetricOptions = { + logger: this._logger, + ...DEFAULT_METRIC_OPTIONS, + ...options + }; + const sumObserver = new SumObserverMetric( + name, + opt, + this._batcher, + this._resource, + this._instrumentationLibrary, + callback + ); + this._registerMetric(name, sumObserver); + return sumObserver; + } + /** * Creates a new `UpDownSumObserver` metric. * @param name the name of the metric. diff --git a/packages/opentelemetry-metrics/src/SumObserverMetric.ts b/packages/opentelemetry-metrics/src/SumObserverMetric.ts new file mode 100644 index 00000000000..e0bfae14bdb --- /dev/null +++ b/packages/opentelemetry-metrics/src/SumObserverMetric.ts @@ -0,0 +1,45 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as api from '@opentelemetry/api'; +import { InstrumentationLibrary } from '@opentelemetry/core'; +import { Resource } from '@opentelemetry/resources'; +import { BaseObserverMetric } from './BaseObserverMetric'; +import { Batcher } from './export/Batcher'; +import { MetricKind } from './export/types'; + +/** This is a SDK implementation of UpDownSumObserver Metric. */ +export class SumObserverMetric extends BaseObserverMetric + implements api.SumObserver { + constructor( + name: string, + options: api.MetricOptions, + batcher: Batcher, + resource: Resource, + instrumentationLibrary: InstrumentationLibrary, + callback?: (observerResult: api.ObserverResult) => unknown + ) { + super( + name, + options, + batcher, + resource, + MetricKind.SUM_OBSERVER, + instrumentationLibrary, + callback + ); + } +} diff --git a/packages/opentelemetry-metrics/test/Meter.test.ts b/packages/opentelemetry-metrics/test/Meter.test.ts index da9fb32ce8d..7c7ce0a80bc 100644 --- a/packages/opentelemetry-metrics/test/Meter.test.ts +++ b/packages/opentelemetry-metrics/test/Meter.test.ts @@ -36,6 +36,7 @@ import * as api from '@opentelemetry/api'; import { NoopLogger, hrTime, hrTimeToNanoseconds } from '@opentelemetry/core'; import { BatchObserverResult } from '../src/BatchObserverResult'; import { SumAggregator } from '../src/export/aggregators'; +import { SumObserverMetric } from '../src/SumObserverMetric' import { Resource } from '@opentelemetry/resources'; import { UpDownSumObserverMetric } from '../src/UpDownSumObserverMetric'; import { hashLabels } from '../src/Utils'; @@ -683,6 +684,149 @@ describe('Meter', () => { }); }); + describe('#SumObserverMetric', () => { + it('should create an Sum observer', () => { + const sumObserver = meter.createSumObserver( + 'name' + ) as SumObserverMetric; + assert.ok(sumObserver instanceof Metric); + }); + + it('should return noop observer when name is invalid', () => { + const spy = sinon.stub(meter['_logger'], 'warn'); + const sumObserver = meter.createSumObserver('na me'); + assert.ok(sumObserver === api.NOOP_SUM_OBSERVER_METRIC); + const args = spy.args[0]; + assert.ok( + args[0], + 'Invalid metric name na me. Defaulting to noop metric implementation.' + ); + }); + + it('should create observer with options', () => { + const sumObserver = meter.createSumObserver('name', { + description: 'desc', + unit: '1', + disabled: false, + }) as SumObserverMetric; + assert.ok(sumObserver instanceof Metric); + }); + + it('should set callback and observe value ', async () => { + let counter = 0; + function getValue() { + counter++; + if (counter % 2 === 0) { + return -1; + } + return 3; + } + const sumObserver = meter.createSumObserver( + 'name', + { + description: 'desc', + }, + (observerResult: api.ObserverResult) => { + // simulate async + return new Promise(resolve => { + setTimeout(() => { + observerResult.observe(getValue(), { pid: '123', core: '1' }); + resolve(); + }, 1); + }); + } + ) as SumObserverMetric; + + let metricRecords = await sumObserver.getMetricRecord(); + assert.strictEqual(metricRecords.length, 1); + let point = metricRecords[0].aggregator.toPoint(); + assert.strictEqual(point.value, 3); + assert.strictEqual( + hashLabels(metricRecords[0].labels), + '|#core:1,pid:123' + ); + + metricRecords = await sumObserver.getMetricRecord(); + assert.strictEqual(metricRecords.length, 1); + point = metricRecords[0].aggregator.toPoint(); + assert.strictEqual(point.value, 2); + + metricRecords = await sumObserver.getMetricRecord(); + assert.strictEqual(metricRecords.length, 1); + point = metricRecords[0].aggregator.toPoint(); + assert.strictEqual(point.value, 5); + }); + + it('should set callback and observe value when callback returns nothing', async () => { + const sumObserver = meter.createSumObserver( + 'name', + { + description: 'desc', + }, + (observerResult: api.ObserverResult) => { + observerResult.observe(1, { pid: '123', core: '1' }); + } + ) as SumObserverMetric; + + const metricRecords = await sumObserver.getMetricRecord(); + assert.strictEqual(metricRecords.length, 1); + }); + + it( + 'should set callback and observe value when callback returns anything' + + ' but Promise', + async () => { + const sumObserver = meter.createSumObserver( + 'name', + { + description: 'desc', + }, + (observerResult: api.ObserverResult) => { + observerResult.observe(1, { pid: '123', core: '1' }); + return '1'; + } + ) as SumObserverMetric; + + const metricRecords = await sumObserver.getMetricRecord(); + assert.strictEqual(metricRecords.length, 1); + } + ); + + it('should reject getMetricRecord when callback throws an error', async () => { + const sumObserver = meter.createSumObserver( + 'name', + { + description: 'desc', + }, + (observerResult: api.ObserverResult) => { + observerResult.observe(1, { pid: '123', core: '1' }); + throw new Error('Boom'); + } + ) as SumObserverMetric; + await sumObserver + .getMetricRecord() + .then() + .catch(e => { + assert.strictEqual(e.message, 'Boom'); + }); + }); + + it('should pipe through resource', async () => { + const sumObserver = meter.createSumObserver( + 'name', + {}, + result => { + result.observe(42, { foo: 'bar' }); + return Promise.resolve(); + } + ) as SumObserverMetric; + assert.ok(sumObserver.resource instanceof Resource); + + const [record] = await sumObserver.getMetricRecord(); + assert.ok(record.resource instanceof Resource); + }); + }); + describe('#ValueObserver', () => { it('should create a value observer', () => { const valueObserver = meter.createValueObserver( From 162d857d62d8032183a03228b43adec148478005 Mon Sep 17 00:00:00 2001 From: bryan Date: Fri, 10 Jul 2020 17:32:08 -0700 Subject: [PATCH 2/6] chore: lint --- packages/opentelemetry-metrics/src/Meter.ts | 2 +- .../opentelemetry-metrics/test/Meter.test.ts | 18 ++++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/opentelemetry-metrics/src/Meter.ts b/packages/opentelemetry-metrics/src/Meter.ts index 62bdf958e57..e6c33621137 100644 --- a/packages/opentelemetry-metrics/src/Meter.ts +++ b/packages/opentelemetry-metrics/src/Meter.ts @@ -205,7 +205,7 @@ export class Meter implements api.Meter { const opt: api.MetricOptions = { logger: this._logger, ...DEFAULT_METRIC_OPTIONS, - ...options + ...options, }; const sumObserver = new SumObserverMetric( name, diff --git a/packages/opentelemetry-metrics/test/Meter.test.ts b/packages/opentelemetry-metrics/test/Meter.test.ts index 7c7ce0a80bc..0f777488f98 100644 --- a/packages/opentelemetry-metrics/test/Meter.test.ts +++ b/packages/opentelemetry-metrics/test/Meter.test.ts @@ -36,7 +36,7 @@ import * as api from '@opentelemetry/api'; import { NoopLogger, hrTime, hrTimeToNanoseconds } from '@opentelemetry/core'; import { BatchObserverResult } from '../src/BatchObserverResult'; import { SumAggregator } from '../src/export/aggregators'; -import { SumObserverMetric } from '../src/SumObserverMetric' +import { SumObserverMetric } from '../src/SumObserverMetric'; import { Resource } from '@opentelemetry/resources'; import { UpDownSumObserverMetric } from '../src/UpDownSumObserverMetric'; import { hashLabels } from '../src/Utils'; @@ -686,9 +686,7 @@ describe('Meter', () => { describe('#SumObserverMetric', () => { it('should create an Sum observer', () => { - const sumObserver = meter.createSumObserver( - 'name' - ) as SumObserverMetric; + const sumObserver = meter.createSumObserver('name') as SumObserverMetric; assert.ok(sumObserver instanceof Metric); }); @@ -812,14 +810,10 @@ describe('Meter', () => { }); it('should pipe through resource', async () => { - const sumObserver = meter.createSumObserver( - 'name', - {}, - result => { - result.observe(42, { foo: 'bar' }); - return Promise.resolve(); - } - ) as SumObserverMetric; + const sumObserver = meter.createSumObserver('name', {}, result => { + result.observe(42, { foo: 'bar' }); + return Promise.resolve(); + }) as SumObserverMetric; assert.ok(sumObserver.resource instanceof Resource); const [record] = await sumObserver.getMetricRecord(); From 39e13c02d29af4534952b7b7ccd4f8f7f54b0072 Mon Sep 17 00:00:00 2001 From: bryan Date: Fri, 10 Jul 2020 18:43:28 -0700 Subject: [PATCH 3/6] fix: sum observer now throws away negative values --- packages/opentelemetry-metrics/src/BaseObserverMetric.ts | 6 +++++- packages/opentelemetry-metrics/src/ObserverResult.ts | 8 ++++++++ packages/opentelemetry-metrics/src/SumObserverMetric.ts | 5 +++++ packages/opentelemetry-metrics/test/Meter.test.ts | 7 +++---- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/opentelemetry-metrics/src/BaseObserverMetric.ts b/packages/opentelemetry-metrics/src/BaseObserverMetric.ts index 3065a34a077..db8911e982c 100644 --- a/packages/opentelemetry-metrics/src/BaseObserverMetric.ts +++ b/packages/opentelemetry-metrics/src/BaseObserverMetric.ts @@ -55,8 +55,12 @@ export abstract class BaseObserverMetric extends Metric ); } + protected getObserverResult(): ObserverResult { + return new ObserverResult(); + } + async getMetricRecord(): Promise { - const observerResult = new ObserverResult(); + const observerResult = this.getObserverResult(); await this._callback(observerResult); observerResult.values.forEach((value, labels) => { const instrument = this.bind(labels); diff --git a/packages/opentelemetry-metrics/src/ObserverResult.ts b/packages/opentelemetry-metrics/src/ObserverResult.ts index 60d555641f9..3350fff9877 100644 --- a/packages/opentelemetry-metrics/src/ObserverResult.ts +++ b/packages/opentelemetry-metrics/src/ObserverResult.ts @@ -29,3 +29,11 @@ export class ObserverResult implements TypeObserverResult { this.values.set(labels, value); } } + +export class MonotonicObserverResult extends ObserverResult { + observe(value: number, labels: Labels): void { + if (value >= 0) { + this.values.set(labels, value); + } + } +} diff --git a/packages/opentelemetry-metrics/src/SumObserverMetric.ts b/packages/opentelemetry-metrics/src/SumObserverMetric.ts index e0bfae14bdb..27bfb099232 100644 --- a/packages/opentelemetry-metrics/src/SumObserverMetric.ts +++ b/packages/opentelemetry-metrics/src/SumObserverMetric.ts @@ -18,6 +18,7 @@ import * as api from '@opentelemetry/api'; import { InstrumentationLibrary } from '@opentelemetry/core'; import { Resource } from '@opentelemetry/resources'; import { BaseObserverMetric } from './BaseObserverMetric'; +import { MonotonicObserverResult, ObserverResult } from './ObserverResult'; import { Batcher } from './export/Batcher'; import { MetricKind } from './export/types'; @@ -42,4 +43,8 @@ export class SumObserverMetric extends BaseObserverMetric callback ); } + + protected getObserverResult(): ObserverResult { + return new MonotonicObserverResult(); + } } diff --git a/packages/opentelemetry-metrics/test/Meter.test.ts b/packages/opentelemetry-metrics/test/Meter.test.ts index 0f777488f98..eb6f3d3d0a2 100644 --- a/packages/opentelemetry-metrics/test/Meter.test.ts +++ b/packages/opentelemetry-metrics/test/Meter.test.ts @@ -713,8 +713,7 @@ describe('Meter', () => { it('should set callback and observe value ', async () => { let counter = 0; function getValue() { - counter++; - if (counter % 2 === 0) { + if (++counter % 2 == 0) { return -1; } return 3; @@ -747,12 +746,12 @@ describe('Meter', () => { metricRecords = await sumObserver.getMetricRecord(); assert.strictEqual(metricRecords.length, 1); point = metricRecords[0].aggregator.toPoint(); - assert.strictEqual(point.value, 2); + assert.strictEqual(point.value, 3); metricRecords = await sumObserver.getMetricRecord(); assert.strictEqual(metricRecords.length, 1); point = metricRecords[0].aggregator.toPoint(); - assert.strictEqual(point.value, 5); + assert.strictEqual(point.value, 6); }); it('should set callback and observe value when callback returns nothing', async () => { From aeb82acf2211eb6e387ca184642bc858498c51a9 Mon Sep 17 00:00:00 2001 From: Bryan Clement Date: Fri, 10 Jul 2020 18:44:05 -0700 Subject: [PATCH 4/6] Update packages/opentelemetry-metrics/src/SumObserverMetric.ts Co-authored-by: Mayur Kale --- packages/opentelemetry-metrics/src/SumObserverMetric.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-metrics/src/SumObserverMetric.ts b/packages/opentelemetry-metrics/src/SumObserverMetric.ts index 27bfb099232..47da781b4fa 100644 --- a/packages/opentelemetry-metrics/src/SumObserverMetric.ts +++ b/packages/opentelemetry-metrics/src/SumObserverMetric.ts @@ -22,7 +22,7 @@ import { MonotonicObserverResult, ObserverResult } from './ObserverResult'; import { Batcher } from './export/Batcher'; import { MetricKind } from './export/types'; -/** This is a SDK implementation of UpDownSumObserver Metric. */ +/** This is a SDK implementation of SumObserver Metric. */ export class SumObserverMetric extends BaseObserverMetric implements api.SumObserver { constructor( From fbfec1f4f55d273a23676925702f0730c1df22b6 Mon Sep 17 00:00:00 2001 From: bryan Date: Sat, 11 Jul 2020 07:35:40 -0700 Subject: [PATCH 5/6] chore: address review comments --- .../src/BaseObserverMetric.ts | 4 +-- .../src/MonotonicObserverResult.ts | 25 +++++++++++++++++++ .../src/ObserverResult.ts | 8 ------ .../src/SumObserverMetric.ts | 5 ++-- 4 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 packages/opentelemetry-metrics/src/MonotonicObserverResult.ts diff --git a/packages/opentelemetry-metrics/src/BaseObserverMetric.ts b/packages/opentelemetry-metrics/src/BaseObserverMetric.ts index db8911e982c..733d701ce3e 100644 --- a/packages/opentelemetry-metrics/src/BaseObserverMetric.ts +++ b/packages/opentelemetry-metrics/src/BaseObserverMetric.ts @@ -55,12 +55,12 @@ export abstract class BaseObserverMetric extends Metric ); } - protected getObserverResult(): ObserverResult { + protected createObserverResult(): ObserverResult { return new ObserverResult(); } async getMetricRecord(): Promise { - const observerResult = this.getObserverResult(); + const observerResult = this.createObserverResult(); await this._callback(observerResult); observerResult.values.forEach((value, labels) => { const instrument = this.bind(labels); diff --git a/packages/opentelemetry-metrics/src/MonotonicObserverResult.ts b/packages/opentelemetry-metrics/src/MonotonicObserverResult.ts new file mode 100644 index 00000000000..480c196169d --- /dev/null +++ b/packages/opentelemetry-metrics/src/MonotonicObserverResult.ts @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Labels } from '@opentelemetry/api'; +import { ObserverResult } from './ObserverResult'; +export class MonotonicObserverResult extends ObserverResult { + observe(value: number, labels: Labels): void { + if (value >= 0) { + this.values.set(labels, value); + } + } +} diff --git a/packages/opentelemetry-metrics/src/ObserverResult.ts b/packages/opentelemetry-metrics/src/ObserverResult.ts index 3350fff9877..60d555641f9 100644 --- a/packages/opentelemetry-metrics/src/ObserverResult.ts +++ b/packages/opentelemetry-metrics/src/ObserverResult.ts @@ -29,11 +29,3 @@ export class ObserverResult implements TypeObserverResult { this.values.set(labels, value); } } - -export class MonotonicObserverResult extends ObserverResult { - observe(value: number, labels: Labels): void { - if (value >= 0) { - this.values.set(labels, value); - } - } -} diff --git a/packages/opentelemetry-metrics/src/SumObserverMetric.ts b/packages/opentelemetry-metrics/src/SumObserverMetric.ts index 47da781b4fa..892ca934664 100644 --- a/packages/opentelemetry-metrics/src/SumObserverMetric.ts +++ b/packages/opentelemetry-metrics/src/SumObserverMetric.ts @@ -18,7 +18,8 @@ import * as api from '@opentelemetry/api'; import { InstrumentationLibrary } from '@opentelemetry/core'; import { Resource } from '@opentelemetry/resources'; import { BaseObserverMetric } from './BaseObserverMetric'; -import { MonotonicObserverResult, ObserverResult } from './ObserverResult'; +import { ObserverResult } from './ObserverResult'; +import { MonotonicObserverResult } from './MonotonicObserverResult'; import { Batcher } from './export/Batcher'; import { MetricKind } from './export/types'; @@ -44,7 +45,7 @@ export class SumObserverMetric extends BaseObserverMetric ); } - protected getObserverResult(): ObserverResult { + protected createObserverResult(): ObserverResult { return new MonotonicObserverResult(); } } From c080034f6ed28a6324cbb707115e8e6333753cde Mon Sep 17 00:00:00 2001 From: Bryan Clement Date: Mon, 13 Jul 2020 11:12:15 -0700 Subject: [PATCH 6/6] Update packages/opentelemetry-metrics/README.md Co-authored-by: Daniel Dyla --- packages/opentelemetry-metrics/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-metrics/README.md b/packages/opentelemetry-metrics/README.md index 97ac5fe888a..7f200f9418e 100644 --- a/packages/opentelemetry-metrics/README.md +++ b/packages/opentelemetry-metrics/README.md @@ -155,7 +155,7 @@ function getRandomValue() { ### Sum Observer -Choose this kind of metric when collecting a sum that never falls over the lifetime of the process. +Choose this kind of metric when collecting a sum that never decreases. The callback can be sync or async. ```js