diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/README.md b/experimental/packages/opentelemetry-sdk-metrics-base/README.md index 8aabceac79..e2b2896bc1 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/README.md +++ b/experimental/packages/opentelemetry-sdk-metrics-base/README.md @@ -7,187 +7,21 @@ OpenTelemetry metrics allow a user to collect data and export it to a metrics backend like [Prometheus](https://prometheus.io/). +## Work In Progress + +The OpenTelemetry SDK in this directory is undergoing drastic changes. If you need to use metrics, we recommend you use [version `0.27.0`](https://github.com/open-telemetry/opentelemetry-js/tree/v0.27.0/experimental/packages/opentelemetry-sdk-metrics-base). + ## Installation ```bash -npm install --save @opentelemetry/sdk-metrics-base +npm install --save "@opentelemetry/sdk-metrics-base@~0.27.0" ``` ## Usage -### Counter - -Choose this kind of metric when the value is a quantity, the sum is of primary interest, and the event count and value distribution are not of primary interest. It is restricted to non-negative increments. -Example uses for Counter: - -- count the number of bytes received -- count the number of requests completed -- count the number of accounts created -- count the number of checkpoints run -- count the number of 5xx errors. - -```js -const { MeterProvider } = require('@opentelemetry/sdk-metrics-base'); - -// Initialize the Meter to capture measurements in various ways. -const meter = new MeterProvider().getMeter('your-meter-name'); - -const counter = meter.createCounter('metric_name', { - description: 'Example of a counter' -}); - -const attributes = { pid: process.pid }; -counter.add(10, attributes); -``` - -### UpDownCounter - -`UpDownCounter` is similar to `Counter` except that it supports negative increments. It is generally useful for capturing changes in an amount of resources used, or any quantity that rises and falls during a request. - -Example uses for UpDownCounter: - -- count the number of active requests -- count memory in use by instrumenting new and delete -- count queue size by instrumenting enqueue and dequeue -- count semaphore up and down operations - -```js -const { MeterProvider } = require('@opentelemetry/sdk-metrics-base'); - -// Initialize the Meter to capture measurements in various ways. -const meter = new MeterProvider().getMeter('your-meter-name'); - -const counter = meter.createUpDownCounter('metric_name', { - description: 'Example of a UpDownCounter' -}); - -const attributes = { pid: process.pid }; -counter.add(Math.random() > 0.5 ? 1 : -1, attributes); -``` - -### Observable Gauge - -Choose this kind of metric when only last value is important without worry about aggregation. -The callback can be sync or async. - -```js -const { MeterProvider } = require('@opentelemetry/sdk-metrics-base'); - -const meter = new MeterProvider().getMeter('your-meter-name'); - - -// async callback - for operation that needs to wait for value -meter.createObservableGauge('your_metric_name', { - description: 'Example of an async observable gauge with callback', -}, async (observableResult) => { - const value = await getAsyncValue(); - observableResult.observe(value, { attribute: '1' }); -}); - -function getAsyncValue() { - return new Promise((resolve) => { - setTimeout(()=> { - resolve(Math.random()); - }, 100); - }); -} - -// sync callback in case you don't need to wait for value -meter.createObservableGauge('your_metric_name', { - description: 'Example of a sync observable gauge with callback', -}, (observableResult) => { - observableResult.observe(getRandomValue(), { attribute: '1' }); - observableResult.observe(getRandomValue(), { attribute: '2' }); -}); - -function getRandomValue() { - return Math.random(); -} -``` - -### ObservableUpDownCounter - -Choose this kind of metric when sum is important and you want to capture any value that starts at zero and rises or falls throughout the process lifetime. -The callback can be sync or async. - -```js -const { MeterProvider } = require('@opentelemetry/sdk-metrics-base'); - -const meter = new MeterProvider().getMeter('your-meter-name'); - -// async callback - for operation that needs to wait for value -meter.createObservableUpDownCounter('your_metric_name', { - description: 'Example of an async observable up down counter with callback', -}, async (observableResult) => { - const value = await getAsyncValue(); - observableResult.observe(value, { attribute: '1' }); -}); - -function getAsyncValue() { - return new Promise((resolve) => { - setTimeout(()=> { - resolve(Math.random()); - }, 100); - }); -} - -// sync callback in case you don't need to wait for value -meter.createObservableUpDownCounter('your_metric_name', { - description: 'Example of a sync observable up down counter with callback', -}, (observableResult) => { - observableResult.observe(getRandomValue(), { attribute: '1' }); -}); - -function getRandomValue() { - return Math.random(); -} - -``` - -### Observable Counter - -Choose this kind of metric when collecting a sum that never decreases. -The callback can be sync or async. - -```js -const { MeterProvider } = require('@opentelemetry/sdk-metrics-base'); - -const meter = new MeterProvider().getMeter('your-meter-name'); - -// async callback in case you need to wait for values -meter.createObservableCounter('example_metric', { - description: 'Example of an async observable counter with callback', -}, async (observableResult) => { - const value = await getAsyncValue(); - observableResult.observe(value, { attribute: '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.createObservableCounter('example_metric', { - description: 'Example of a sync observable counter with callback', -}, (observableResult) => { - const value = getRandomValue(); - observableResult.observe(value, { attribute: '1' }); -}); - -function getRandomValue() { - return Math.random(); -} -``` - -### Histogram +Please see the [version `0.27.0` README](https://github.com/open-telemetry/opentelemetry-js/tree/v0.27.0/experimental/packages/opentelemetry-sdk-metrics-base#usage). -`Histogram` is a non-additive synchronous instrument useful for recording any non-additive number, positive or negative. -Values captured by `Histogram.record(value)` are treated as individual events belonging to a distribution that is being summarized. -`Histogram` should be chosen either when capturing measurements that do not contribute meaningfully to a sum, or when capturing numbers that are additive in nature, but where the distribution of individual increments is considered interesting. +TODO: Add usage information for updated SDK ## Useful links @@ -207,4 +41,4 @@ Apache 2.0 - See [LICENSE][license-url] for more information. [devDependencies-image]: https://status.david-dm.org/gh/open-telemetry/opentelemetry-js.svg?path=packages%2Fopentelemetry-sdk-metrics-base&type=dev [devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-sdk-metrics-base&type=dev [npm-url]: https://www.npmjs.com/package/@opentelemetry/sdk-metrics-base -[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fsdk-metrics-base.svg +[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fmetrics.svg diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/package.json b/experimental/packages/opentelemetry-sdk-metrics-base/package.json index cdbac0f786..b03868fd5f 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/package.json +++ b/experimental/packages/opentelemetry-sdk-metrics-base/package.json @@ -1,7 +1,8 @@ { - "name": "@opentelemetry/sdk-metrics-base", - "version": "0.27.0", - "description": "OpenTelemetry metrics SDK", + "name": "@opentelemetry/sdk-metrics-base-wip", + "version": "0.26.0", + "private": true, + "description": "Work in progress OpenTelemetry metrics SDK", "main": "build/src/index.js", "module": "build/esm/index.js", "types": "build/src/index.d.ts", @@ -55,7 +56,7 @@ "mocha": "7.2.0", "nyc": "15.1.0", "rimraf": "3.0.2", - "sinon": "12.0.1", + "sinon": "11.1.2", "ts-mocha": "8.0.0", "typescript": "4.3.5" }, @@ -63,9 +64,9 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@opentelemetry/api-metrics": "0.27.0", - "@opentelemetry/core": "1.0.1", - "@opentelemetry/resources": "1.0.1", + "@opentelemetry/api-metrics": "0.26.0", + "@opentelemetry/core": "1.0.0", + "@opentelemetry/resources": "1.0.0", "lodash.merge": "^4.6.2" } } diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/BoundInstrument.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/BoundInstrument.ts deleted file mode 100644 index 71d330626b..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/BoundInstrument.ts +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 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 { diag } from '@opentelemetry/api'; -import * as api from '@opentelemetry/api-metrics'; -import { Aggregator } from './export/types'; - -/** - * This class represent the base to BoundInstrument, which is responsible for generating - * the TimeSeries. - */ -export class BaseBoundInstrument { - protected _attributes: api.Attributes; - - constructor( - attributes: api.Attributes, - private readonly _disabled: boolean, - private readonly _valueType: api.ValueType, - private readonly _aggregator: Aggregator - ) { - this._attributes = attributes; - } - - update(value: number): void { - if (this._disabled) return; - if (typeof value !== 'number') { - diag.error( - `Metric cannot accept a non-number value for ${Object.values( - this._attributes - )}.` - ); - return; - } - - if (this._valueType === api.ValueType.INT && !Number.isInteger(value)) { - diag.warn( - `INT value type cannot accept a floating-point value for ${Object.values( - this._attributes - )}, ignoring the fractional digits.` - ); - value = Math.trunc(value); - } - - this._aggregator.update(value); - } - - getAttributes(): api.Attributes { - return this._attributes; - } - - getAggregator(): Aggregator { - return this._aggregator; - } -} - -/** - * BoundCounter allows the SDK to observe/record a single metric event. The - * value of single instrument in the `Counter` associated with specified Attributes. - */ -export class BoundCounter - extends BaseBoundInstrument - implements api.Counter { - constructor( - attributes: api.Attributes, - disabled: boolean, - valueType: api.ValueType, - aggregator: Aggregator - ) { - super(attributes, disabled, valueType, aggregator); - } - - add(value: number): void { - if (value < 0) { - diag.error(`Counter cannot descend for ${Object.values(this._attributes)}`); - return; - } - - this.update(value); - } -} - -/** - * BoundUpDownCounter allows the SDK to observe/record a single metric event. - * The value of single instrument in the `UpDownCounter` associated with - * specified Attributes. - */ -export class BoundUpDownCounter - extends BaseBoundInstrument - implements api.UpDownCounter { - constructor( - attributes: api.Attributes, - disabled: boolean, - valueType: api.ValueType, - aggregator: Aggregator - ) { - super(attributes, disabled, valueType, aggregator); - } - - add(value: number): void { - this.update(value); - } -} - -/** - * BoundMeasure is an implementation of the {@link BoundMeasure} interface. - */ -export class BoundHistogram - extends BaseBoundInstrument - implements api.Histogram { - constructor( - attributes: api.Attributes, - disabled: boolean, - valueType: api.ValueType, - aggregator: Aggregator - ) { - super(attributes, disabled, valueType, aggregator); - } - - record(value: number): void { - this.update(value); - } -} - -/** - * BoundObservable is an implementation of the {@link BoundObservable} interface. - */ -export class BoundObservable extends BaseBoundInstrument { - constructor( - attributes: api.Attributes, - disabled: boolean, - valueType: api.ValueType, - aggregator: Aggregator - ) { - super(attributes, disabled, valueType, aggregator); - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/CounterMetric.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/CounterMetric.ts deleted file mode 100644 index cda71ac921..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/CounterMetric.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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-metrics'; -import { InstrumentationLibrary } from '@opentelemetry/core'; -import { Resource } from '@opentelemetry/resources'; -import { BoundCounter } from './BoundInstrument'; -import { Processor } from './export/Processor'; -import { MetricKind } from './export/types'; -import { Metric } from './Metric'; - -/** This is a SDK implementation of Counter Metric. */ -export class CounterMetric extends Metric implements api.Counter { - constructor( - name: string, - options: api.MetricOptions, - private readonly _processor: Processor, - resource: Resource, - instrumentationLibrary: InstrumentationLibrary - ) { - super(name, options, MetricKind.COUNTER, resource, instrumentationLibrary); - } - protected _makeInstrument(attributes: api.Attributes): BoundCounter { - return new BoundCounter( - attributes, - this._disabled, - this._valueType, - this._processor.aggregatorFor(this._descriptor) - ); - } - - /** - * Adds the given value to the current value. Values cannot be negative. - * @param value the value to add. - * @param [attributes = {}] key-values pairs that are associated with a specific metric - * that you want to record. - */ - add(value: number, attributes: api.Attributes = {}): void { - this.bind(attributes).add(value); - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/HistogramMetric.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/HistogramMetric.ts deleted file mode 100644 index 07c969b51d..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/HistogramMetric.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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-metrics'; -import { InstrumentationLibrary } from '@opentelemetry/core'; -import { Resource } from '@opentelemetry/resources'; -import { BoundHistogram } from './BoundInstrument'; -import { Processor } from './export/Processor'; -import { MetricKind } from './export/types'; -import { Metric } from './Metric'; - -/** This is a SDK implementation of Histogram Metric. */ -export class HistogramMetric - extends Metric - implements api.Histogram { - constructor( - name: string, - options: api.MetricOptions, - private readonly _processor: Processor, - resource: Resource, - instrumentationLibrary: InstrumentationLibrary - ) { - super( - name, - options, - MetricKind.HISTOGRAM, - resource, - instrumentationLibrary - ); - } - - protected _makeInstrument(attributes: api.Attributes): BoundHistogram { - return new BoundHistogram( - attributes, - this._disabled, - this._valueType, - this._processor.aggregatorFor(this._descriptor) - ); - } - - record(value: number, attributes: api.Attributes = {}): void { - this.bind(attributes).record(value); - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/Meter.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/Meter.ts deleted file mode 100644 index d4f3692451..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/Meter.ts +++ /dev/null @@ -1,332 +0,0 @@ -/* - * 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 { diag } from '@opentelemetry/api'; -import * as api from '@opentelemetry/api-metrics'; -import { InstrumentationLibrary } from '@opentelemetry/core'; -import { Resource } from '@opentelemetry/resources'; -import { BaseBoundInstrument } from './BoundInstrument'; -import { CounterMetric } from './CounterMetric'; -import { PushController } from './export/Controller'; -import { NoopExporter } from './export/NoopExporter'; -import { Processor, UngroupedProcessor } from './export/Processor'; -import { Metric } from './Metric'; -import { ObservableCounterMetric } from './ObservableCounterMetric'; -import { DEFAULT_CONFIG, DEFAULT_METRIC_OPTIONS, MeterConfig } from './types'; -import { UpDownCounterMetric } from './UpDownCounterMetric'; -import { ObservableUpDownCounterMetric } from './ObservableUpDownCounterMetric'; -import { ObservableGaugeMetric } from './ObservableGaugeMetric'; -import { HistogramMetric } from './HistogramMetric'; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const merge = require('lodash.merge'); -// @TODO - replace once the core is released -// import { merge } from '@opentelemetry/core'; - -/** - * Meter is an implementation of the {@link Meter} interface. - */ -export class Meter implements api.Meter { - private readonly _metrics = new Map>(); - private readonly _processor: Processor; - private readonly _resource: Resource; - private readonly _instrumentationLibrary: InstrumentationLibrary; - private readonly _controller: PushController; - private _isShutdown = false; - private _shuttingDownPromise: Promise = Promise.resolve(); - - /** - * Constructs a new Meter instance. - */ - constructor( - instrumentationLibrary: InstrumentationLibrary, - config: MeterConfig = {} - ) { - const mergedConfig = merge({}, DEFAULT_CONFIG, config); - this._processor = mergedConfig.processor ?? new UngroupedProcessor(); - this._resource = - mergedConfig.resource || Resource.empty(); - this._instrumentationLibrary = instrumentationLibrary; - // start the push controller - const exporter = mergedConfig.exporter || new NoopExporter(); - const interval = mergedConfig.interval; - this._controller = new PushController(this, exporter, interval); - } - - /** - * Creates and returns a new {@link Histogram}. - * @param name the name of the metric. - * @param [options] the metric options. - */ - createHistogram( - name: string, - options?: api.MetricOptions - ): api.Histogram { - if (!this._isValidName(name)) { - diag.warn( - `Invalid metric name ${name}. Defaulting to noop metric implementation.` - ); - return api.NOOP_HISTOGRAM_METRIC; - } - const opt: api.MetricOptions = { - ...DEFAULT_METRIC_OPTIONS, - ...options, - }; - - const histogram = new HistogramMetric( - name, - opt, - this._processor, - this._resource, - this._instrumentationLibrary - ); - this._registerMetric(name, histogram); - return histogram; - } - - /** - * Creates a new counter metric. Generally, this kind of metric when the - * value is a quantity, the sum is of primary interest, and the event count - * and value distribution are not of primary interest. - * @param name the name of the metric. - * @param [options] the metric options. - */ - createCounter(name: string, options?: api.MetricOptions): api.Counter { - if (!this._isValidName(name)) { - diag.warn( - `Invalid metric name ${name}. Defaulting to noop metric implementation.` - ); - return api.NOOP_COUNTER_METRIC; - } - const opt: api.MetricOptions = { - ...DEFAULT_METRIC_OPTIONS, - ...options, - }; - const counter = new CounterMetric( - name, - opt, - this._processor, - this._resource, - this._instrumentationLibrary - ); - this._registerMetric(name, counter); - return counter; - } - - /** - * Creates a new `UpDownCounter` metric. UpDownCounter is a synchronous - * instrument and very similar to Counter except that Add(increment) - * supports negative increments. It is generally useful for capturing changes - * in an amount of resources used, or any quantity that rises and falls - * during a request. - * - * @param name the name of the metric. - * @param [options] the metric options. - */ - createUpDownCounter( - name: string, - options?: api.MetricOptions - ): api.UpDownCounter { - if (!this._isValidName(name)) { - diag.warn( - `Invalid metric name ${name}. Defaulting to noop metric implementation.` - ); - return api.NOOP_COUNTER_METRIC; - } - const opt: api.MetricOptions = { - ...DEFAULT_METRIC_OPTIONS, - ...options, - }; - const upDownCounter = new UpDownCounterMetric( - name, - opt, - this._processor, - this._resource, - this._instrumentationLibrary - ); - this._registerMetric(name, upDownCounter); - return upDownCounter; - } - - /** - * Creates a new `ObservableGauge` metric. - * @param name the name of the metric. - * @param [options] the metric options. - * @param [callback] the observable gauge callback - */ - createObservableGauge( - name: string, - options: api.MetricOptions = {}, - callback?: (observableResult: api.ObservableResult) => unknown - ): api.ObservableGauge { - if (!this._isValidName(name)) { - diag.warn( - `Invalid metric name ${name}. Defaulting to noop metric implementation.` - ); - return api.NOOP_OBSERVABLE_GAUGE_METRIC; - } - const opt: api.MetricOptions = { - ...DEFAULT_METRIC_OPTIONS, - ...options, - }; - const observableGauge = new ObservableGaugeMetric( - name, - opt, - this._processor, - this._resource, - this._instrumentationLibrary, - callback - ); - this._registerMetric(name, observableGauge); - return observableGauge; - } - - createObservableCounter( - name: string, - options: api.MetricOptions = {}, - callback?: (observableResult: api.ObservableResult) => unknown - ): api.ObservableCounter { - if (!this._isValidName(name)) { - diag.warn( - `Invalid metric name ${name}. Defaulting to noop metric implementation.` - ); - return api.NOOP_OBSERVABLE_COUNTER_METRIC; - } - const opt: api.MetricOptions = { - ...DEFAULT_METRIC_OPTIONS, - ...options, - }; - const observableCounter = new ObservableCounterMetric( - name, - opt, - this._processor, - this._resource, - this._instrumentationLibrary, - callback - ); - this._registerMetric(name, observableCounter); - return observableCounter; - } - - /** - * Creates a new `ObservableUpDownCounter` metric. - * @param name the name of the metric. - * @param [options] the metric options. - * @param [callback] the observable gauge callback - */ - createObservableUpDownCounter( - name: string, - options: api.MetricOptions = {}, - callback?: (observableResult: api.ObservableResult) => unknown - ): api.ObservableUpDownCounter { - if (!this._isValidName(name)) { - diag.warn( - `Invalid metric name ${name}. Defaulting to noop metric implementation.` - ); - return api.NOOP_OBSERVABLE_UP_DOWN_COUNTER_METRIC; - } - const opt: api.MetricOptions = { - ...DEFAULT_METRIC_OPTIONS, - ...options, - }; - const observableUpDownCounter = new ObservableUpDownCounterMetric( - name, - opt, - this._processor, - this._resource, - this._instrumentationLibrary, - callback - ); - this._registerMetric(name, observableUpDownCounter); - return observableUpDownCounter; - } - - /** - * Collects all the metrics created with this `Meter` for export. - * - * Utilizes the processor to create checkpoints of the current values in - * each aggregator belonging to the metrics that were created with this - * meter instance. - */ - async collect(): Promise { - // after this all remaining metrics can be run - const metricsRecords = Array.from(this._metrics.values()).map(metric => { - return metric.getMetricRecord(); - }); - - await Promise.all(metricsRecords).then(records => { - records.forEach(metrics => { - metrics.forEach(metric => this._processor.process(metric)); - }); - }); - } - - getProcessor(): Processor { - return this._processor; - } - - shutdown(): Promise { - if (this._isShutdown) { - return this._shuttingDownPromise; - } - this._isShutdown = true; - - this._shuttingDownPromise = new Promise((resolve, reject) => { - Promise.resolve() - .then(() => { - return this._controller.shutdown(); - }) - .then(resolve) - .catch(e => { - reject(e); - }); - }); - return this._shuttingDownPromise; - } - - /** - * Registers metric to register. - * @param name The name of the metric. - * @param metric The metric to register. - */ - private _registerMetric( - name: string, - metric: Metric - ): void { - if (this._metrics.has(name)) { - diag.error(`A metric with the name ${name} has already been registered.`); - return; - } - this._metrics.set(name, metric); - } - - /** - * Ensure a metric name conforms to the following rules: - * - * 1. They are non-empty strings - * - * 2. The first character must be non-numeric, non-space, non-punctuation - * - * 3. Subsequent characters must be belong to the alphanumeric characters, - * '_', '.', and '-'. - * - * Names are case insensitive - * - * @param name Name of metric to be created - */ - private _isValidName(name: string): boolean { - return Boolean(name.match(/^[a-z][a-z0-9_.-]{0,62}$/i)); - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/MeterProvider.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/MeterProvider.ts deleted file mode 100644 index 3f4c6078e7..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/MeterProvider.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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-metrics'; -import { Resource } from '@opentelemetry/resources'; -import { Meter } from '.'; -import { DEFAULT_CONFIG, MeterConfig } from './types'; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const merge = require('lodash.merge'); -// @TODO - replace once the core is released -// import { merge } from '@opentelemetry/core'; - - -/** - * This class represents a meter provider which platform libraries can extend - */ -export class MeterProvider implements api.MeterProvider { - private readonly _config: MeterConfig; - private readonly _meters: Map = new Map(); - private _shuttingDownPromise: Promise = Promise.resolve(); - private _isShutdown = false; - readonly resource: Resource; - - constructor(config: MeterConfig = {}) { - const mergedConfig = merge({}, DEFAULT_CONFIG, config); - this.resource = mergedConfig.resource || Resource.empty(); - this.resource = Resource.default().merge(this.resource); - this._config = Object.assign({}, mergedConfig, { - resource: this.resource, - }); - } - - /** - * Returns a Meter, creating one if one with the given name and version is not already created - * - * @returns Meter A Meter with the given name and version - */ - getMeter(name: string, version?: string, options?: api.MeterOptions): Meter { - const key = `${name}@${version ?? ''}:${options?.schemaUrl ?? ''}`; - if (!this._meters.has(key)) { - this._meters.set( - key, - new Meter({ - name, - version, - schemaUrl: options?.schemaUrl - }, this._config) - ); - } - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return this._meters.get(key)!; - } - - shutdown(): Promise { - if (this._isShutdown) { - return this._shuttingDownPromise; - } - this._isShutdown = true; - - this._shuttingDownPromise = new Promise((resolve, reject) => { - Promise.resolve() - .then(() => { - return Promise.all( - Array.from(this._meters, ([_, meter]) => meter.shutdown()) - ); - }) - .then(() => { - if (this._config.exporter) { - return this._config.exporter.shutdown(); - } - return; - }) - .then(resolve) - .catch(e => { - reject(e); - }); - }); - return this._shuttingDownPromise; - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/Metric.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/Metric.ts deleted file mode 100644 index dd7ad028fa..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/Metric.ts +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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-metrics'; -import { InstrumentationLibrary } from '@opentelemetry/core'; -import { Resource } from '@opentelemetry/resources'; -import { BaseBoundInstrument } from './BoundInstrument'; -import { MetricDescriptor, MetricKind, MetricRecord } from './export/types'; -import { hashAttributes } from './Utils'; - -/** This is a SDK implementation of {@link Metric} interface. */ -export abstract class Metric { - protected readonly _disabled: boolean; - protected readonly _valueType: api.ValueType; - protected readonly _descriptor: MetricDescriptor; - protected readonly _boundaries: number[] | undefined; - protected readonly _aggregationTemporality: api.AggregationTemporality; - private readonly _instruments: Map = new Map(); - - constructor( - private readonly _name: string, - private readonly _options: api.MetricOptions, - private readonly _kind: MetricKind, - readonly resource: Resource, - readonly instrumentationLibrary: InstrumentationLibrary - ) { - this._disabled = !!_options.disabled; - this._valueType = - typeof _options.valueType === 'number' - ? _options.valueType - : api.ValueType.DOUBLE; - this._boundaries = _options.boundaries; - this._descriptor = this._getMetricDescriptor(); - this._aggregationTemporality = - _options.aggregationTemporality === undefined - ? api.AggregationTemporality.AGGREGATION_TEMPORALITY_CUMULATIVE - : _options.aggregationTemporality; - } - - /** - * Returns an Instrument associated with specified Attributes. - * It is recommended to keep a reference to the Instrument instead of always - * calling this method for each operation. - * @param attributes key-values pairs that are associated with a specific metric - * that you want to record. - */ - bind(attributes: api.Attributes): T { - const hash = hashAttributes(attributes); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - if (this._instruments.has(hash)) return this._instruments.get(hash)!; - - const instrument = this._makeInstrument(attributes); - this._instruments.set(hash, instrument); - return instrument; - } - - /** - * Removes the Instrument from the metric, if it is present. - * @param attributes key-values pairs that are associated with a specific metric. - */ - unbind(attributes: api.Attributes): void { - this._instruments.delete(hashAttributes(attributes)); - } - - /** - * Clears all Instruments from the Metric. - */ - clear(): void { - this._instruments.clear(); - } - - /** - * Returns kind of metric - */ - getKind(): MetricKind { - return this._kind; - } - - getAggregationTemporality(): api.AggregationTemporality { - return this._aggregationTemporality; - } - - getMetricRecord(): Promise { - return new Promise(resolve => { - resolve( - Array.from(this._instruments.values()).map(instrument => ({ - descriptor: this._descriptor, - attributes: instrument.getAttributes(), - aggregator: instrument.getAggregator(), - aggregationTemporality: this.getAggregationTemporality(), - resource: this.resource, - instrumentationLibrary: this.instrumentationLibrary, - })) - ); - }); - } - - private _getMetricDescriptor(): MetricDescriptor { - return { - name: this._name, - description: this._options.description || '', - unit: this._options.unit || '1', - metricKind: this._kind, - valueType: this._valueType, - ...(this._boundaries && { boundaries: this._boundaries }), - }; - } - - protected abstract _makeInstrument(attributes: api.Attributes): T; -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableBaseMetric.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableBaseMetric.ts deleted file mode 100644 index a5c24c9c0b..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableBaseMetric.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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-metrics'; -import { Observation } from '@opentelemetry/api-metrics'; -import { InstrumentationLibrary } from '@opentelemetry/core'; -import { Resource } from '@opentelemetry/resources'; -import { BoundObservable } from './BoundInstrument'; -import { Processor } from './export/Processor'; -import { MetricKind, MetricRecord } from './export/types'; -import { Metric } from './Metric'; -import { ObservableResult } from './ObservableResult'; - -const NOOP_CALLBACK = () => {}; - -/** - * This is a SDK implementation of Base Observer Metric. - * All observables should extend this class - */ -export abstract class ObservableBaseMetric - extends Metric - implements api.ObservableBase { - protected _callback: (observableResult: api.ObservableResult) => unknown; - - constructor( - name: string, - options: api.MetricOptions, - private readonly _processor: Processor, - resource: Resource, - metricKind: MetricKind, - instrumentationLibrary: InstrumentationLibrary, - callback?: (observableResult: api.ObservableResult) => unknown - ) { - super(name, options, metricKind, resource, instrumentationLibrary); - this._callback = callback || NOOP_CALLBACK; - } - - protected _makeInstrument(attributes: api.Attributes): BoundObservable { - return new BoundObservable( - attributes, - this._disabled, - this._valueType, - this._processor.aggregatorFor(this._descriptor) - ); - } - - override async getMetricRecord(): Promise { - const observableResult = new ObservableResult(); - await this._callback(observableResult); - - this._processResults(observableResult); - - return super.getMetricRecord(); - } - - protected _processResults(observableResult: ObservableResult): void { - observableResult.values.forEach((value, attributes) => { - const instrument = this.bind(attributes); - instrument.update(value); - }); - } - - observation(value: number): Observation { - return { - value, - observable: this as ObservableBaseMetric, - }; - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableCounterMetric.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableCounterMetric.ts deleted file mode 100644 index e4b79b5878..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableCounterMetric.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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-metrics'; -import { InstrumentationLibrary } from '@opentelemetry/core'; -import { Resource } from '@opentelemetry/resources'; -import { ObservableBaseMetric } from './ObservableBaseMetric'; -import { Processor } from './export/Processor'; -import { LastValue, MetricKind } from './export/types'; -import { ObservableResult } from './ObservableResult'; - -/** This is a SDK implementation of ObservableCounter Metric. */ -export class ObservableCounterMetric - extends ObservableBaseMetric - implements api.ObservableCounter { - constructor( - name: string, - options: api.MetricOptions, - processor: Processor, - resource: Resource, - instrumentationLibrary: InstrumentationLibrary, - callback?: (observableResult: api.ObservableResult) => unknown - ) { - super( - name, - options, - processor, - resource, - MetricKind.OBSERVABLE_COUNTER, - instrumentationLibrary, - callback - ); - } - - protected override _processResults(observableResult: ObservableResult): void { - observableResult.values.forEach((value, attributes) => { - const instrument = this.bind(attributes); - // ObservableCounter is monotonic which means it should only accept values - // greater or equal then previous value - const previous = instrument.getAggregator().toPoint(); - let previousValue = -Infinity; - if (previous.timestamp[0] !== 0 || previous.timestamp[1] !== 0) { - previousValue = previous.value as LastValue; - } - if (value >= previousValue) { - instrument.update(value); - } - }); - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableGaugeMetric.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableGaugeMetric.ts deleted file mode 100644 index 2d59e78f95..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableGaugeMetric.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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-metrics'; -import { InstrumentationLibrary } from '@opentelemetry/core'; -import { Resource } from '@opentelemetry/resources'; -import { ObservableBaseMetric } from './ObservableBaseMetric'; -import { Processor } from './export/Processor'; -import { MetricKind } from './export/types'; - -/** This is a SDK implementation of ObservableGauge Metric. */ -export class ObservableGaugeMetric - extends ObservableBaseMetric - implements api.ObservableGauge { - constructor( - name: string, - options: api.MetricOptions, - processor: Processor, - resource: Resource, - instrumentationLibrary: InstrumentationLibrary, - callback?: (observableResult: api.ObservableResult) => unknown - ) { - super( - name, - options, - processor, - resource, - MetricKind.OBSERVABLE_GAUGE, - instrumentationLibrary, - callback - ); - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableResult.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableResult.ts deleted file mode 100644 index 3ad7ee8a60..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableResult.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 { - ObservableResult as TypeObservableResult, - Attributes, -} from '@opentelemetry/api-metrics'; - -/** - * Implementation of {@link TypeObservableResult} - */ -export class ObservableResult implements TypeObservableResult { - values: Map = new Map(); - - observe(value: number, attributes: Attributes): void { - this.values.set(attributes, value); - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableUpDownCounterMetric.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableUpDownCounterMetric.ts deleted file mode 100644 index d9a767044a..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/ObservableUpDownCounterMetric.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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-metrics'; -import { InstrumentationLibrary } from '@opentelemetry/core'; -import { Resource } from '@opentelemetry/resources'; -import { ObservableBaseMetric } from './ObservableBaseMetric'; -import { Processor } from './export/Processor'; -import { MetricKind } from './export/types'; - -/** This is a SDK implementation of ObservableUpDownCounter Metric. */ -export class ObservableUpDownCounterMetric - extends ObservableBaseMetric - implements api.ObservableUpDownCounter { - constructor( - name: string, - options: api.MetricOptions, - processor: Processor, - resource: Resource, - instrumentationLibrary: InstrumentationLibrary, - callback?: (observableResult: api.ObservableResult) => unknown - ) { - super( - name, - options, - processor, - resource, - MetricKind.OBSERVABLE_UP_DOWN_COUNTER, - instrumentationLibrary, - callback - ); - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/UpDownCounterMetric.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/UpDownCounterMetric.ts deleted file mode 100644 index 6706ee01f7..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/UpDownCounterMetric.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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-metrics'; -import { Resource } from '@opentelemetry/resources'; -import { InstrumentationLibrary } from '@opentelemetry/core'; -import { BoundUpDownCounter } from './BoundInstrument'; -import { MetricKind } from './export/types'; -import { Processor } from './export/Processor'; -import { Metric } from './Metric'; - -/** This is a SDK implementation of UpDownCounter Metric. */ -export class UpDownCounterMetric - extends Metric - implements api.UpDownCounter { - constructor( - name: string, - options: api.MetricOptions, - private readonly _processor: Processor, - resource: Resource, - instrumentationLibrary: InstrumentationLibrary - ) { - super( - name, - options, - MetricKind.UP_DOWN_COUNTER, - resource, - instrumentationLibrary - ); - } - protected _makeInstrument(attributes: api.Attributes): BoundUpDownCounter { - return new BoundUpDownCounter( - attributes, - this._disabled, - this._valueType, - this._processor.aggregatorFor(this._descriptor) - ); - } - - /** - * Adds the given value to the current value. Values cannot be negative. - * @param value the value to add. - * @param [attributes = {}] key-values pairs that are associated with a specific - * metric that you want to record. - */ - add(value: number, attributes: api.Attributes = {}): void { - this.bind(attributes).add(value); - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/Utils.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/Utils.ts deleted file mode 100644 index 8de8687fe6..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/Utils.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 { Attributes } from '@opentelemetry/api-metrics'; - -/** - * Type guard to remove nulls from arrays - * - * @param value value to be checked for null equality - */ -export function notNull(value: T | null): value is T { - return value !== null; -} - -/** - * Converting the unordered attributes into unique identifier string. - * @param attributes user provided unordered Attributes. - */ -export function hashAttributes(attributes: Attributes): string { - let keys = Object.keys(attributes); - if (keys.length === 0) return ''; - - keys = keys.sort(); - return keys.reduce((result, key) => { - if (result.length > 2) { - result += ','; - } - return (result += key + ':' + attributes[key]); - }, '|#'); -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/ConsoleMetricExporter.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/ConsoleMetricExporter.ts deleted file mode 100644 index ccdcc8400d..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/ConsoleMetricExporter.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 { MetricExporter, MetricRecord, Histogram } from './types'; -import { ExportResult, ExportResultCode } from '@opentelemetry/core'; - -/** - * This is implementation of {@link MetricExporter} that prints metrics data to - * the console. This class can be used for diagnostic purposes. - */ - -/* eslint-disable no-console */ -export class ConsoleMetricExporter implements MetricExporter { - export( - metrics: MetricRecord[], - resultCallback: (result: ExportResult) => void - ): void { - for (const metric of metrics) { - console.log(metric.descriptor); - console.log(metric.attributes); - const point = metric.aggregator.toPoint(); - if (typeof point.value === 'number') { - console.log('value: ' + point.value); - } else if (typeof (point.value as Histogram).buckets === 'object') { - const histogram = point.value as Histogram; - console.log( - `count: ${histogram.count}, sum: ${histogram.sum}, buckets: ${histogram.buckets}` - ); - } else { - console.log(point.value); - } - } - return resultCallback({ code: ExportResultCode.SUCCESS }); - } - - shutdown(): Promise { - // By default does nothing - return Promise.resolve(); - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/Controller.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/Controller.ts deleted file mode 100644 index 7c0c82700c..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/Controller.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 { - ExportResultCode, - unrefTimer, - globalErrorHandler, -} from '@opentelemetry/core'; -import { Meter } from '../Meter'; -import { MetricExporter } from './types'; - -const DEFAULT_EXPORT_INTERVAL = 60_000; - -export class Controller {} - -/** Controller organizes a periodic push of metric data. */ -export class PushController extends Controller { - private _timer: NodeJS.Timeout; - - constructor( - private readonly _meter: Meter, - private readonly _exporter: MetricExporter, - interval: number = DEFAULT_EXPORT_INTERVAL - ) { - super(); - this._timer = setInterval(() => { - this._collect().catch(err => { - globalErrorHandler(err); - }); - }, interval); - unrefTimer(this._timer); - } - - shutdown(): Promise { - clearInterval(this._timer); - return this._collect(); - } - - private async _collect(): Promise { - await this._meter.collect(); - return new Promise(resolve => { - this._exporter.export( - this._meter.getProcessor().checkPointSet(), - result => { - if (result.code !== ExportResultCode.SUCCESS) { - globalErrorHandler( - result.error ?? - new Error('PushController: export failed in _collect') - ); - } - resolve(); - } - ); - }); - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/NoopExporter.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/NoopExporter.ts deleted file mode 100644 index 8e279833f2..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/NoopExporter.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 { MetricExporter, MetricRecord } from './types'; -import { ExportResult } from '@opentelemetry/core'; - -export class NoopExporter implements MetricExporter { - // By default does nothing - export( - _metrics: MetricRecord[], - _resultCallback: (result: ExportResult) => void - ): void {} - - // By default does nothing - shutdown(): Promise { - return Promise.resolve(); - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/Processor.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/Processor.ts deleted file mode 100644 index 519ffe99d4..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/Processor.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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 aggregators from './aggregators'; -import { - MetricRecord, - MetricKind, - Aggregator, - MetricDescriptor, -} from './types'; - -/** - * Base class for all processor types. - * - * The processor is responsible for storing the aggregators and aggregated - * values received from updates from metrics in the meter. The stored values - * will be sent to an exporter for exporting. - */ -export abstract class Processor { - protected readonly _batchMap = new Map(); - - /** Returns an aggregator based off metric descriptor. */ - abstract aggregatorFor(metricKind: MetricDescriptor): Aggregator; - - /** Stores record information to be ready for exporting. */ - abstract process(record: MetricRecord): void; - - checkPointSet(): MetricRecord[] { - return Array.from(this._batchMap.values()); - } -} - -/** - * Processor which retains all dimensions/attributes. It accepts all records and - * passes them for exporting. - */ -export class UngroupedProcessor extends Processor { - aggregatorFor(metricDescriptor: MetricDescriptor): Aggregator { - switch (metricDescriptor.metricKind) { - case MetricKind.COUNTER: - case MetricKind.UP_DOWN_COUNTER: - return new aggregators.SumAggregator(); - - case MetricKind.OBSERVABLE_COUNTER: - case MetricKind.OBSERVABLE_UP_DOWN_COUNTER: - case MetricKind.OBSERVABLE_GAUGE: - return new aggregators.LastValueAggregator(); - - case MetricKind.HISTOGRAM: - return new aggregators.HistogramAggregator( - metricDescriptor.boundaries || [Infinity] - ); - - default: - return new aggregators.LastValueAggregator(); - } - } - - process(record: MetricRecord): void { - const attributes = Object.keys(record.attributes) - .map(k => `${k}=${record.attributes[k]}`) - .join(','); - this._batchMap.set(record.descriptor.name + attributes, record); - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/aggregators/Histogram.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/aggregators/Histogram.ts deleted file mode 100644 index b877db33bd..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/aggregators/Histogram.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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 { - HistogramAggregatorType, - Point, - Histogram, - AggregatorKind, -} from '../types'; -import { HrTime } from '@opentelemetry/api'; -import { hrTime } from '@opentelemetry/core'; - -/** - * Basic aggregator which observes events and counts them in pre-defined buckets - * and provides the total sum and count of all observations. - */ -export class HistogramAggregator implements HistogramAggregatorType { - public kind: AggregatorKind.HISTOGRAM = AggregatorKind.HISTOGRAM; - private _current: Histogram; - private _lastUpdateTime: HrTime; - private readonly _boundaries: number[]; - - constructor(boundaries: number[]) { - if (boundaries === undefined || boundaries.length === 0) { - throw new Error('HistogramAggregator should be created with boundaries.'); - } - // we need to an ordered set to be able to correctly compute count for each - // boundary since we'll iterate on each in order. - this._boundaries = boundaries.sort((a, b) => a - b); - this._current = this._newEmptyCheckpoint(); - this._lastUpdateTime = hrTime(); - } - - update(value: number): void { - this._lastUpdateTime = hrTime(); - this._current.count += 1; - this._current.sum += value; - - for (let i = 0; i < this._boundaries.length; i++) { - if (value < this._boundaries[i]) { - this._current.buckets.counts[i] += 1; - return; - } - } - // value is above all observed boundaries - this._current.buckets.counts[this._boundaries.length] += 1; - } - - toPoint(): Point { - return { - value: this._current, - timestamp: this._lastUpdateTime, - }; - } - - private _newEmptyCheckpoint(): Histogram { - return { - buckets: { - boundaries: this._boundaries, - counts: this._boundaries.map(() => 0).concat([0]), - }, - sum: 0, - count: 0, - }; - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/aggregators/LastValue.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/aggregators/LastValue.ts deleted file mode 100644 index b2462f497a..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/aggregators/LastValue.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 { - AggregatorKind, - LastValue, - LastValueAggregatorType, - Point, -} from '../types'; -import { HrTime } from '@opentelemetry/api'; -import { hrTime } from '@opentelemetry/core'; - -/** Basic aggregator for LastValue which keeps the last recorded value. */ -export class LastValueAggregator implements LastValueAggregatorType { - private _current: number = 0; - private _lastUpdateTime: HrTime = [0, 0]; - kind: AggregatorKind.LAST_VALUE = AggregatorKind.LAST_VALUE; - - update(value: number): void { - this._current = value; - this._lastUpdateTime = hrTime(); - } - - toPoint(): Point { - return { - value: this._current, - timestamp: this._lastUpdateTime, - }; - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/aggregators/Sum.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/aggregators/Sum.ts deleted file mode 100644 index 3a33e920ba..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/aggregators/Sum.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 { Point, Sum, AggregatorKind, SumAggregatorType } from '../types'; -import { HrTime } from '@opentelemetry/api'; -import { hrTime } from '@opentelemetry/core'; - -/** Basic aggregator which calculates a Sum from individual measurements. */ -export class SumAggregator implements SumAggregatorType { - public kind: AggregatorKind.SUM = AggregatorKind.SUM; - private _current: number = 0; - private _lastUpdateTime: HrTime = [0, 0]; - - update(value: number): void { - this._current += value; - this._lastUpdateTime = hrTime(); - } - - toPoint(): Point { - return { - value: this._current, - timestamp: this._lastUpdateTime, - }; - } -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/types.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/export/types.ts deleted file mode 100644 index 0f64af0901..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/types.ts +++ /dev/null @@ -1,165 +0,0 @@ -/* - * 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 { HrTime } from '@opentelemetry/api'; -import { - Attributes, - AggregationTemporality, - ValueType, -} from '@opentelemetry/api-metrics'; -import { ExportResult, InstrumentationLibrary } from '@opentelemetry/core'; -import { Resource } from '@opentelemetry/resources'; - -/** The kind of metric. */ -export enum MetricKind { - COUNTER, - UP_DOWN_COUNTER, - HISTOGRAM, - OBSERVABLE_COUNTER, - OBSERVABLE_UP_DOWN_COUNTER, - OBSERVABLE_GAUGE, -} - -export const MetricKindValues = Object.values(MetricKind); - -/** The kind of aggregator. */ -export enum AggregatorKind { - SUM, - LAST_VALUE, - HISTOGRAM, -} - -/** Sum returns an aggregated sum. */ -export type Sum = number; - -/** LastValue returns last value. */ -export type LastValue = number; - -export interface Histogram { - /** - * Buckets are implemented using two different arrays: - * - boundaries: contains every finite bucket boundary, which are inclusive lower bounds - * - counts: contains event counts for each bucket - * - * Note that we'll always have n+1 buckets, where n is the number of boundaries. - * This is because we need to count events that are below the lowest boundary. - * - * Example: if we measure the values: [5, 30, 5, 40, 5, 15, 15, 15, 25] - * with the boundaries [ 10, 20, 30 ], we will have the following state: - * - * buckets: { - * boundaries: [10, 20, 30], - * counts: [3, 3, 1, 2], - * } - */ - buckets: { - boundaries: number[]; - counts: number[]; - }; - sum: number; - count: number; -} - -export type PointValueType = Sum | LastValue | Histogram; - -export interface MetricRecord { - readonly descriptor: MetricDescriptor; - readonly attributes: Attributes; - readonly aggregator: Aggregator; - readonly aggregationTemporality: AggregationTemporality; - readonly resource: Resource; - readonly instrumentationLibrary: InstrumentationLibrary; -} - -export interface MetricDescriptor { - readonly name: string; - readonly description: string; - readonly unit: string; - readonly metricKind: MetricKind; - readonly valueType: ValueType; - readonly boundaries?: number[]; -} - -/** - * Base interface that represents a metric exporter - */ -export interface MetricExporter { - /** Exports the list of a given {@link MetricRecord} */ - export( - metrics: MetricRecord[], - resultCallback: (result: ExportResult) => void - ): void; - - /** Stops the exporter. */ - shutdown(): Promise; -} - -/** - * Base interface for aggregators. Aggregators are responsible for holding - * aggregated values and taking a snapshot of these values upon export. - * - * Use {@link Aggregator} instead of this BaseAggregator. - */ -interface BaseAggregator { - /** The kind of the aggregator. */ - kind: AggregatorKind; - - /** Updates the current with the new value. */ - update(value: number): void; -} - -/** SumAggregatorType aggregate values into a {@link Sum} point type. */ -export interface SumAggregatorType extends BaseAggregator { - kind: AggregatorKind.SUM; - - /** Returns snapshot of the current point (value with timestamp). */ - toPoint(): Point; -} - -/** - * LastValueAggregatorType aggregate values into a {@link LastValue} point - * type. - */ -export interface LastValueAggregatorType extends BaseAggregator { - kind: AggregatorKind.LAST_VALUE; - - /** Returns snapshot of the current point (value with timestamp). */ - toPoint(): Point; -} - -/** - * HistogramAggregatorType aggregate values into a {@link Histogram} point - * type. - */ -export interface HistogramAggregatorType extends BaseAggregator { - kind: AggregatorKind.HISTOGRAM; - - /** Returns snapshot of the current point (value with timestamp). */ - toPoint(): Point; -} - -export type Aggregator = - | SumAggregatorType - | LastValueAggregatorType - | HistogramAggregatorType; - -/** - * Point represents a snapshot of aggregated values of aggregators. - */ -export interface Point { - value: T; - timestamp: HrTime; -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/index.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/index.ts index a3d74d5352..f4fa9c6472 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/index.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/src/index.ts @@ -14,16 +14,4 @@ * limitations under the License. */ -export * from './BoundInstrument'; -export * from './CounterMetric'; -export * from './HistogramMetric'; -export * from './Meter'; -export * from './MeterProvider'; -export * from './Metric'; -export * from './ObservableGaugeMetric'; -export * from './export/aggregators'; -export * from './export/ConsoleMetricExporter'; -export * from './export/Processor'; -export * from './export/types'; -export * from './UpDownCounterMetric'; -export { MeterConfig } from './types'; +export * from './version'; diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/types.ts b/experimental/packages/opentelemetry-sdk-metrics-base/src/types.ts deleted file mode 100644 index 338d5805ae..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/types.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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-metrics'; -import { Resource } from '@opentelemetry/resources'; -import { Processor } from './export/Processor'; -import { MetricExporter } from './export/types'; - -/** MeterConfig provides an interface for configuring a Meter. */ -export interface MeterConfig extends api.MeterOptions { - /** Metric exporter. */ - exporter?: MetricExporter; - - /** Metric collect interval */ - interval?: number; - - /** Resource associated with metric telemetry */ - resource?: Resource; - - /** Metric Processor. */ - processor?: Processor; -} - -/** Default Meter configuration. */ -export const DEFAULT_CONFIG = {}; - -/** The default metric creation options value. */ -export const DEFAULT_METRIC_OPTIONS = { - disabled: false, - description: '', - unit: '1', - valueType: api.ValueType.DOUBLE, -}; diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/Meter.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/Meter.test.ts deleted file mode 100644 index 7d227177dc..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/Meter.test.ts +++ /dev/null @@ -1,1212 +0,0 @@ -/* - * 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 { diag } from '@opentelemetry/api'; -import * as api from '@opentelemetry/api-metrics'; -import { hrTime, hrTimeToNanoseconds } from '@opentelemetry/core'; -import { Resource } from '@opentelemetry/resources'; -import * as assert from 'assert'; -import * as sinon from 'sinon'; -import { - CounterMetric, - Histogram, - LastValue, - LastValueAggregator, - Meter, - MeterProvider, - Metric, - MetricKind, - MetricRecord, - Sum, - UpDownCounterMetric, - ObservableGaugeMetric, - HistogramMetric, -} from '../src'; -import { SumAggregator } from '../src/export/aggregators'; -import { ObservableCounterMetric } from '../src/ObservableCounterMetric'; -import { ObservableUpDownCounterMetric } from '../src/ObservableUpDownCounterMetric'; -import { hashAttributes } from '../src/Utils'; - -const nonNumberValues = [ - // type undefined - undefined, - // type null - null, - // type function - function () {}, - // type boolean - true, - false, - // type string - '1', - // type object - {}, - // type symbol - // symbols cannot be cast to number, early errors will be thrown. -]; - -if (Number(process.versions.node.match(/^\d+/)) >= 10) { - nonNumberValues.push( - // type bigint - // Preferring BigInt builtin object instead of bigint literal to keep Node.js v8.x working. - // TODO: should metric instruments support bigint? - BigInt(1) // eslint-disable-line node/no-unsupported-features/es-builtins - ); -} - -describe('Meter', () => { - let meter: Meter; - const keya = 'keya'; - const keyb = 'keyb'; - const attributes: api.Attributes = { [keyb]: 'value2', [keya]: 'value1' }; - - beforeEach(() => { - meter = new MeterProvider().getMeter('test-meter'); - }); - - afterEach(() => { - sinon.restore(); - }); - - describe('#counter', () => { - const performanceTimeOrigin = hrTime(); - - it('should create a counter', () => { - const counter = meter.createCounter('name'); - assert.ok(counter instanceof Metric); - }); - - it('should create a counter with options', () => { - const counter = meter.createCounter('name', { - description: 'desc', - unit: '1', - disabled: false, - }); - assert.ok(counter instanceof Metric); - }); - - it('should be able to call add() directly on counter', async () => { - const counter = meter.createCounter('name') as CounterMetric; - counter.add(10, attributes); - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - - assert.strictEqual(record1.aggregator.toPoint().value, 10); - const lastTimestamp = record1.aggregator.toPoint().timestamp; - assert.ok( - hrTimeToNanoseconds(lastTimestamp) > - hrTimeToNanoseconds(performanceTimeOrigin) - ); - counter.add(10, attributes); - assert.strictEqual(record1.aggregator.toPoint().value, 20); - - assert.ok( - hrTimeToNanoseconds(record1.aggregator.toPoint().timestamp) > - hrTimeToNanoseconds(lastTimestamp) - ); - }); - - it('should be able to call add with no attributes', async () => { - const counter = meter.createCounter('name', { - description: 'desc', - unit: '1', - disabled: false, - }); - counter.add(1); - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - assert.strictEqual(record1.aggregator.toPoint().value, 1); - }); - - it('should pipe through resource', async () => { - const counter = meter.createCounter('name') as CounterMetric; - assert.ok(counter.resource instanceof Resource); - - counter.add(1, { foo: 'bar' }); - - const [record] = await counter.getMetricRecord(); - assert.ok(record.resource instanceof Resource); - }); - - it('should pipe through instrumentation library', async () => { - const counter = meter.createCounter('name') as CounterMetric; - assert.ok(counter.instrumentationLibrary); - - counter.add(1, { foo: 'bar' }); - - const [record] = await counter.getMetricRecord(); - const { name, version } = record.instrumentationLibrary; - assert.strictEqual(name, 'test-meter'); - assert.strictEqual(version, undefined); - }); - - describe('.bind()', () => { - it('should create a counter instrument', async () => { - const counter = meter.createCounter('name') as CounterMetric; - const boundCounter = counter.bind(attributes); - boundCounter.add(10); - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - - assert.strictEqual(record1.aggregator.toPoint().value, 10); - boundCounter.add(10); - assert.strictEqual(record1.aggregator.toPoint().value, 20); - }); - - it('should return the aggregator', () => { - const counter = meter.createCounter('name') as CounterMetric; - const boundCounter = counter.bind(attributes); - boundCounter.add(20); - assert.ok(boundCounter.getAggregator() instanceof SumAggregator); - assert.strictEqual(boundCounter.getAttributes(), attributes); - }); - - it('should add positive values only', async () => { - const counter = meter.createCounter('name') as CounterMetric; - const boundCounter = counter.bind(attributes); - boundCounter.add(10); - assert.strictEqual(meter.getProcessor().checkPointSet().length, 0); - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - - assert.strictEqual(record1.aggregator.toPoint().value, 10); - boundCounter.add(-100); - assert.strictEqual(record1.aggregator.toPoint().value, 10); - }); - - it('should not add the instrument data when disabled', async () => { - const counter = meter.createCounter('name', { - disabled: true, - }) as CounterMetric; - const boundCounter = counter.bind(attributes); - boundCounter.add(10); - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - assert.strictEqual(record1.aggregator.toPoint().value, 0); - }); - - it('should return same instrument on same attribute values', async () => { - const counter = meter.createCounter('name') as CounterMetric; - const boundCounter = counter.bind(attributes); - boundCounter.add(10); - const boundCounter1 = counter.bind(attributes); - boundCounter1.add(10); - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - - assert.strictEqual(record1.aggregator.toPoint().value, 20); - assert.strictEqual(boundCounter, boundCounter1); - }); - }); - - describe('.unbind()', () => { - it('should remove a counter instrument', () => { - const counter = meter.createCounter('name') as CounterMetric; - const boundCounter = counter.bind(attributes); - assert.strictEqual(counter['_instruments'].size, 1); - counter.unbind(attributes); - assert.strictEqual(counter['_instruments'].size, 0); - const boundCounter1 = counter.bind(attributes); - assert.strictEqual(counter['_instruments'].size, 1); - assert.notStrictEqual(boundCounter, boundCounter1); - }); - - it('should not fail when removing non existing instrument', () => { - const counter = meter.createCounter('name') as CounterMetric; - counter.unbind({}); - }); - - it('should clear all instruments', () => { - const counter = meter.createCounter('name') as CounterMetric; - counter.bind(attributes); - assert.strictEqual(counter['_instruments'].size, 1); - counter.clear(); - assert.strictEqual(counter['_instruments'].size, 0); - }); - }); - - describe('.registerMetric()', () => { - it('skip already registered Metric', async () => { - const counter1 = meter.createCounter('name1') as CounterMetric; - counter1.bind(attributes).add(10); - - // should skip below metric - const counter2 = meter.createCounter('name1', { - valueType: api.ValueType.INT, - }) as CounterMetric; - counter2.bind(attributes).add(500); - - await meter.collect(); - const record = meter.getProcessor().checkPointSet(); - - assert.strictEqual(record.length, 1); - assert.deepStrictEqual(record[0].descriptor, { - description: '', - metricKind: MetricKind.COUNTER, - name: 'name1', - unit: '1', - valueType: api.ValueType.DOUBLE, - }); - assert.strictEqual(record[0].aggregator.toPoint().value, 10); - }); - }); - - describe('names', () => { - it('should create counter with valid names', () => { - const counter1 = meter.createCounter('name1'); - const counter2 = meter.createCounter( - 'Name_with-all.valid_CharacterClasses' - ); - assert.ok(counter1 instanceof CounterMetric); - assert.ok(counter2 instanceof CounterMetric); - }); - - it('should return no op metric if name is an empty string', () => { - const counter = meter.createCounter(''); - assert.ok(counter instanceof api.NoopMetric); - }); - - it('should return no op metric if name does not start with a letter', () => { - const counter1 = meter.createCounter('1name'); - const counter_ = meter.createCounter('_name'); - assert.ok(counter1 instanceof api.NoopMetric); - assert.ok(counter_ instanceof api.NoopMetric); - }); - - it('should return no op metric if name is an empty string contain only letters, numbers, ".", "_", and "-"', () => { - const counter = meter.createCounter('name with invalid characters^&*('); - assert.ok(counter instanceof api.NoopMetric); - }); - - it('should return no op metric if name exceeded length of 63', () => { - const counter = meter.createCounter('a'.repeat(63)); - assert.ok(counter instanceof CounterMetric); - const counter2 = meter.createCounter('a'.repeat(64)); - assert.ok(counter2 instanceof api.NoopMetric); - }); - }); - }); - - describe('#UpDownCounter', () => { - const performanceTimeOrigin = hrTime(); - - it('should create a UpDownCounter', () => { - const upDownCounter = meter.createUpDownCounter('name'); - assert.ok(upDownCounter instanceof Metric); - }); - - it('should create a UpDownCounter with options', () => { - const upDownCounter = meter.createUpDownCounter('name', { - description: 'desc', - unit: '1', - disabled: false, - }); - assert.ok(upDownCounter instanceof Metric); - }); - - it('should be able to call add() directly on UpDownCounter', async () => { - const upDownCounter = meter.createUpDownCounter('name'); - upDownCounter.add(10, attributes); - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - - assert.strictEqual(record1.aggregator.toPoint().value, 10); - const lastTimestamp = record1.aggregator.toPoint().timestamp; - assert.ok( - hrTimeToNanoseconds(lastTimestamp) > - hrTimeToNanoseconds(performanceTimeOrigin) - ); - upDownCounter.add(10, attributes); - assert.strictEqual(record1.aggregator.toPoint().value, 20); - - assert.ok( - hrTimeToNanoseconds(record1.aggregator.toPoint().timestamp) > - hrTimeToNanoseconds(lastTimestamp) - ); - }); - - it('should be able to call add with no attributes', async () => { - const upDownCounter = meter.createUpDownCounter('name', { - description: 'desc', - unit: '1', - disabled: false, - }); - upDownCounter.add(1); - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - assert.strictEqual(record1.aggregator.toPoint().value, 1); - }); - - it('should pipe through resource', async () => { - const upDownCounter = meter.createUpDownCounter( - 'name' - ) as UpDownCounterMetric; - assert.ok(upDownCounter.resource instanceof Resource); - - upDownCounter.add(1, { foo: 'bar' }); - - const [record] = await upDownCounter.getMetricRecord(); - assert.ok(record.resource instanceof Resource); - }); - - describe('.bind()', () => { - it('should create a UpDownCounter instrument', async () => { - const upDownCounter = meter.createUpDownCounter('name') as UpDownCounterMetric; - const boundCounter = upDownCounter.bind(attributes); - boundCounter.add(10); - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - - assert.strictEqual(record1.aggregator.toPoint().value, 10); - boundCounter.add(-200); - assert.strictEqual(record1.aggregator.toPoint().value, -190); - }); - - it('should return the aggregator', () => { - const upDownCounter = meter.createUpDownCounter( - 'name' - ) as UpDownCounterMetric; - const boundCounter = upDownCounter.bind(attributes); - boundCounter.add(20); - assert.ok(boundCounter.getAggregator() instanceof SumAggregator); - assert.strictEqual(boundCounter.getAttributes(), attributes); - }); - - it('should not add the instrument data when disabled', async () => { - const upDownCounter = meter.createUpDownCounter('name', { - disabled: true, - }) as UpDownCounterMetric; - const boundCounter = upDownCounter.bind(attributes); - boundCounter.add(10); - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - assert.strictEqual(record1.aggregator.toPoint().value, 0); - }); - - it('should return same instrument on same attribute values', async () => { - const upDownCounter = meter.createUpDownCounter('name') as UpDownCounterMetric; - const boundCounter = upDownCounter.bind(attributes); - boundCounter.add(10); - const boundCounter1 = upDownCounter.bind(attributes); - boundCounter1.add(10); - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - - assert.strictEqual(record1.aggregator.toPoint().value, 20); - assert.strictEqual(boundCounter, boundCounter1); - }); - - it('should truncate non-integer values for INT valueType', async () => { - const upDownCounter = meter.createUpDownCounter('name', { - valueType: api.ValueType.INT, - }) as UpDownCounterMetric; - const boundCounter = upDownCounter.bind(attributes); - - [-1.1, 2.2].forEach(val => { - boundCounter.add(val); - }); - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - assert.strictEqual(record1.aggregator.toPoint().value, 1); - }); - - it('should ignore non-number values for INT valueType', async () => { - const upDownCounter = meter.createUpDownCounter('name', { - valueType: api.ValueType.DOUBLE, - }) as UpDownCounterMetric; - const boundCounter = upDownCounter.bind(attributes); - - await Promise.all( - nonNumberValues.map(async val => { - // @ts-expect-error verify non number types - boundCounter.add(val); - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - - assert.strictEqual(record1.aggregator.toPoint().value, 0); - }) - ); - }); - - it('should ignore non-number values for DOUBLE valueType', async () => { - const upDownCounter = meter.createUpDownCounter('name', { - valueType: api.ValueType.DOUBLE, - }) as UpDownCounterMetric; - const boundCounter = upDownCounter.bind(attributes); - - await Promise.all( - nonNumberValues.map(async val => { - // @ts-expect-error verify non number types - boundCounter.add(val); - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - - assert.strictEqual(record1.aggregator.toPoint().value, 0); - }) - ); - }); - }); - - describe('.unbind()', () => { - it('should remove a UpDownCounter instrument', () => { - const upDownCounter = meter.createUpDownCounter( - 'name' - ) as UpDownCounterMetric; - const boundCounter = upDownCounter.bind(attributes); - assert.strictEqual(upDownCounter['_instruments'].size, 1); - upDownCounter.unbind(attributes); - assert.strictEqual(upDownCounter['_instruments'].size, 0); - const boundCounter1 = upDownCounter.bind(attributes); - assert.strictEqual(upDownCounter['_instruments'].size, 1); - assert.notStrictEqual(boundCounter, boundCounter1); - }); - - it('should not fail when removing non existing instrument', () => { - const upDownCounter = meter.createUpDownCounter('name') as UpDownCounterMetric; - upDownCounter.unbind({}); - }); - - it('should clear all instruments', () => { - const upDownCounter = meter.createUpDownCounter( - 'name' - ) as CounterMetric; - upDownCounter.bind(attributes); - assert.strictEqual(upDownCounter['_instruments'].size, 1); - upDownCounter.clear(); - assert.strictEqual(upDownCounter['_instruments'].size, 0); - }); - }); - - describe('.registerMetric()', () => { - it('skip already registered Metric', async () => { - const counter1 = meter.createCounter('name1') as CounterMetric; - counter1.bind(attributes).add(10); - - // should skip below metric - const counter2 = meter.createCounter('name1', { - valueType: api.ValueType.INT, - }) as CounterMetric; - counter2.bind(attributes).add(500); - - await meter.collect(); - const record = meter.getProcessor().checkPointSet(); - - assert.strictEqual(record.length, 1); - assert.deepStrictEqual(record[0].descriptor, { - description: '', - metricKind: MetricKind.COUNTER, - name: 'name1', - unit: '1', - valueType: api.ValueType.DOUBLE, - }); - assert.strictEqual(record[0].aggregator.toPoint().value, 10); - }); - }); - - describe('names', () => { - it('should create counter with valid names', () => { - const counter1 = meter.createCounter('name1'); - const counter2 = meter.createCounter( - 'Name_with-all.valid_CharacterClasses' - ); - assert.ok(counter1 instanceof CounterMetric); - assert.ok(counter2 instanceof CounterMetric); - }); - - it('should return no op metric if name is an empty string', () => { - const counter = meter.createCounter(''); - assert.ok(counter instanceof api.NoopMetric); - }); - - it('should return no op metric if name does not start with a letter', () => { - const counter1 = meter.createCounter('1name'); - const counter_ = meter.createCounter('_name'); - assert.ok(counter1 instanceof api.NoopMetric); - assert.ok(counter_ instanceof api.NoopMetric); - }); - - it('should return no op metric if name is an empty string contain only letters, numbers, ".", "_", and "-"', () => { - const counter = meter.createCounter('name with invalid characters^&*('); - assert.ok(counter instanceof api.NoopMetric); - }); - }); - }); - - describe('#Histogram', () => { - it('should create a histogram', () => { - const histogram = meter.createHistogram('name'); - assert.ok(histogram instanceof Metric); - }); - - it('should create a histogram with options', () => { - const histogram = meter.createHistogram('name', { - description: 'desc', - unit: '1', - disabled: false, - }); - assert.ok(histogram instanceof Metric); - }); - - it('should set histogram boundaries for histogram', async () => { - const histogram = meter.createHistogram('name', { - description: 'desc', - unit: '1', - disabled: false, - boundaries: [10, 20, 30, 100], - }) as HistogramMetric; - - histogram.record(10); - histogram.record(30); - histogram.record(50); - histogram.record(200); - - await meter.collect(); - const [record] = meter.getProcessor().checkPointSet(); - assert.deepStrictEqual(record.aggregator.toPoint().value as Histogram, { - buckets: { - boundaries: [10, 20, 30, 100], - counts: [0, 1, 0, 2, 1], - }, - count: 4, - sum: 290, - }); - - assert.ok(histogram instanceof Metric); - }); - - it('should pipe through resource', async () => { - const histogram = meter.createHistogram( - 'name' - ) as HistogramMetric; - assert.ok(histogram.resource instanceof Resource); - - histogram.record(1, { foo: 'bar' }); - - const [record] = await histogram.getMetricRecord(); - assert.ok(record.resource instanceof Resource); - }); - - it('should pipe through instrumentation library', async () => { - const histogram = meter.createHistogram( - 'name' - ) as HistogramMetric; - assert.ok(histogram.instrumentationLibrary); - - histogram.record(1, { foo: 'bar' }); - - const [record] = await histogram.getMetricRecord(); - const { name, version } = record.instrumentationLibrary; - assert.strictEqual(name, 'test-meter'); - assert.strictEqual(version, undefined); - }); - - describe('names', () => { - it('should return no op metric if name is an empty string', () => { - const histogram = meter.createHistogram(''); - assert.ok(histogram instanceof api.NoopMetric); - }); - - it('should return no op metric if name does not start with a letter', () => { - const histogram1 = meter.createHistogram('1name'); - const histogram_ = meter.createHistogram('_name'); - assert.ok(histogram1 instanceof api.NoopMetric); - assert.ok(histogram_ instanceof api.NoopMetric); - }); - - it('should return no op metric if name is an empty string contain only letters, numbers, ".", "_", and "-"', () => { - const histogram = meter.createHistogram( - 'name with invalid characters^&*(' - ); - assert.ok(histogram instanceof api.NoopMetric); - }); - }); - - describe('.bind()', () => { - const performanceTimeOrigin = hrTime(); - - it('should create a histogram instrument', () => { - const histogram = meter.createHistogram( - 'name' - ) as HistogramMetric; - const boundHistogram = histogram.bind(attributes); - assert.doesNotThrow(() => boundHistogram.record(10)); - }); - - it('should not set the instrument data when disabled', async () => { - const histogram = meter.createHistogram('name', { - disabled: true, - }) as HistogramMetric; - const boundHistogram = histogram.bind(attributes); - boundHistogram.record(10); - - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - assert.deepStrictEqual( - record1.aggregator.toPoint().value as Histogram, - { - buckets: { - boundaries: [Infinity], - counts: [0, 0], - }, - count: 0, - sum: 0, - } - ); - }); - - it('should accept negative (and positive) values', async () => { - const histogram = meter.createHistogram('name') as HistogramMetric; - const boundHistogram = histogram.bind(attributes); - boundHistogram.record(-10); - boundHistogram.record(50); - - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - assert.deepStrictEqual( - record1.aggregator.toPoint().value as Histogram, - { - buckets: { - boundaries: [Infinity], - counts: [2, 0], - }, - count: 2, - sum: 40, - } - ); - assert.ok( - hrTimeToNanoseconds(record1.aggregator.toPoint().timestamp) > - hrTimeToNanoseconds(performanceTimeOrigin) - ); - }); - - it('should return same instrument on same attribute values', async () => { - const histogram = meter.createHistogram( - 'name' - ) as HistogramMetric; - const boundHistogram1 = histogram.bind(attributes); - boundHistogram1.record(10); - const boundHistogram2 = histogram.bind(attributes); - boundHistogram2.record(100); - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - assert.deepStrictEqual( - record1.aggregator.toPoint().value as Histogram, - { - buckets: { - boundaries: [Infinity], - counts: [2, 0], - }, - count: 2, - sum: 110, - } - ); - assert.strictEqual(boundHistogram1, boundHistogram2); - }); - - it('should ignore non-number values', async () => { - const histogram = meter.createHistogram( - 'name' - ) as HistogramMetric; - const boundHistogram = histogram.bind(attributes); - - await Promise.all( - nonNumberValues.map(async val => { - // @ts-expect-error verify non number types - boundHistogram.record(val); - await meter.collect(); - const [record1] = meter.getProcessor().checkPointSet(); - assert.deepStrictEqual( - record1.aggregator.toPoint().value as Histogram, - { - buckets: { - boundaries: [Infinity], - counts: [0, 0], - }, - count: 0, - sum: 0, - } - ); - }) - ); - }); - }); - - describe('.unbind()', () => { - it('should remove the histogram instrument', () => { - const histogram = meter.createHistogram( - 'name' - ) as HistogramMetric; - const boundHistogram = histogram.bind(attributes); - assert.strictEqual(histogram['_instruments'].size, 1); - histogram.unbind(attributes); - assert.strictEqual(histogram['_instruments'].size, 0); - const boundHistogram2 = histogram.bind(attributes); - assert.strictEqual(histogram['_instruments'].size, 1); - assert.notStrictEqual(boundHistogram, boundHistogram2); - }); - - it('should not fail when removing non existing instrument', () => { - const histogram = meter.createHistogram('name') as HistogramMetric; - histogram.unbind({}); - }); - - it('should clear all instruments', () => { - const histogram = meter.createHistogram( - 'name' - ) as HistogramMetric; - histogram.bind(attributes); - assert.strictEqual(histogram['_instruments'].size, 1); - histogram.clear(); - assert.strictEqual(histogram['_instruments'].size, 0); - }); - }); - }); - - describe('#ObservableCounterMetric', () => { - it('should create an ObservableCounter', () => { - const observableCounter = meter.createObservableCounter('name') as ObservableCounterMetric; - assert.ok(observableCounter instanceof Metric); - }); - - it('should return noop observable counter when name is invalid', () => { - // Need to stub/spy on the underlying logger as the "diag" instance is global - const spy = sinon.stub(diag, 'warn'); - const observableCounter = meter.createObservableCounter('na me'); - assert.ok(observableCounter === api.NOOP_OBSERVABLE_COUNTER_METRIC); - const args = spy.args[0]; - assert.ok( - args[0], - 'Invalid metric name na me. Defaulting to noop metric implementation.' - ); - }); - - it('should create observable counter with options', () => { - const observableCounter = meter.createObservableCounter('name', { - description: 'desc', - unit: '1', - disabled: false, - }) as ObservableCounterMetric; - assert.ok(observableCounter instanceof Metric); - }); - - it('should set callback and observe value ', async () => { - let counter = 0; - - function getValue() { - diag.info('getting value, counter:', counter); - if (++counter % 2 === 0) { - return 3; - } - return -1; - } - - const observableCounter = meter.createObservableCounter( - 'name', - { - description: 'desc', - }, - (observableResult: api.ObservableResult) => { - // simulate async - return new Promise(resolve => { - setTimeout(() => { - observableResult.observe(getValue(), { pid: '123', core: '1' }); - resolve(); - }, 1); - }); - } - ) as ObservableCounterMetric; - - let metricRecords = await observableCounter.getMetricRecord(); - assert.strictEqual(metricRecords.length, 1); - let point = metricRecords[0].aggregator.toPoint(); - assert.strictEqual(point.value, -1); - assert.strictEqual( - hashAttributes(metricRecords[0].attributes), - '|#core:1,pid:123' - ); - - metricRecords = await observableCounter.getMetricRecord(); - assert.strictEqual(metricRecords.length, 1); - point = metricRecords[0].aggregator.toPoint(); - assert.strictEqual(point.value, 3); - - metricRecords = await observableCounter.getMetricRecord(); - assert.strictEqual(metricRecords.length, 1); - point = metricRecords[0].aggregator.toPoint(); - assert.strictEqual(point.value, 3); - }); - - it('should set callback and observe value when callback returns nothing', async () => { - const observableCounter = meter.createObservableCounter( - 'name', - { - description: 'desc', - }, - (observableResult: api.ObservableResult) => { - observableResult.observe(1, { pid: '123', core: '1' }); - } - ) as ObservableCounterMetric; - - const metricRecords = await observableCounter.getMetricRecord(); - assert.strictEqual(metricRecords.length, 1); - }); - - it( - 'should set callback and observe value when callback returns anything' + - ' but Promise', - async () => { - const observableCounter = meter.createObservableCounter( - 'name', - { - description: 'desc', - }, - (observableResult: api.ObservableResult) => { - observableResult.observe(1, { pid: '123', core: '1' }); - return '1'; - } - ) as ObservableCounterMetric; - - const metricRecords = await observableCounter.getMetricRecord(); - assert.strictEqual(metricRecords.length, 1); - } - ); - - it('should reject getMetricRecord when callback throws an error', async () => { - const observableCounter = meter.createObservableCounter( - 'name', - { - description: 'desc', - }, - (observableResult: api.ObservableResult) => { - observableResult.observe(1, { pid: '123', core: '1' }); - throw new Error('Boom'); - } - ) as ObservableCounterMetric; - await observableCounter - .getMetricRecord() - .then() - .catch(e => { - assert.strictEqual(e.message, 'Boom'); - }); - }); - - it('should pipe through resource', async () => { - const observableCounter = meter.createObservableCounter('name', {}, result => { - result.observe(42, { foo: 'bar' }); - return Promise.resolve(); - }) as ObservableCounterMetric; - assert.ok(observableCounter.resource instanceof Resource); - - const [record] = await observableCounter.getMetricRecord(); - assert.ok(record.resource instanceof Resource); - }); - }); - - describe('#ObservableGauge', () => { - it('should create an observable gauge', () => { - const observableGauge = meter.createObservableGauge( - 'name' - ) as ObservableGaugeMetric; - assert.ok(observableGauge instanceof Metric); - }); - - it('should return noop observable gauge when name is invalid', () => { - // Need to stub/spy on the underlying logger as the "diag" instance is global - const spy = sinon.stub(diag, 'warn'); - const observableGauge = meter.createObservableGauge('na me'); - assert.ok(observableGauge === api.NOOP_OBSERVABLE_GAUGE_METRIC); - const args = spy.args[0]; - assert.ok( - args[0], - 'Invalid metric name na me. Defaulting to noop metric implementation.' - ); - }); - - it('should create observable gauge with options', () => { - const observableGauge = meter.createObservableGauge('name', { - description: 'desc', - unit: '1', - disabled: false, - }) as ObservableGaugeMetric; - assert.ok(observableGauge instanceof Metric); - }); - - it('should set callback and observe value ', async () => { - const observableGauge = meter.createObservableGauge( - 'name', - { - description: 'desc', - }, - (observableResult: api.ObservableResult) => { - // simulate async - return new Promise(resolve => { - setTimeout(() => { - observableResult.observe(getCpuUsage(), { pid: '123', core: '1' }); - observableResult.observe(getCpuUsage(), { pid: '123', core: '2' }); - observableResult.observe(getCpuUsage(), { pid: '123', core: '3' }); - observableResult.observe(getCpuUsage(), { pid: '123', core: '4' }); - resolve(); - }, 1); - }); - } - ) as ObservableGaugeMetric; - - function getCpuUsage() { - return Math.random(); - } - - const metricRecords: MetricRecord[] = await observableGauge.getMetricRecord(); - assert.strictEqual(metricRecords.length, 4); - - const metric1 = metricRecords[0]; - const metric2 = metricRecords[1]; - const metric3 = metricRecords[2]; - const metric4 = metricRecords[3]; - assert.strictEqual(hashAttributes(metric1.attributes), '|#core:1,pid:123'); - assert.strictEqual(hashAttributes(metric2.attributes), '|#core:2,pid:123'); - assert.strictEqual(hashAttributes(metric3.attributes), '|#core:3,pid:123'); - assert.strictEqual(hashAttributes(metric4.attributes), '|#core:4,pid:123'); - - ensureMetric(metric1); - ensureMetric(metric2); - ensureMetric(metric3); - ensureMetric(metric4); - }); - - it('should pipe through resource', async () => { - const observableGauge = meter.createObservableGauge('name', {}, result => { - result.observe(42, { foo: 'bar' }); - }) as ObservableGaugeMetric; - assert.ok(observableGauge.resource instanceof Resource); - - const [record] = await observableGauge.getMetricRecord(); - assert.ok(record.resource instanceof Resource); - }); - }); - - describe('#ObservableUpDownCounterMetric', () => { - it('should create an ObservableUpDownCounter', () => { - const observableUpDownCounter = meter.createObservableUpDownCounter( - 'name' - ) as ObservableUpDownCounterMetric; - assert.ok(observableUpDownCounter instanceof Metric); - }); - - it('should return noop observable up down counter when name is invalid', () => { - // Need to stub/spy on the underlying logger as the "diag" instance is global - const spy = sinon.stub(diag, 'warn'); - const observableUpDownCounter = meter.createObservableUpDownCounter('na me'); - assert.ok(observableUpDownCounter === api.NOOP_OBSERVABLE_UP_DOWN_COUNTER_METRIC); - const args = spy.args[0]; - assert.ok( - args[0], - 'Invalid metric name na me. Defaulting to noop metric implementation.' - ); - }); - - it('should create observable up down counter with options', () => { - const observableUpDownCounter = meter.createObservableUpDownCounter('name', { - description: 'desc', - unit: '1', - disabled: false, - }) as ObservableUpDownCounterMetric; - assert.ok(observableUpDownCounter instanceof Metric); - }); - - it('should set callback and observe value ', async () => { - let counter = 0; - - function getValue() { - counter++; - if (counter % 2 === 0) { - return 2; - } - return 3; - } - - const observableUpDownCounter = meter.createObservableUpDownCounter( - 'name', - { - description: 'desc', - }, - (observableResult: api.ObservableResult) => { - // simulate async - return new Promise(resolve => { - setTimeout(() => { - observableResult.observe(getValue(), { pid: '123', core: '1' }); - resolve(); - }, 1); - }); - } - ) as ObservableUpDownCounterMetric; - - let metricRecords = await observableUpDownCounter.getMetricRecord(); - assert.strictEqual(metricRecords.length, 1); - let point = metricRecords[0].aggregator.toPoint(); - assert.strictEqual(point.value, 3); - assert.strictEqual( - hashAttributes(metricRecords[0].attributes), - '|#core:1,pid:123' - ); - - metricRecords = await observableUpDownCounter.getMetricRecord(); - assert.strictEqual(metricRecords.length, 1); - point = metricRecords[0].aggregator.toPoint(); - assert.strictEqual(point.value, 2); - - metricRecords = await observableUpDownCounter.getMetricRecord(); - assert.strictEqual(metricRecords.length, 1); - point = metricRecords[0].aggregator.toPoint(); - assert.strictEqual(point.value, 3); - }); - - it('should set callback and observe value when callback returns nothing', async () => { - const observableUpDownCounter = meter.createObservableUpDownCounter( - 'name', - { - description: 'desc', - }, - (observableResult: api.ObservableResult) => { - observableResult.observe(1, { pid: '123', core: '1' }); - } - ) as ObservableUpDownCounterMetric; - - const metricRecords = await observableUpDownCounter.getMetricRecord(); - assert.strictEqual(metricRecords.length, 1); - }); - - it( - 'should set callback and observe value when callback returns anything' + - ' but Promise', - async () => { - const observableUpDownCounter = meter.createObservableUpDownCounter( - 'name', - { - description: 'desc', - }, - (observableResult: api.ObservableResult) => { - observableResult.observe(1, { pid: '123', core: '1' }); - return '1'; - } - ) as ObservableUpDownCounterMetric; - - const metricRecords = await observableUpDownCounter.getMetricRecord(); - assert.strictEqual(metricRecords.length, 1); - } - ); - - it('should reject getMetricRecord when callback throws an error', async () => { - const observableUpDownCounter = meter.createObservableUpDownCounter( - 'name', - { - description: 'desc', - }, - (observableResult: api.ObservableResult) => { - observableResult.observe(1, { pid: '123', core: '1' }); - throw new Error('Boom'); - } - ) as ObservableUpDownCounterMetric; - await observableUpDownCounter - .getMetricRecord() - .then() - .catch(e => { - assert.strictEqual(e.message, 'Boom'); - }); - }); - - it('should pipe through resource', async () => { - const observableUpDownCounter = meter.createObservableUpDownCounter( - 'name', - {}, - result => { - result.observe(42, { foo: 'bar' }); - return Promise.resolve(); - } - ) as ObservableUpDownCounterMetric; - assert.ok(observableUpDownCounter.resource instanceof Resource); - - const [record] = await observableUpDownCounter.getMetricRecord(); - assert.ok(record.resource instanceof Resource); - }); - }); - - describe('#getMetrics', () => { - it('should create a DOUBLE counter', async () => { - const key = 'key'; - const counter = meter.createCounter('counter', { - description: 'test', - }) as CounterMetric; - const attributes = { [key]: 'counter-value' }; - const boundCounter = counter.bind(attributes); - boundCounter.add(10.45); - - await meter.collect(); - const record = meter.getProcessor().checkPointSet(); - - assert.strictEqual(record.length, 1); - assert.deepStrictEqual(record[0].descriptor, { - name: 'counter', - description: 'test', - metricKind: MetricKind.COUNTER, - unit: '1', - valueType: api.ValueType.DOUBLE, - }); - assert.strictEqual(record[0].attributes, attributes); - const value = record[0].aggregator.toPoint().value as Sum; - assert.strictEqual(value, 10.45); - }); - - it('should create an INT counter', async () => { - const key = 'key'; - const counter = meter.createCounter('counter', { - description: 'test', - valueType: api.ValueType.INT, - }) as CounterMetric; - const attributes = { [key]: 'counter-value' }; - const boundCounter = counter.bind(attributes); - boundCounter.add(10.45); - - await meter.collect(); - const record = meter.getProcessor().checkPointSet(); - - assert.strictEqual(record.length, 1); - assert.deepStrictEqual(record[0].descriptor, { - name: 'counter', - description: 'test', - metricKind: MetricKind.COUNTER, - unit: '1', - valueType: api.ValueType.INT, - }); - assert.strictEqual(record[0].attributes, attributes); - const value = record[0].aggregator.toPoint().value as Sum; - assert.strictEqual(value, 10); - }); - }); -}); - -function ensureMetric(metric: MetricRecord, name?: string, value?: LastValue) { - assert.ok(metric.aggregator instanceof LastValueAggregator); - const lastValue = metric.aggregator.toPoint().value; - if (value) { - assert.deepStrictEqual(lastValue, value); - } - const descriptor = metric.descriptor; - assert.strictEqual(descriptor.name, name || 'name'); - assert.strictEqual(descriptor.description, 'desc'); - assert.strictEqual(descriptor.unit, '1'); - assert.strictEqual(descriptor.metricKind, MetricKind.OBSERVABLE_GAUGE); - assert.strictEqual(descriptor.valueType, api.ValueType.DOUBLE); -} diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/MeterProvider.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/MeterProvider.test.ts deleted file mode 100644 index 36e68e1271..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/MeterProvider.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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 assert from 'assert'; -import * as sinon from 'sinon'; -import { - MeterProvider, - Meter, - CounterMetric, - MetricRecord, - MetricDescriptor, - Aggregator, - Processor, -} from '../src'; - -describe('MeterProvider', () => { - afterEach(() => { - sinon.restore(); - }); - - describe('constructor', () => { - it('should construct an instance without any options', () => { - const provider = new MeterProvider(); - assert.ok(provider instanceof MeterProvider); - }); - - it('should construct an instance with logger', () => { - const provider = new MeterProvider(); - assert.ok(provider instanceof MeterProvider); - }); - }); - - describe('getMeter', () => { - it('should return an instance of Meter', () => { - const meter = new MeterProvider().getMeter('test-meter-provider'); - assert.ok(meter instanceof Meter); - }); - - it('should propagate resources', () => { - const meterProvider = new MeterProvider(); - const meter = meterProvider.getMeter('test-meter-provider'); - const counter = meter.createCounter('test-counter') as CounterMetric; - assert.strictEqual((meter as any)._resource, meterProvider.resource); - assert.strictEqual(counter.resource, meterProvider.resource); - }); - - it('should return the meter with default version without a version option', () => { - const provider = new MeterProvider(); - const meter1 = provider.getMeter('default'); - const meter2 = provider.getMeter('default', undefined); - assert.deepEqual(meter1, meter2); - }); - - it('should return the same Meter instance with same name & version', () => { - const provider = new MeterProvider(); - const meter1 = provider.getMeter('meter1', 'ver1'); - const meter2 = provider.getMeter('meter1', 'ver1'); - assert.deepEqual(meter1, meter2); - }); - - it('should return different Meter instance with different name or version', () => { - const provider = new MeterProvider(); - - const meter1 = provider.getMeter('meter1', 'ver1'); - const meter2 = provider.getMeter('meter1'); - assert.notEqual(meter1, meter2); - - const meter3 = provider.getMeter('meter2', 'ver2'); - const meter4 = provider.getMeter('meter3', 'ver2'); - assert.notEqual(meter3, meter4); - }); - - it('should allow custom processor', () => { - class CustomProcessor extends Processor { - process(record: MetricRecord): void { - throw new Error('process method not implemented.'); - } - - aggregatorFor(metricKind: MetricDescriptor): Aggregator { - throw new Error('aggregatorFor method not implemented.'); - } - } - - const meter = new MeterProvider({ - processor: new CustomProcessor(), - }).getMeter('custom-processor', '*'); - - assert.throws(() => { - const histogram = meter.createHistogram('myHistogram'); - histogram.record(1); - }, /aggregatorFor method not implemented/); - }); - }); - - describe('shutdown()', () => { - it('should call shutdown when manually invoked', () => { - const meterProvider = new MeterProvider({ - interval: Math.pow(2, 31) - 1, - }); - const shutdownStub1 = sinon.stub( - meterProvider.getMeter('meter1'), - 'shutdown' - ); - const shutdownStub2 = sinon.stub( - meterProvider.getMeter('meter2'), - 'shutdown' - ); - meterProvider.shutdown().then(() => { - sinon.assert.calledOnce(shutdownStub1); - sinon.assert.calledOnce(shutdownStub2); - }); - }); - }); -}); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/Processor.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/Processor.test.ts deleted file mode 100644 index 72af5f7bb6..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/Processor.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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-metrics'; -import * as assert from 'assert'; -import { CounterMetric, Meter, MeterProvider } from '../src'; - -describe('Processor', () => { - describe('Ungrouped', () => { - let meter: Meter; - let fooCounter: api.Counter; - let barCounter: api.Counter; - beforeEach(() => { - meter = new MeterProvider({ - interval: 10000, - }).getMeter('test-meter'); - const counter = meter.createCounter('ungrouped-processor-test') as CounterMetric; - fooCounter = counter.bind({ key: 'foo' }); - barCounter = counter.bind({ key: 'bar' }); - }); - - it('should process a batch', async () => { - fooCounter.add(1); - barCounter.add(1); - barCounter.add(2); - await meter.collect(); - const checkPointSet = meter.getProcessor().checkPointSet(); - assert.strictEqual(checkPointSet.length, 2); - for (const record of checkPointSet) { - switch (record.attributes.key) { - case 'foo': - assert.strictEqual(record.aggregator.toPoint().value, 1); - break; - case 'bar': - assert.strictEqual(record.aggregator.toPoint().value, 3); - break; - default: - throw new Error('Unknown attributeset'); - } - } - }); - }); -}); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/export/ConsoleMetricExporter.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/export/ConsoleMetricExporter.test.ts deleted file mode 100644 index 10168b0174..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/export/ConsoleMetricExporter.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 assert from 'assert'; -import * as sinon from 'sinon'; -import { ConsoleMetricExporter, CounterMetric, MeterProvider, MetricKind } from '../../src'; -import { ValueType } from '@opentelemetry/api-metrics'; - -/* eslint-disable no-console */ -describe('ConsoleMetricExporter', () => { - let consoleExporter: ConsoleMetricExporter; - let previousConsoleLog: any; - - beforeEach(() => { - previousConsoleLog = console.log; - console.log = () => {}; - consoleExporter = new ConsoleMetricExporter(); - }); - - afterEach(() => { - console.log = previousConsoleLog; - }); - - describe('.export()', () => { - it('should export information about metrics', async () => { - const spyConsole = sinon.spy(console, 'log'); - - const meter = new MeterProvider().getMeter( - 'test-console-metric-exporter' - ); - const counter = meter.createCounter('counter', { - description: 'a test description', - }) as CounterMetric; - const boundCounter = counter.bind({ - key1: 'attributeValue1', - key2: 'attributeValue2', - }); - boundCounter.add(10); - - await meter.collect(); - consoleExporter.export(meter.getProcessor().checkPointSet(), () => {}); - assert.strictEqual(spyConsole.args.length, 3); - const [descriptor, attributes, value] = spyConsole.args; - assert.deepStrictEqual(descriptor, [ - { - description: 'a test description', - metricKind: MetricKind.COUNTER, - name: 'counter', - unit: '1', - valueType: ValueType.DOUBLE, - }, - ]); - assert.deepStrictEqual(attributes, [ - { - key1: 'attributeValue1', - key2: 'attributeValue2', - }, - ]); - assert.deepStrictEqual(value[0], 'value: 10'); - }); - }); -}); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/export/Controller.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/export/Controller.test.ts deleted file mode 100644 index c753db6a48..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/export/Controller.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 assert from 'assert'; -import * as sinon from 'sinon'; -import { CounterMetric, MeterProvider, MetricExporter, MetricRecord } from '../../src'; -import { - ExportResult, - ExportResultCode, - setGlobalErrorHandler, -} from '@opentelemetry/core'; - -class MockExporter implements MetricExporter { - constructor(private _result: ExportResult) {} - - export( - metrics: MetricRecord[], - resultCallback: (result: ExportResult) => void - ): void { - return resultCallback(this._result); - } - - shutdown() { - return Promise.resolve(); - } -} - -describe('Controller', () => { - describe('._collect()', () => { - it('should use globalErrorHandler in case of error', done => { - const errorHandlerSpy = sinon.spy(); - setGlobalErrorHandler(errorHandlerSpy); - const expectedError = new Error('Failed to export'); - const meter = new MeterProvider({ - exporter: new MockExporter({ - code: ExportResultCode.FAILED, - error: expectedError, - }), - }).getMeter('test-console-metric-exporter'); - const counter = (meter - .createCounter('counter', { - description: 'a test description', - }) as CounterMetric) - .bind({}); - counter.add(10); - - // @ts-expect-error trigger the collection from the controller - meter._controller._collect(); - // wait for result - setTimeout(() => { - assert.deepStrictEqual( - errorHandlerSpy.args[0][0].message, - expectedError.message - ); - setGlobalErrorHandler(() => {}); - return done(); - }, 0); - }); - }); -}); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/test/export/aggregators/Histogram.test.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/export/aggregators/Histogram.test.ts deleted file mode 100644 index 6ebf7bdf64..0000000000 --- a/experimental/packages/opentelemetry-sdk-metrics-base/test/export/aggregators/Histogram.test.ts +++ /dev/null @@ -1,194 +0,0 @@ -/* - * 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 assert from 'assert'; -import { HistogramAggregator } from '../../../src/export/aggregators'; -import { Histogram } from '../../../src'; -import { hrTime, hrTimeToMilliseconds } from '@opentelemetry/core'; -import sinon = require('sinon'); - -describe('HistogramAggregator', () => { - describe('constructor()', () => { - it('should construct a histogramAggregator', () => { - assert.doesNotThrow(() => { - new HistogramAggregator([1, 2]); - }); - }); - - it('should sort boundaries', () => { - const aggregator = new HistogramAggregator([ - 200, - 500, - 300, - 700, - 1000, - 1500, - ]); - const point = aggregator.toPoint().value as Histogram; - assert.deepEqual(point.buckets.boundaries, [ - 200, - 300, - 500, - 700, - 1000, - 1500, - ]); - }); - - it('should throw if no boundaries are defined', () => { - // @ts-expect-error verify use without boundaries - assert.throws(() => new HistogramAggregator()); - assert.throws(() => new HistogramAggregator([])); - }); - }); - - describe('.update()', () => { - it('should update the second bucket', () => { - const aggregator = new HistogramAggregator([100, 200]); - aggregator.update(150); - const point = aggregator.toPoint().value as Histogram; - assert.equal(point.count, 1); - assert.equal(point.sum, 150); - assert.equal(point.buckets.counts[0], 0); - assert.equal(point.buckets.counts[1], 1); - assert.equal(point.buckets.counts[2], 0); - }); - - it('should update the second bucket', () => { - const aggregator = new HistogramAggregator([100, 200]); - aggregator.update(50); - const point = aggregator.toPoint().value as Histogram; - assert.equal(point.count, 1); - assert.equal(point.sum, 50); - assert.equal(point.buckets.counts[0], 1); - assert.equal(point.buckets.counts[1], 0); - assert.equal(point.buckets.counts[2], 0); - }); - - it('should update the third bucket since value is above all boundaries', () => { - const aggregator = new HistogramAggregator([100, 200]); - aggregator.update(250); - const point = aggregator.toPoint().value as Histogram; - assert.equal(point.count, 1); - assert.equal(point.sum, 250); - assert.equal(point.buckets.counts[0], 0); - assert.equal(point.buckets.counts[1], 0); - assert.equal(point.buckets.counts[2], 1); - }); - - it('should update the third bucket since boundaries are inclusive lower bounds', () => { - const aggregator = new HistogramAggregator([100, 200]); - aggregator.update(200); - const point = aggregator.toPoint().value as Histogram; - assert.equal(point.count, 1); - assert.equal(point.sum, 200); - assert.equal(point.buckets.counts[0], 0); - assert.equal(point.buckets.counts[1], 0); - assert.equal(point.buckets.counts[2], 1); - }); - }); - - describe('.timestamp', () => { - let clock: sinon.SinonFakeTimers; - before(() => { - clock = sinon.useFakeTimers({ toFake: ['hrtime'] }); - }); - - it('should update point timestamp', () => { - const aggregator = new HistogramAggregator([100, 200]); - const timestamp = hrTimeToMilliseconds(hrTime()); - const timeDiff = 10; - clock.tick(timeDiff); - aggregator.update(150); - assert.equal( - hrTimeToMilliseconds(aggregator.toPoint().timestamp) >= - timestamp + timeDiff, - true - ); - }); - - after(() => { - clock.restore(); - }); - }); - - describe('.count', () => { - it('should return last checkpoint count', () => { - const aggregator = new HistogramAggregator([100]); - let point = aggregator.toPoint().value as Histogram; - assert.equal(point.count, point.count); - aggregator.update(10); - point = aggregator.toPoint().value as Histogram; - assert.equal(point.count, 1); - assert.equal(point.count, point.count); - }); - }); - - describe('.sum', () => { - it('should return last checkpoint sum', () => { - const aggregator = new HistogramAggregator([100]); - let point = aggregator.toPoint().value as Histogram; - assert.equal(point.sum, point.sum); - aggregator.update(10); - point = aggregator.toPoint().value as Histogram; - assert.equal(point.sum, 10); - }); - }); - - describe('.reset()', () => { - it('should create a empty checkoint by default', () => { - const aggregator = new HistogramAggregator([100]); - const point = aggregator.toPoint().value as Histogram; - assert.deepEqual(point.buckets.boundaries, [100]); - assert(point.buckets.counts.every(count => count === 0)); - // should contains one bucket for each boundary + one for values outside of the largest boundary - assert.equal(point.buckets.counts.length, 2); - assert.deepEqual(point.buckets.boundaries, [100]); - assert.equal(point.count, 0); - assert.equal(point.sum, 0); - }); - - it('should update checkpoint', () => { - const aggregator = new HistogramAggregator([100]); - aggregator.update(10); - const point = aggregator.toPoint().value as Histogram; - assert.equal(point.count, 1); - assert.equal(point.sum, 10); - assert.deepEqual(point.buckets.boundaries, [100]); - assert.equal(point.buckets.counts.length, 2); - assert.deepEqual(point.buckets.counts, [1, 0]); - }); - }); - - describe('.toPoint()', () => { - it('should return default checkpoint', () => { - const aggregator = new HistogramAggregator([100]); - const point = aggregator.toPoint().value as Histogram; - assert.deepEqual(aggregator.toPoint().value, point); - assert(aggregator.toPoint().timestamp.every(nbr => nbr > 0)); - }); - - it('should return last checkpoint if updated', () => { - const aggregator = new HistogramAggregator([100]); - aggregator.update(100); - assert( - aggregator - .toPoint() - .timestamp.every(nbr => typeof nbr === 'number' && nbr !== 0) - ); - }); - }); -}); diff --git a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/aggregators/index.ts b/experimental/packages/opentelemetry-sdk-metrics-base/test/noop.test.ts similarity index 88% rename from experimental/packages/opentelemetry-sdk-metrics-base/src/export/aggregators/index.ts rename to experimental/packages/opentelemetry-sdk-metrics-base/test/noop.test.ts index 4f531c287d..76c007ebc0 100644 --- a/experimental/packages/opentelemetry-sdk-metrics-base/src/export/aggregators/index.ts +++ b/experimental/packages/opentelemetry-sdk-metrics-base/test/noop.test.ts @@ -14,6 +14,6 @@ * limitations under the License. */ -export * from './Histogram'; -export * from './LastValue'; -export * from './Sum'; +describe('nothing', () => { + it.skip('tests nothing'); +});