diff --git a/examples/metrics/README.md b/examples/metrics/README.md
index 63fd8a5858..ee242cfd26 100644
--- a/examples/metrics/README.md
+++ b/examples/metrics/README.md
@@ -26,7 +26,7 @@ npm run start:observer
### Prometheus
-1. In prometheus search for "metric_observer"
+1. In prometheus search for "cpu_core_usage", "cpu_temp_per_app", "cpu_usage_per_app"
### Links
@@ -35,7 +35,9 @@ npm run start:observer
### Example
-
+![Screenshot of the running example](metrics/observer.png)
+![Screenshot of the running example](metrics/observer_batch.png)
+![Screenshot of the running example](metrics/observer_batch2.png)
## Useful links
diff --git a/examples/metrics/metrics/observer.js b/examples/metrics/metrics/observer.js
index 52c424b11a..f455383a05 100644
--- a/examples/metrics/metrics/observer.js
+++ b/examples/metrics/metrics/observer.js
@@ -1,6 +1,7 @@
'use strict';
-const { MeterProvider, MetricObservable } = require('@opentelemetry/metrics');
+const { MeterProvider } = require('@opentelemetry/metrics');
+const { ConsoleLogger, LogLevel } = require('@opentelemetry/core');
const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus');
const exporter = new PrometheusExporter(
@@ -19,25 +20,68 @@ const meter = new MeterProvider({
interval: 2000,
}).getMeter('example-observer');
-const otelCpuUsage = meter.createObserver('metric_observer', {
- monotonic: false,
- description: 'Example of a observer',
+meter.createValueObserver('cpu_core_usage', {
+ description: 'Example of a sync value observer with callback',
+}, (observerResult) => { // this callback is called once per each interval
+ observerResult.observe(getRandomValue(), { core: '1' });
+ observerResult.observe(getRandomValue(), { core: '2' });
});
-function getCpuUsage() {
- return Math.random();
-}
+// no callback as they will be updated in batch observer
+const tempMetric = meter.createValueObserver('cpu_temp_per_app', {
+ description: 'Example of sync value observer used with async batch observer',
+});
-const observable = new MetricObservable();
+// no callback as they will be updated in batch observer
+const cpuUsageMetric = meter.createValueObserver('cpu_usage_per_app', {
+ description: 'Example of sync value observer used with async batch observer',
+});
-setInterval(() => {
- observable.next(getCpuUsage());
-}, 5000);
+meter.createBatchObserver('metric_batch_observer', (observerBatchResult) => {
+ Promise.all([
+ someAsyncMetrics(),
+ // simulate waiting
+ new Promise((resolve, reject) => {
+ setTimeout(resolve, 300);
+ }),
+ ]).then(([apps, waiting]) => {
+ apps.forEach(app => {
+ observerBatchResult.observe({ app: app.name, core: '1' }, [
+ tempMetric.observation(app.core1.temp),
+ cpuUsageMetric.observation(app.core1.usage),
+ ]);
+ observerBatchResult.observe({ app: app.name, core: '2' }, [
+ tempMetric.observation(app.core2.temp),
+ cpuUsageMetric.observation(app.core2.usage),
+ ]);
+ });
+ });
+ }, {
+ maxTimeoutUpdateMS: 500,
+ logger: new ConsoleLogger(LogLevel.DEBUG)
+ },
+);
-otelCpuUsage.setCallback((observerResult) => {
- observerResult.observe(getCpuUsage, { pid: process.pid, core: '1' });
- observerResult.observe(getCpuUsage, { pid: process.pid, core: '2' });
- observerResult.observe(getCpuUsage, { pid: process.pid, core: '3' });
- observerResult.observe(getCpuUsage, { pid: process.pid, core: '4' });
- observerResult.observe(observable, { pid: process.pid, core: '5' });
-});
+function someAsyncMetrics() {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const stats = [
+ {
+ name: 'app1',
+ core1: { usage: getRandomValue(), temp: getRandomValue() * 100 },
+ core2: { usage: getRandomValue(), temp: getRandomValue() * 100 },
+ },
+ {
+ name: 'app2',
+ core1: { usage: getRandomValue(), temp: getRandomValue() * 100 },
+ core2: { usage: getRandomValue(), temp: getRandomValue() * 100 },
+ },
+ ];
+ resolve(stats);
+ }, 200);
+ });
+}
+
+function getRandomValue() {
+ return Math.random();
+}
diff --git a/examples/metrics/metrics/observer.png b/examples/metrics/metrics/observer.png
index 79f77b0c1c..b8c3c48910 100644
Binary files a/examples/metrics/metrics/observer.png and b/examples/metrics/metrics/observer.png differ
diff --git a/examples/metrics/metrics/observer_batch.png b/examples/metrics/metrics/observer_batch.png
new file mode 100644
index 0000000000..32b5f25fe2
Binary files /dev/null and b/examples/metrics/metrics/observer_batch.png differ
diff --git a/examples/metrics/metrics/observer_batch2.png b/examples/metrics/metrics/observer_batch2.png
new file mode 100644
index 0000000000..a8792e913a
Binary files /dev/null and b/examples/metrics/metrics/observer_batch2.png differ
diff --git a/examples/metrics/package.json b/examples/metrics/package.json
index f412f371a4..e7b8bfce83 100644
--- a/examples/metrics/package.json
+++ b/examples/metrics/package.json
@@ -26,6 +26,7 @@
"url": "https://github.com/open-telemetry/opentelemetry-js/issues"
},
"dependencies": {
+ "@opentelemetry/core": "^0.9.0",
"@opentelemetry/exporter-prometheus": "^0.9.0",
"@opentelemetry/metrics": "^0.9.0"
},
diff --git a/examples/tracer-web/examples/xml-http-request/index.js b/examples/tracer-web/examples/xml-http-request/index.js
index ce22cec2c5..fb1a6529c8 100644
--- a/examples/tracer-web/examples/xml-http-request/index.js
+++ b/examples/tracer-web/examples/xml-http-request/index.js
@@ -26,16 +26,19 @@ providerWithZone.register({
const webTracerWithZone = providerWithZone.getTracer('example-tracer-web');
-const getData = (url) => new Promise((resolve, _reject) => {
+const getData = (url) => new Promise((resolve, reject) => {
// eslint-disable-next-line no-undef
const req = new XMLHttpRequest();
req.open('GET', url, true);
req.setRequestHeader('Content-Type', 'application/json');
req.setRequestHeader('Accept', 'application/json');
- req.send();
req.onload = () => {
resolve();
};
+ req.onerror = () => {
+ reject();
+ };
+ req.send();
});
// example of keeping track of context between async operations
@@ -53,6 +56,9 @@ const prepareClickEvent = () => {
getData(url1).then((_data) => {
webTracerWithZone.getCurrentSpan().addEvent('fetching-span1-completed');
span1.end();
+ }, ()=> {
+ webTracerWithZone.getCurrentSpan().addEvent('fetching-error');
+ span1.end();
});
});
}
diff --git a/packages/opentelemetry-api/src/index.ts b/packages/opentelemetry-api/src/index.ts
index 2f5f81ac99..f9bcf40664 100644
--- a/packages/opentelemetry-api/src/index.ts
+++ b/packages/opentelemetry-api/src/index.ts
@@ -22,13 +22,14 @@ export * from './context/propagation/NoopHttpTextPropagator';
export * from './context/propagation/setter';
export * from './correlation_context/CorrelationContext';
export * from './correlation_context/EntryValue';
+export * from './metrics/BatchObserverResult';
export * from './metrics/BoundInstrument';
export * from './metrics/Meter';
export * from './metrics/MeterProvider';
export * from './metrics/Metric';
-export * from './metrics/MetricObservable';
export * from './metrics/NoopMeter';
export * from './metrics/NoopMeterProvider';
+export * from './metrics/Observation';
export * from './metrics/ObserverResult';
export * from './trace/attributes';
export * from './trace/Event';
diff --git a/packages/opentelemetry-api/src/metrics/MetricObservable.ts b/packages/opentelemetry-api/src/metrics/BatchObserverResult.ts
similarity index 61%
rename from packages/opentelemetry-api/src/metrics/MetricObservable.ts
rename to packages/opentelemetry-api/src/metrics/BatchObserverResult.ts
index 161d2919f4..bae99eb866 100644
--- a/packages/opentelemetry-api/src/metrics/MetricObservable.ts
+++ b/packages/opentelemetry-api/src/metrics/BatchObserverResult.ts
@@ -13,20 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export interface MetricObservable {
- /**
- * Sets the next value for observable metric
- * @param value
- */
- next: (value: number) => void;
- /**
- * Subscribes for every value change
- * @param callback
- */
- subscribe: (callback: (value: number) => void) => void;
+
+import { Labels } from './Metric';
+import { Observation } from './Observation';
+
+/**
+ * Interface that is being used in callback function for Observer Metric
+ * for batch
+ */
+export interface BatchObserverResult {
/**
- * Removes the subscriber
- * @param [callback]
+ * Used to observe (update) observations for certain labels
+ * @param labels
+ * @param observations
*/
- unsubscribe: (callback?: (value: number) => void) => void;
+ observe(labels: Labels, observations: Observation[]): void;
}
diff --git a/packages/opentelemetry-api/src/metrics/BoundInstrument.ts b/packages/opentelemetry-api/src/metrics/BoundInstrument.ts
index 5b77158356..4ffbd143c7 100644
--- a/packages/opentelemetry-api/src/metrics/BoundInstrument.ts
+++ b/packages/opentelemetry-api/src/metrics/BoundInstrument.ts
@@ -44,3 +44,8 @@ export interface BoundValueRecorder {
spanContext: SpanContext
): void;
}
+
+/** An Instrument for Base Observer */
+export interface BoundBaseObserver {
+ update(value: number): void;
+}
diff --git a/packages/opentelemetry-api/src/metrics/Meter.ts b/packages/opentelemetry-api/src/metrics/Meter.ts
index cf6d15df04..eb570a4802 100644
--- a/packages/opentelemetry-api/src/metrics/Meter.ts
+++ b/packages/opentelemetry-api/src/metrics/Meter.ts
@@ -14,13 +14,17 @@
* limitations under the License.
*/
+import { BatchObserverResult } from './BatchObserverResult';
import {
MetricOptions,
Counter,
ValueRecorder,
- Observer,
+ ValueObserver,
+ BatchObserver,
+ BatchMetricOptions,
UpDownCounter,
} from './Metric';
+import { ObserverResult } from './ObserverResult';
/**
* An interface to allow the recording metrics.
@@ -66,9 +70,27 @@ export interface Meter {
createUpDownCounter(name: string, options?: MetricOptions): UpDownCounter;
/**
- * Creates a new `Observer` metric.
+ * Creates a new `ValueObserver` metric.
* @param name the name of the metric.
* @param [options] the metric options.
+ * @param [callback] the observer callback
*/
- createObserver(name: string, options?: MetricOptions): Observer;
+ createValueObserver(
+ name: string,
+ options?: MetricOptions,
+ callback?: (observerResult: ObserverResult) => void
+ ): ValueObserver;
+
+ /**
+ * Creates a new `BatchObserver` metric, can be used to update many metrics
+ * at the same time and when operations needs to be async
+ * @param name the name of the metric.
+ * @param callback the batch observer callback
+ * @param [options] the metric batch options.
+ */
+ createBatchObserver(
+ name: string,
+ callback: (batchObserverResult: BatchObserverResult) => void,
+ options?: BatchMetricOptions
+ ): BatchObserver;
}
diff --git a/packages/opentelemetry-api/src/metrics/Metric.ts b/packages/opentelemetry-api/src/metrics/Metric.ts
index 24f36cfd05..ebf514d6e3 100644
--- a/packages/opentelemetry-api/src/metrics/Metric.ts
+++ b/packages/opentelemetry-api/src/metrics/Metric.ts
@@ -16,8 +16,12 @@
import { CorrelationContext } from '../correlation_context/CorrelationContext';
import { SpanContext } from '../trace/span_context';
-import { ObserverResult } from './ObserverResult';
-import { BoundCounter, BoundValueRecorder } from './BoundInstrument';
+import {
+ BoundBaseObserver,
+ BoundCounter,
+ BoundValueRecorder,
+} from './BoundInstrument';
+import { Logger } from '../common/Logger';
/**
* Options needed for metric creation
@@ -58,6 +62,18 @@ export interface MetricOptions {
* @default {@link ValueType.DOUBLE}
*/
valueType?: ValueType;
+
+ /**
+ * User provided logger.
+ */
+ logger?: Logger;
+}
+
+export interface BatchMetricOptions extends MetricOptions {
+ /**
+ * Indicates how long the batch metric should wait to update before cancel
+ */
+ maxTimeoutUpdateMS?: number;
}
/** The Type of value. It describes how the data is reported. */
@@ -148,16 +164,21 @@ export interface ValueRecorder extends UnboundMetric {
}
/** Base interface for the Observer metrics. */
-export interface Observer extends Metric {
- /**
- * Sets a callback where user can observe value for certain labels. The
- * observers are called periodically to retrieve the value.
- * @param callback a function that will be called once to set observers
- * for values
- */
- setCallback(callback: (observerResult: ObserverResult) => void): void;
+export interface BaseObserver extends UnboundMetric {
+ observation: (
+ value: number
+ ) => {
+ value: number;
+ observer: BaseObserver;
+ };
}
+/** Base interface for the Value Observer metrics. */
+export type ValueObserver = BaseObserver;
+
+/** Base interface for the Batch Observer metrics. */
+export type BatchObserver = Metric;
+
/**
* key-value pairs passed by the user.
*/
diff --git a/packages/opentelemetry-api/src/metrics/NoopMeter.ts b/packages/opentelemetry-api/src/metrics/NoopMeter.ts
index 075a1e544e..9d63f21bb5 100644
--- a/packages/opentelemetry-api/src/metrics/NoopMeter.ts
+++ b/packages/opentelemetry-api/src/metrics/NoopMeter.ts
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import { BatchObserverResult } from './BatchObserverResult';
import { Meter } from './Meter';
import {
MetricOptions,
@@ -21,10 +22,16 @@ import {
Labels,
Counter,
ValueRecorder,
- Observer,
+ ValueObserver,
+ BatchObserver,
UpDownCounter,
+ BaseObserver,
} from './Metric';
-import { BoundValueRecorder, BoundCounter } from './BoundInstrument';
+import {
+ BoundValueRecorder,
+ BoundCounter,
+ BoundBaseObserver,
+} from './BoundInstrument';
import { CorrelationContext } from '../correlation_context/CorrelationContext';
import { SpanContext } from '../trace/span_context';
import { ObserverResult } from './ObserverResult';
@@ -64,12 +71,29 @@ export class NoopMeter implements Meter {
}
/**
- * Returns constant noop observer.
+ * Returns constant noop value observer.
* @param name the name of the metric.
* @param [options] the metric options.
+ * @param [callback] the value observer callback
+ */
+ createValueObserver(
+ name: string,
+ options?: MetricOptions,
+ callback?: (observerResult: ObserverResult) => void
+ ): ValueObserver {
+ return NOOP_VALUE_OBSERVER_METRIC;
+ }
+
+ /**
+ * Returns constant noop batch observer.
+ * @param name the name of the metric.
+ * @param callback the batch observer callback
*/
- createObserver(name: string, options?: MetricOptions): Observer {
- return NOOP_OBSERVER_METRIC;
+ createBatchObserver(
+ name: string,
+ callback: (batchObserverResult: BatchObserverResult) => void
+ ): BatchObserver {
+ return NOOP_BATCH_OBSERVER_METRIC;
}
}
@@ -79,6 +103,7 @@ export class NoopMetric implements UnboundMetric {
constructor(instrument: T) {
this._instrument = instrument;
}
+
/**
* Returns a Bound Instrument associated with specified Labels.
* It is recommended to keep a reference to the Bound Instrument instead of
@@ -131,10 +156,19 @@ export class NoopValueRecorderMetric extends NoopMetric
}
}
-export class NoopObserverMetric extends NoopMetric implements Observer {
- setCallback(callback: (observerResult: ObserverResult) => void): void {}
+export class NoopBaseObserverMetric extends NoopMetric
+ implements BaseObserver {
+ observation() {
+ return {
+ observer: this as BaseObserver,
+ value: 0,
+ };
+ }
}
+export class NoopBatchObserverMetric extends NoopMetric
+ implements BatchObserver {}
+
export class NoopBoundCounter implements BoundCounter {
add(value: number): void {
return;
@@ -151,6 +185,10 @@ export class NoopBoundValueRecorder implements BoundValueRecorder {
}
}
+export class NoopBoundBaseObserver implements BoundBaseObserver {
+ update(value: number) {}
+}
+
export const NOOP_METER = new NoopMeter();
export const NOOP_BOUND_COUNTER = new NoopBoundCounter();
export const NOOP_COUNTER_METRIC = new NoopCounterMetric(NOOP_BOUND_COUNTER);
@@ -160,4 +198,8 @@ export const NOOP_VALUE_RECORDER_METRIC = new NoopValueRecorderMetric(
NOOP_BOUND_VALUE_RECORDER
);
-export const NOOP_OBSERVER_METRIC = new NoopObserverMetric();
+export const NOOP_BOUND_BASE_OBSERVER = new NoopBoundBaseObserver();
+export const NOOP_VALUE_OBSERVER_METRIC = new NoopBaseObserverMetric(
+ NOOP_BOUND_BASE_OBSERVER
+);
+export const NOOP_BATCH_OBSERVER_METRIC = new NoopBatchObserverMetric();
diff --git a/packages/opentelemetry-api/src/metrics/Observation.ts b/packages/opentelemetry-api/src/metrics/Observation.ts
new file mode 100644
index 0000000000..d36f48fb71
--- /dev/null
+++ b/packages/opentelemetry-api/src/metrics/Observation.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { BaseObserver } from './Metric';
+
+/**
+ * Interface for updating value of certain value observer
+ */
+export interface Observation {
+ observer: BaseObserver;
+ value: number;
+}
diff --git a/packages/opentelemetry-api/src/metrics/ObserverResult.ts b/packages/opentelemetry-api/src/metrics/ObserverResult.ts
index 0b7286ac38..7792483ad1 100644
--- a/packages/opentelemetry-api/src/metrics/ObserverResult.ts
+++ b/packages/opentelemetry-api/src/metrics/ObserverResult.ts
@@ -15,11 +15,10 @@
*/
import { Labels } from './Metric';
-import { MetricObservable } from './MetricObservable';
/**
- * Interface that is being used in function setCallback for Observer Metric
+ * Interface that is being used in callback function for Observer Metric
*/
export interface ObserverResult {
- observe(callback: Function | MetricObservable, labels: Labels): void;
+ observe(value: number, labels: Labels): void;
}
diff --git a/packages/opentelemetry-exporter-prometheus/src/prometheus.ts b/packages/opentelemetry-exporter-prometheus/src/prometheus.ts
index e2b23b33a2..2951ddeeee 100644
--- a/packages/opentelemetry-exporter-prometheus/src/prometheus.ts
+++ b/packages/opentelemetry-exporter-prometheus/src/prometheus.ts
@@ -14,22 +14,21 @@
* limitations under the License.
*/
+import * as api from '@opentelemetry/api';
import {
ExportResult,
- NoopLogger,
hrTimeToMilliseconds,
+ NoopLogger,
} from '@opentelemetry/core';
import {
- CounterSumAggregator,
- LastValue,
- MetricExporter,
- MetricRecord,
+ Distribution,
+ Histogram,
MetricDescriptor,
+ MetricExporter,
MetricKind,
- ObserverAggregator,
+ MetricRecord,
Sum,
} from '@opentelemetry/metrics';
-import * as api from '@opentelemetry/api';
import { createServer, IncomingMessage, Server, ServerResponse } from 'http';
import { Counter, Gauge, Metric, Registry } from 'prom-client';
import * as url from 'url';
@@ -143,18 +142,33 @@ export class PrometheusExporter implements MetricExporter {
}
if (metric instanceof Gauge) {
- if (record.aggregator instanceof CounterSumAggregator) {
- metric.set(labels, point.value as Sum);
- } else if (record.aggregator instanceof ObserverAggregator) {
+ if (typeof point.value === 'number') {
+ if (
+ record.descriptor.metricKind === MetricKind.VALUE_OBSERVER ||
+ record.descriptor.metricKind === MetricKind.VALUE_RECORDER
+ ) {
+ metric.set(
+ labels,
+ point.value,
+ hrTimeToMilliseconds(point.timestamp)
+ );
+ } else {
+ metric.set(labels, point.value);
+ }
+ } else if ((point.value as Histogram).buckets) {
metric.set(
labels,
- point.value as LastValue,
+ (point.value as Histogram).sum,
+ hrTimeToMilliseconds(point.timestamp)
+ );
+ } else if (typeof (point.value as Distribution).max === 'number') {
+ metric.set(
+ labels,
+ (point.value as Distribution).sum,
hrTimeToMilliseconds(point.timestamp)
);
}
}
-
- // TODO: only counter and gauge are implemented in metrics so far
}
private _registerMetric(record: MetricRecord): Metric | undefined {
@@ -194,7 +208,10 @@ export class PrometheusExporter implements MetricExporter {
return new Counter(metricObject);
case MetricKind.UP_DOWN_COUNTER:
return new Gauge(metricObject);
- case MetricKind.OBSERVER:
+ // case MetricKind.VALUE_RECORDER:
+ // case MetricKind.SUM_OBSERVER:
+ // case MetricKind.UP_DOWN_SUM_OBSERVER:
+ case MetricKind.VALUE_OBSERVER:
return new Gauge(metricObject);
default:
// Other metric types are currently unimplemented
diff --git a/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts b/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts
index f0a152e9b2..ba499de3f5 100644
--- a/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts
+++ b/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts
@@ -17,10 +17,9 @@
import { HrTime, ObserverResult } from '@opentelemetry/api';
import {
CounterMetric,
- CounterSumAggregator,
+ SumAggregator,
Meter,
MeterProvider,
- ObserverMetric,
Point,
} from '@opentelemetry/metrics';
import * as assert from 'assert';
@@ -33,15 +32,15 @@ const mockedTimeMS = 1586347902211000;
describe('PrometheusExporter', () => {
let toPoint: () => Point;
before(() => {
- toPoint = CounterSumAggregator.prototype.toPoint;
- CounterSumAggregator.prototype.toPoint = function (): Point {
+ toPoint = SumAggregator.prototype.toPoint;
+ SumAggregator.prototype.toPoint = function (): Point {
const point = toPoint.apply(this);
point.timestamp = mockedHrTime;
return point;
};
});
after(() => {
- CounterSumAggregator.prototype.toPoint = toPoint;
+ SumAggregator.prototype.toPoint = toPoint;
});
describe('constructor', () => {
it('should construct an exporter', () => {
@@ -204,35 +203,36 @@ describe('PrometheusExporter', () => {
const boundCounter = counter.bind({ key1: 'labelValue1' });
boundCounter.add(10);
- meter.collect();
- exporter.export(meter.getBatcher().checkPointSet(), () => {
- // This is to test the special case where counters are destroyed
- // and recreated in the exporter in order to get around prom-client's
- // aggregation and use ours.
- boundCounter.add(10);
+ meter.collect().then(() => {
exporter.export(meter.getBatcher().checkPointSet(), () => {
- http
- .get('http://localhost:9464/metrics', res => {
- res.on('data', chunk => {
- const body = chunk.toString();
- const lines = body.split('\n');
-
- assert.strictEqual(
- lines[0],
- '# HELP counter a test description'
- );
-
- assert.deepStrictEqual(lines, [
- '# HELP counter a test description',
- '# TYPE counter counter',
- `counter{key1="labelValue1"} 20 ${mockedTimeMS}`,
- '',
- ]);
-
- done();
- });
- })
- .on('error', errorHandler(done));
+ // This is to test the special case where counters are destroyed
+ // and recreated in the exporter in order to get around prom-client's
+ // aggregation and use ours.
+ boundCounter.add(10);
+ exporter.export(meter.getBatcher().checkPointSet(), () => {
+ http
+ .get('http://localhost:9464/metrics', res => {
+ res.on('data', chunk => {
+ const body = chunk.toString();
+ const lines = body.split('\n');
+
+ assert.strictEqual(
+ lines[0],
+ '# HELP counter a test description'
+ );
+
+ assert.deepStrictEqual(lines, [
+ '# HELP counter a test description',
+ '# TYPE counter counter',
+ `counter{key1="labelValue1"} 20 ${mockedTimeMS}`,
+ '',
+ ]);
+
+ done();
+ });
+ })
+ .on('error', errorHandler(done));
+ });
});
});
});
@@ -242,16 +242,60 @@ describe('PrometheusExporter', () => {
return Math.random();
}
- const observer = meter.createObserver('metric_observer', {
- description: 'a test description',
- }) as ObserverMetric;
+ meter.createValueObserver(
+ 'metric_observer',
+ {
+ description: 'a test description',
+ },
+ (observerResult: ObserverResult) => {
+ observerResult.observe(getCpuUsage(), {
+ pid: String(123),
+ core: '1',
+ });
+ }
+ );
- observer.setCallback((observerResult: ObserverResult) => {
- observerResult.observe(getCpuUsage, { pid: String(123), core: '1' });
+ meter.collect().then(() => {
+ exporter.export(meter.getBatcher().checkPointSet(), () => {
+ exporter.export(meter.getBatcher().checkPointSet(), () => {
+ http
+ .get('http://localhost:9464/metrics', res => {
+ res.on('data', chunk => {
+ const body = chunk.toString();
+ const lines = body.split('\n');
+
+ assert.strictEqual(
+ lines[0],
+ '# HELP metric_observer a test description'
+ );
+ assert.strictEqual(lines[1], '# TYPE metric_observer gauge');
+
+ const line3 = lines[2].split(' ');
+ assert.strictEqual(
+ line3[0],
+ 'metric_observer{pid="123",core="1"}'
+ );
+ assert.ok(
+ parseFloat(line3[1]) >= 0 && parseFloat(line3[1]) <= 1
+ );
+
+ done();
+ });
+ })
+ .on('error', errorHandler(done));
+ });
+ });
});
+ });
+
+ it('should export multiple labels', done => {
+ const counter = meter.createCounter('counter', {
+ description: 'a test description',
+ }) as CounterMetric;
- meter.collect();
- exporter.export(meter.getBatcher().checkPointSet(), () => {
+ counter.bind({ counterKey1: 'labelValue1' }).add(10);
+ counter.bind({ counterKey1: 'labelValue2' }).add(20);
+ meter.collect().then(() => {
exporter.export(meter.getBatcher().checkPointSet(), () => {
http
.get('http://localhost:9464/metrics', res => {
@@ -259,22 +303,13 @@ describe('PrometheusExporter', () => {
const body = chunk.toString();
const lines = body.split('\n');
- assert.strictEqual(
- lines[0],
- '# HELP metric_observer a test description'
- );
-
- assert.strictEqual(lines[1], '# TYPE metric_observer gauge');
-
- const line3 = lines[2].split(' ');
- assert.strictEqual(
- line3[0],
- 'metric_observer{pid="123",core="1"}'
- );
- assert.ok(
- parseFloat(line3[1]) >= 0 && parseFloat(line3[1]) <= 1
- );
- assert.ok(parseInt(line3[2], 10) <= new Date().getTime());
+ assert.deepStrictEqual(lines, [
+ '# HELP counter a test description',
+ '# TYPE counter counter',
+ `counter{counterKey1="labelValue1"} 10 ${mockedTimeMS}`,
+ `counter{counterKey1="labelValue2"} 20 ${mockedTimeMS}`,
+ '',
+ ]);
done();
});
@@ -284,36 +319,6 @@ describe('PrometheusExporter', () => {
});
});
- it('should export multiple labels', done => {
- const counter = meter.createCounter('counter', {
- description: 'a test description',
- }) as CounterMetric;
-
- counter.bind({ counterKey1: 'labelValue1' }).add(10);
- counter.bind({ counterKey1: 'labelValue2' }).add(20);
- meter.collect();
- exporter.export(meter.getBatcher().checkPointSet(), () => {
- http
- .get('http://localhost:9464/metrics', res => {
- res.on('data', chunk => {
- const body = chunk.toString();
- const lines = body.split('\n');
-
- assert.deepStrictEqual(lines, [
- '# HELP counter a test description',
- '# TYPE counter counter',
- `counter{counterKey1="labelValue1"} 10 ${mockedTimeMS}`,
- `counter{counterKey1="labelValue2"} 20 ${mockedTimeMS}`,
- '',
- ]);
-
- done();
- });
- })
- .on('error', errorHandler(done));
- });
- });
-
it('should export a comment if no metrics are registered', done => {
exporter.export([], () => {
http
@@ -336,25 +341,26 @@ describe('PrometheusExporter', () => {
const boundCounter = counter.bind({ key1: 'labelValue1' });
boundCounter.add(10);
- meter.collect();
- exporter.export(meter.getBatcher().checkPointSet(), () => {
- http
- .get('http://localhost:9464/metrics', res => {
- res.on('data', chunk => {
- const body = chunk.toString();
- const lines = body.split('\n');
+ meter.collect().then(() => {
+ exporter.export(meter.getBatcher().checkPointSet(), () => {
+ http
+ .get('http://localhost:9464/metrics', res => {
+ res.on('data', chunk => {
+ const body = chunk.toString();
+ const lines = body.split('\n');
- assert.deepStrictEqual(lines, [
- '# HELP counter description missing',
- '# TYPE counter counter',
- `counter{key1="labelValue1"} 10 ${mockedTimeMS}`,
- '',
- ]);
+ assert.deepStrictEqual(lines, [
+ '# HELP counter description missing',
+ '# TYPE counter counter',
+ `counter{key1="labelValue1"} 10 ${mockedTimeMS}`,
+ '',
+ ]);
- done();
- });
- })
- .on('error', errorHandler(done));
+ done();
+ });
+ })
+ .on('error', errorHandler(done));
+ });
});
});
@@ -362,25 +368,26 @@ describe('PrometheusExporter', () => {
const counter = meter.createCounter('counter.bad-name');
const boundCounter = counter.bind({ key1: 'labelValue1' });
boundCounter.add(10);
- meter.collect();
- exporter.export(meter.getBatcher().checkPointSet(), () => {
- http
- .get('http://localhost:9464/metrics', res => {
- res.on('data', chunk => {
- const body = chunk.toString();
- const lines = body.split('\n');
+ meter.collect().then(() => {
+ exporter.export(meter.getBatcher().checkPointSet(), () => {
+ http
+ .get('http://localhost:9464/metrics', res => {
+ res.on('data', chunk => {
+ const body = chunk.toString();
+ const lines = body.split('\n');
- assert.deepStrictEqual(lines, [
- '# HELP counter_bad_name description missing',
- '# TYPE counter_bad_name counter',
- `counter_bad_name{key1="labelValue1"} 10 ${mockedTimeMS}`,
- '',
- ]);
+ assert.deepStrictEqual(lines, [
+ '# HELP counter_bad_name description missing',
+ '# TYPE counter_bad_name counter',
+ `counter_bad_name{key1="labelValue1"} 10 ${mockedTimeMS}`,
+ '',
+ ]);
- done();
- });
- })
- .on('error', errorHandler(done));
+ done();
+ });
+ })
+ .on('error', errorHandler(done));
+ });
});
});
@@ -390,22 +397,23 @@ describe('PrometheusExporter', () => {
});
counter.bind({ key1: 'labelValue1' }).add(20);
- meter.collect();
- exporter.export(meter.getBatcher().checkPointSet(), () => {
- http
- .get('http://localhost:9464/metrics', res => {
- res.on('data', chunk => {
- assert.deepStrictEqual(chunk.toString().split('\n'), [
- '# HELP counter a test description',
- '# TYPE counter gauge',
- 'counter{key1="labelValue1"} 20',
- '',
- ]);
+ meter.collect().then(() => {
+ exporter.export(meter.getBatcher().checkPointSet(), () => {
+ http
+ .get('http://localhost:9464/metrics', res => {
+ res.on('data', chunk => {
+ assert.deepStrictEqual(chunk.toString().split('\n'), [
+ '# HELP counter a test description',
+ '# TYPE counter gauge',
+ 'counter{key1="labelValue1"} 20',
+ '',
+ ]);
- done();
- });
- })
- .on('error', errorHandler(done));
+ done();
+ });
+ })
+ .on('error', errorHandler(done));
+ });
});
});
});
@@ -435,8 +443,8 @@ describe('PrometheusExporter', () => {
prefix: 'test_prefix',
});
- exporter.startServer(() => {
- meter.collect();
+ exporter.startServer(async () => {
+ await meter.collect();
exporter!.export(meter.getBatcher().checkPointSet(), () => {
http
.get('http://localhost:9464/metrics', res => {
@@ -464,8 +472,8 @@ describe('PrometheusExporter', () => {
port: 8080,
});
- exporter.startServer(() => {
- meter.collect();
+ exporter.startServer(async () => {
+ await meter.collect();
exporter!.export(meter.getBatcher().checkPointSet(), () => {
http
.get('http://localhost:8080/metrics', res => {
@@ -493,8 +501,8 @@ describe('PrometheusExporter', () => {
endpoint: '/test',
});
- exporter.startServer(() => {
- meter.collect();
+ exporter.startServer(async () => {
+ await meter.collect();
exporter!.export(meter.getBatcher().checkPointSet(), () => {
http
.get('http://localhost:9464/test', res => {
diff --git a/packages/opentelemetry-metrics/README.md b/packages/opentelemetry-metrics/README.md
index de6b733eed..f24668528d 100644
--- a/packages/opentelemetry-metrics/README.md
+++ b/packages/opentelemetry-metrics/README.md
@@ -74,37 +74,77 @@ boundCounter.add(Math.random() > 0.5 ? 1 : -1);
```
-### Observable
+### Value Observer
Choose this kind of metric when only last value is important without worry about aggregation
```js
-const { MeterProvider, MetricObservable } = require('@opentelemetry/metrics');
+const { MeterProvider } = require('@opentelemetry/metrics');
-// Initialize the Meter to capture measurements in various ways.
const meter = new MeterProvider().getMeter('your-meter-name');
-const observer = meter.createObserver('metric_name', {
- description: 'Example of a observer'
+meter.createValueObserver('cpu_core_usage', {
+ description: 'Example of a sync observer with callback',
+}, (observerResult) => {
+ observerResult.observe(getRandomValue(), { core: '1' });
+ observerResult.observe(getRandomValue(), { core: '2' });
});
-function getCpuUsage() {
+function getRandomValue() {
return Math.random();
}
-const metricObservable = new MetricObservable();
+```
+
+### Batch Observer
+
+Choose this kind of metric when you need to update multiple observers with the results of a single async calculation.
+
+```js
+const { MeterProvider } = require('@opentelemetry/metrics');
+const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus');
+
+const exporter = new PrometheusExporter(
+ {
+ startServer: true,
+ },
+ () => {
+ console.log('prometheus scrape endpoint: http://localhost:9464/metrics');
+ },
+);
+
+const meter = new MeterProvider({
+ exporter,
+ interval: 3000,
+}).getMeter('example-observer');
+
+const cpuUsageMetric = meter.createValueObserver('cpu_usage_per_app', {
+ description: 'CPU',
+});
-observer.setCallback((observerResult) => {
- // synchronous callback
- observerResult.observe(getCpuUsage, { pid: process.pid, core: '1' });
- // asynchronous callback
- observerResult.observe(metricObservable, { pid: process.pid, core: '2' });
+const MemUsageMetric = meter.createValueObserver('mem_usage_per_app', {
+ description: 'Memory',
});
-// simulate asynchronous operation
-setInterval(()=> {
- metricObservable.next(getCpuUsage());
-}, 2000)
+meter.createBatchObserver('metric_batch_observer', (observerBatchResult) => {
+ getSomeAsyncMetrics().then(metrics => {
+ observerBatchResult.observe({ app: 'myApp' }, [
+ cpuUsageMetric.observation(metrics.value1),
+ MemUsageMetric.observation(metrics.value2)
+ ]);
+ });
+});
+
+function getSomeAsyncMetrics() {
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ resolve({
+ value1: Math.random(),
+ value2: Math.random(),
+ });
+ }, 100)
+ });
+}
```
diff --git a/packages/opentelemetry-metrics/src/BaseObserverMetric.ts b/packages/opentelemetry-metrics/src/BaseObserverMetric.ts
new file mode 100644
index 0000000000..61167183df
--- /dev/null
+++ b/packages/opentelemetry-metrics/src/BaseObserverMetric.ts
@@ -0,0 +1,74 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as api from '@opentelemetry/api';
+import { InstrumentationLibrary } from '@opentelemetry/core';
+import { Resource } from '@opentelemetry/resources';
+import { BoundObserver } from './BoundInstrument';
+import { Batcher } from './export/Batcher';
+import { MetricKind, MetricRecord } from './export/types';
+import { Metric } from './Metric';
+import { ObserverResult } from './ObserverResult';
+
+const NOOP_CALLBACK = () => {};
+
+/**
+ * This is a SDK implementation of Base Observer Metric.
+ * All observers should extend this class
+ */
+export abstract class BaseObserverMetric extends Metric
+ implements api.BaseObserver {
+ protected _callback: (observerResult: api.ObserverResult) => void;
+
+ constructor(
+ name: string,
+ options: api.MetricOptions,
+ private readonly _batcher: Batcher,
+ resource: Resource,
+ metricKind: MetricKind,
+ instrumentationLibrary: InstrumentationLibrary,
+ callback?: (observerResult: api.ObserverResult) => void
+ ) {
+ super(name, options, metricKind, resource, instrumentationLibrary);
+ this._callback = callback || NOOP_CALLBACK;
+ }
+
+ protected _makeInstrument(labels: api.Labels): BoundObserver {
+ return new BoundObserver(
+ labels,
+ this._disabled,
+ this._valueType,
+ this._logger,
+ this._batcher.aggregatorFor(this._descriptor)
+ );
+ }
+
+ getMetricRecord(): Promise {
+ const observerResult = new ObserverResult();
+ this._callback(observerResult);
+ observerResult.values.forEach((value, labels) => {
+ const instrument = this.bind(labels);
+ instrument.update(value);
+ });
+ return super.getMetricRecord();
+ }
+
+ observation(value: number) {
+ return {
+ value,
+ observer: this as BaseObserverMetric,
+ };
+ }
+}
diff --git a/packages/opentelemetry-metrics/src/BatchObserverMetric.ts b/packages/opentelemetry-metrics/src/BatchObserverMetric.ts
new file mode 100644
index 0000000000..17e80634c4
--- /dev/null
+++ b/packages/opentelemetry-metrics/src/BatchObserverMetric.ts
@@ -0,0 +1,91 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as api from '@opentelemetry/api';
+import { InstrumentationLibrary } from '@opentelemetry/core';
+import { Resource } from '@opentelemetry/resources';
+import { BatchObserverResult } from './BatchObserverResult';
+import { BoundObserver } from './BoundInstrument';
+import { Batcher } from './export/Batcher';
+import { MetricKind, MetricRecord } from './export/types';
+import { Metric } from './Metric';
+
+const NOOP_CALLBACK = () => {};
+const MAX_TIMEOUT_UPDATE_MS = 500;
+
+/** This is a SDK implementation of Batch Observer Metric. */
+export class BatchObserverMetric extends Metric
+ implements api.BatchObserver {
+ private _callback: (observerResult: api.BatchObserverResult) => void;
+ private _maxTimeoutUpdateMS: number;
+
+ constructor(
+ name: string,
+ options: api.BatchMetricOptions,
+ private readonly _batcher: Batcher,
+ resource: Resource,
+ instrumentationLibrary: InstrumentationLibrary,
+ callback?: (observerResult: api.BatchObserverResult) => void
+ ) {
+ super(
+ name,
+ options,
+ MetricKind.VALUE_OBSERVER,
+ resource,
+ instrumentationLibrary
+ );
+ this._maxTimeoutUpdateMS =
+ options.maxTimeoutUpdateMS ?? MAX_TIMEOUT_UPDATE_MS;
+ this._callback = callback || NOOP_CALLBACK;
+ }
+
+ protected _makeInstrument(labels: api.Labels): BoundObserver {
+ return new BoundObserver(
+ labels,
+ this._disabled,
+ this._valueType,
+ this._logger,
+ this._batcher.aggregatorFor(this._descriptor)
+ );
+ }
+
+ getMetricRecord(): Promise {
+ this._logger.debug('getMetricRecord - start');
+ return new Promise((resolve, reject) => {
+ const observerResult = new BatchObserverResult();
+
+ // cancels after MAX_TIMEOUT_MS - no more waiting for results
+ const timer = setTimeout(() => {
+ observerResult.cancelled = true;
+ // remove callback to prevent user from updating the values later if
+ // for any reason the observerBatchResult will be referenced
+ observerResult.onObserveCalled();
+ super.getMetricRecord().then(resolve, reject);
+ this._logger.debug('getMetricRecord - timeout');
+ }, this._maxTimeoutUpdateMS);
+
+ // sets callback for each "observe" method
+ observerResult.onObserveCalled(() => {
+ clearTimeout(timer);
+ super.getMetricRecord().then(resolve, reject);
+ this._logger.debug('getMetricRecord - end');
+ });
+
+ // calls the BatchObserverResult callback
+ this._callback(observerResult);
+ });
+ }
+}
diff --git a/packages/opentelemetry-metrics/src/BatchObserverResult.ts b/packages/opentelemetry-metrics/src/BatchObserverResult.ts
new file mode 100644
index 0000000000..17f382360a
--- /dev/null
+++ b/packages/opentelemetry-metrics/src/BatchObserverResult.ts
@@ -0,0 +1,60 @@
+/*
+ * 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';
+
+/**
+ * Implementation of api BatchObserverResult
+ */
+export class BatchObserverResult implements api.BatchObserverResult {
+ private _callback: (() => void) | undefined;
+ private _immediate: NodeJS.Immediate | undefined;
+ /**
+ * Cancels the further updates.
+ * This is used to prevent updating the value of result that took too
+ * long to update. For example to avoid update after timeout.
+ * See {@link BatchObserverMetric.getMetricRecord}
+ */
+ cancelled = false;
+
+ /**
+ * used to save a callback that will be called after the observations are
+ * updated
+ * @param [callback]
+ */
+ onObserveCalled(callback?: () => void) {
+ this._callback = callback;
+ }
+
+ observe(labels: api.Labels, observations: api.Observation[]): void {
+ if (this.cancelled || !this._callback) {
+ return;
+ }
+ observations.forEach(observation => {
+ observation.observer.bind(labels).update(observation.value);
+ });
+ if (!this._immediate) {
+ this._immediate = setImmediate(() => {
+ if (typeof this._callback === 'function') {
+ this._callback();
+ // prevent user from updating the values later if for any reason
+ // the observerBatchResult will be referenced and then try to use
+ this._callback = undefined;
+ }
+ });
+ }
+ }
+}
diff --git a/packages/opentelemetry-metrics/src/CounterMetric.ts b/packages/opentelemetry-metrics/src/CounterMetric.ts
new file mode 100644
index 0000000000..c3f35fb8bb
--- /dev/null
+++ b/packages/opentelemetry-metrics/src/CounterMetric.ts
@@ -0,0 +1,56 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as api from '@opentelemetry/api';
+import { InstrumentationLibrary } from '@opentelemetry/core';
+import { Resource } from '@opentelemetry/resources';
+import { BoundCounter } from './BoundInstrument';
+import { Batcher } from './export/Batcher';
+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 _batcher: Batcher,
+ resource: Resource,
+ instrumentationLibrary: InstrumentationLibrary
+ ) {
+ super(name, options, MetricKind.COUNTER, resource, instrumentationLibrary);
+ }
+ protected _makeInstrument(labels: api.Labels): BoundCounter {
+ return new BoundCounter(
+ labels,
+ this._disabled,
+ this._valueType,
+ this._logger,
+ // @todo: consider to set to CounterSumAggregator always.
+ this._batcher.aggregatorFor(this._descriptor)
+ );
+ }
+
+ /**
+ * Adds the given value to the current value. Values cannot be negative.
+ * @param value the value to add.
+ * @param [labels = {}] key-values pairs that are associated with a specific metric
+ * that you want to record.
+ */
+ add(value: number, labels: api.Labels = {}) {
+ this.bind(labels).add(value);
+ }
+}
diff --git a/packages/opentelemetry-metrics/src/Meter.ts b/packages/opentelemetry-metrics/src/Meter.ts
index 22d4fda93f..0842eefabd 100644
--- a/packages/opentelemetry-metrics/src/Meter.ts
+++ b/packages/opentelemetry-metrics/src/Meter.ts
@@ -17,20 +17,15 @@
import * as api from '@opentelemetry/api';
import { ConsoleLogger, InstrumentationLibrary } from '@opentelemetry/core';
import { Resource } from '@opentelemetry/resources';
+import { BatchObserverMetric } from './BatchObserverMetric';
import { BaseBoundInstrument } from './BoundInstrument';
import { UpDownCounterMetric } from './UpDownCounterMetric';
-import {
- Metric,
- CounterMetric,
- ValueRecorderMetric,
- ObserverMetric,
-} from './Metric';
-import {
- MetricOptions,
- DEFAULT_METRIC_OPTIONS,
- DEFAULT_CONFIG,
- MeterConfig,
-} from './types';
+import { CounterMetric } from './CounterMetric';
+import { MetricRecord } from './export/types';
+import { ValueRecorderMetric } from './ValueRecorderMetric';
+import { Metric } from './Metric';
+import { ValueObserverMetric } from './ValueObserverMetric';
+import { DEFAULT_METRIC_OPTIONS, DEFAULT_CONFIG, MeterConfig } from './types';
import { Batcher, UngroupedBatcher } from './export/Batcher';
import { PushController } from './export/Controller';
import { NoopExporter } from './export/NoopExporter';
@@ -77,7 +72,7 @@ export class Meter implements api.Meter {
);
return api.NOOP_VALUE_RECORDER_METRIC;
}
- const opt: MetricOptions = {
+ const opt: api.MetricOptions = {
logger: this._logger,
...DEFAULT_METRIC_OPTIONS,
absolute: true, // value recorders are defined as absolute by default
@@ -109,7 +104,7 @@ export class Meter implements api.Meter {
);
return api.NOOP_COUNTER_METRIC;
}
- const opt: MetricOptions = {
+ const opt: api.MetricOptions = {
logger: this._logger,
...DEFAULT_METRIC_OPTIONS,
...options,
@@ -145,9 +140,9 @@ export class Meter implements api.Meter {
);
return api.NOOP_COUNTER_METRIC;
}
- const opt: MetricOptions = {
- logger: this._logger,
+ const opt: api.MetricOptions = {
...DEFAULT_METRIC_OPTIONS,
+ logger: this._logger,
...options,
};
const upDownCounter = new UpDownCounterMetric(
@@ -162,31 +157,71 @@ export class Meter implements api.Meter {
}
/**
- * Creates a new observer metric.
+ * Creates a new value observer metric.
* @param name the name of the metric.
* @param [options] the metric options.
+ * @param [callback] the value observer callback
*/
- createObserver(name: string, options?: api.MetricOptions): api.Observer {
+ createValueObserver(
+ name: string,
+ options: api.MetricOptions = {},
+ callback?: (observerResult: api.ObserverResult) => void
+ ): api.ValueObserver {
if (!this._isValidName(name)) {
this._logger.warn(
`Invalid metric name ${name}. Defaulting to noop metric implementation.`
);
- return api.NOOP_OBSERVER_METRIC;
+ return api.NOOP_VALUE_OBSERVER_METRIC;
}
- const opt: MetricOptions = {
+ const opt: api.MetricOptions = {
logger: this._logger,
...DEFAULT_METRIC_OPTIONS,
...options,
};
- const observer = new ObserverMetric(
+ const valueObserver = new ValueObserverMetric(
name,
opt,
this._batcher,
this._resource,
- this._instrumentationLibrary
+ this._instrumentationLibrary,
+ callback
+ );
+ this._registerMetric(name, valueObserver);
+ return valueObserver;
+ }
+
+ /**
+ * Creates a new batch observer metric.
+ * @param name the name of the metric.
+ * @param callback the batch observer callback
+ * @param [options] the metric batch options.
+ */
+ createBatchObserver(
+ name: string,
+ callback: (observerResult: api.BatchObserverResult) => void,
+ options: api.BatchMetricOptions = {}
+ ): api.BatchObserver {
+ if (!this._isValidName(name)) {
+ this._logger.warn(
+ `Invalid metric name ${name}. Defaulting to noop metric implementation.`
+ );
+ return api.NOOP_BATCH_OBSERVER_METRIC;
+ }
+ const opt: api.BatchMetricOptions = {
+ logger: this._logger,
+ ...DEFAULT_METRIC_OPTIONS,
+ ...options,
+ };
+ const batchObserver = new BatchObserverMetric(
+ name,
+ opt,
+ this._batcher,
+ this._resource,
+ this._instrumentationLibrary,
+ callback
);
- this._registerMetric(name, observer);
- return observer;
+ this._registerMetric(name, batchObserver);
+ return batchObserver;
}
/**
@@ -196,11 +231,20 @@ export class Meter implements api.Meter {
* each aggregator belonging to the metrics that were created with this
* meter instance.
*/
- collect() {
- Array.from(this._metrics.values()).forEach(metric => {
- metric.getMetricRecord().forEach(record => {
- this._batcher.process(record);
+ collect(): Promise {
+ return new Promise((resolve, reject) => {
+ const metrics: Promise[] = [];
+ Array.from(this._metrics.values()).forEach(metric => {
+ metrics.push(metric.getMetricRecord());
});
+ Promise.all(metrics)
+ .then(records => {
+ records.forEach(metrics => {
+ metrics.forEach(metric => this._batcher.process(metric));
+ });
+ resolve();
+ })
+ .catch(reject);
});
}
diff --git a/packages/opentelemetry-metrics/src/Metric.ts b/packages/opentelemetry-metrics/src/Metric.ts
index 7dcb3ab52c..6edfc3aa92 100644
--- a/packages/opentelemetry-metrics/src/Metric.ts
+++ b/packages/opentelemetry-metrics/src/Metric.ts
@@ -13,19 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
import * as api from '@opentelemetry/api';
+import { NoopLogger } from '@opentelemetry/core';
import { Resource } from '@opentelemetry/resources';
-import {
- BoundCounter,
- BaseBoundInstrument,
- BoundValueRecorder,
- BoundObserver,
-} from './BoundInstrument';
-import { ObserverResult } from './ObserverResult';
-import { MetricOptions } from './types';
-import { MetricKind, MetricDescriptor, MetricRecord } from './export/types';
-import { Batcher } from './export/Batcher';
+import { BaseBoundInstrument } from './BoundInstrument';
+import { MetricDescriptor, MetricKind, MetricRecord } from './export/types';
import { hashLabels } from './Utils';
import { InstrumentationLibrary } from '@opentelemetry/core';
@@ -40,14 +32,17 @@ export abstract class Metric
constructor(
private readonly _name: string,
- private readonly _options: MetricOptions,
+ private readonly _options: api.MetricOptions,
private readonly _kind: MetricKind,
readonly resource: Resource,
readonly instrumentationLibrary: InstrumentationLibrary
) {
- this._disabled = _options.disabled;
- this._valueType = _options.valueType;
- this._logger = _options.logger;
+ this._disabled = !!_options.disabled;
+ this._valueType =
+ typeof _options.valueType === 'number'
+ ? _options.valueType
+ : api.ValueType.DOUBLE;
+ this._logger = _options.logger ?? new NoopLogger();
this._descriptor = this._getMetricDescriptor();
}
@@ -82,21 +77,25 @@ export abstract class Metric
this._instruments.clear();
}
- getMetricRecord(): MetricRecord[] {
- return Array.from(this._instruments.values()).map(instrument => ({
- descriptor: this._descriptor,
- labels: instrument.getLabels(),
- aggregator: instrument.getAggregator(),
- resource: this.resource,
- instrumentationLibrary: this.instrumentationLibrary,
- }));
+ getMetricRecord(): Promise {
+ return new Promise(resolve => {
+ resolve(
+ Array.from(this._instruments.values()).map(instrument => ({
+ descriptor: this._descriptor,
+ labels: instrument.getLabels(),
+ aggregator: instrument.getAggregator(),
+ resource: this.resource,
+ instrumentationLibrary: this.instrumentationLibrary,
+ }))
+ );
+ });
}
private _getMetricDescriptor(): MetricDescriptor {
return {
name: this._name,
- description: this._options.description,
- unit: this._options.unit,
+ description: this._options.description || '',
+ unit: this._options.unit || '1',
metricKind: this._kind,
valueType: this._valueType,
};
@@ -104,121 +103,3 @@ export abstract class Metric
protected abstract _makeInstrument(labels: api.Labels): T;
}
-
-/** This is a SDK implementation of Counter Metric. */
-export class CounterMetric extends Metric implements api.Counter {
- constructor(
- name: string,
- options: MetricOptions,
- private readonly _batcher: Batcher,
- resource: Resource,
- instrumentationLibrary: InstrumentationLibrary
- ) {
- super(name, options, MetricKind.COUNTER, resource, instrumentationLibrary);
- }
- protected _makeInstrument(labels: api.Labels): BoundCounter {
- return new BoundCounter(
- labels,
- this._disabled,
- this._valueType,
- this._logger,
- // @todo: consider to set to CounterSumAggregator always.
- this._batcher.aggregatorFor(this._descriptor)
- );
- }
-
- /**
- * Adds the given value to the current value. Values cannot be negative.
- * @param value the value to add.
- * @param [labels = {}] key-values pairs that are associated with a specific
- * metric that you want to record.
- */
- add(value: number, labels: api.Labels = {}) {
- this.bind(labels).add(value);
- }
-}
-
-export class ValueRecorderMetric extends Metric
- implements api.ValueRecorder {
- protected readonly _absolute: boolean;
-
- constructor(
- name: string,
- options: MetricOptions,
- private readonly _batcher: Batcher,
- resource: Resource,
- instrumentationLibrary: InstrumentationLibrary
- ) {
- super(
- name,
- options,
- MetricKind.VALUE_RECORDER,
- resource,
- instrumentationLibrary
- );
-
- this._absolute = options.absolute !== undefined ? options.absolute : true; // Absolute default is true
- }
- protected _makeInstrument(labels: api.Labels): BoundValueRecorder {
- return new BoundValueRecorder(
- labels,
- this._disabled,
- this._absolute,
- this._valueType,
- this._logger,
- this._batcher.aggregatorFor(this._descriptor)
- );
- }
-
- record(value: number, labels: api.Labels = {}) {
- this.bind(labels).record(value);
- }
-}
-
-/** This is a SDK implementation of Observer Metric. */
-export class ObserverMetric extends Metric
- implements api.Observer {
- private _observerResult = new ObserverResult();
-
- constructor(
- name: string,
- options: MetricOptions,
- private readonly _batcher: Batcher,
- resource: Resource,
- instrumentationLibrary: InstrumentationLibrary
- ) {
- super(name, options, MetricKind.OBSERVER, resource, instrumentationLibrary);
- }
-
- protected _makeInstrument(labels: api.Labels): BoundObserver {
- return new BoundObserver(
- labels,
- this._disabled,
- this._valueType,
- this._logger,
- this._batcher.aggregatorFor(this._descriptor)
- );
- }
-
- getMetricRecord(): MetricRecord[] {
- this._observerResult.callbackObservers.forEach((callback, labels) => {
- const instrument = this.bind(labels);
- instrument.update(callback());
- });
- return super.getMetricRecord();
- }
-
- /**
- * Sets a callback where user can observe value for certain labels
- * @param callback
- */
- setCallback(callback: (observerResult: api.ObserverResult) => void): void {
- callback(this._observerResult);
- this._observerResult.observers.forEach((observer, labels) => {
- observer.subscribe(value => {
- const instrument = this.bind(labels);
- instrument.update(value);
- });
- });
- }
-}
diff --git a/packages/opentelemetry-metrics/src/MetricObservable.ts b/packages/opentelemetry-metrics/src/MetricObservable.ts
deleted file mode 100644
index f6c751a1e9..0000000000
--- a/packages/opentelemetry-metrics/src/MetricObservable.ts
+++ /dev/null
@@ -1,51 +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';
-
-type Subscriber = (value?: number) => void;
-
-/**
- * Implements the Metric Observable pattern
- */
-export class MetricObservable implements api.MetricObservable {
- private _subscribers: Subscriber[] = [];
-
- next(value: number) {
- for (const subscriber of this._subscribers) {
- subscriber(value);
- }
- }
-
- subscribe(subscriber: Function) {
- if (typeof subscriber === 'function') {
- this._subscribers.push(subscriber as Subscriber);
- }
- }
-
- unsubscribe(subscriber?: Function) {
- if (typeof subscriber === 'function') {
- for (let i = 0, j = this._subscribers.length; i < j; i++) {
- if (this._subscribers[i] === subscriber) {
- this._subscribers.splice(i, 1);
- break;
- }
- }
- } else {
- this._subscribers = [];
- }
- }
-}
diff --git a/packages/opentelemetry-metrics/src/ObserverResult.ts b/packages/opentelemetry-metrics/src/ObserverResult.ts
index 32793dcc9c..60d555641f 100644
--- a/packages/opentelemetry-metrics/src/ObserverResult.ts
+++ b/packages/opentelemetry-metrics/src/ObserverResult.ts
@@ -15,7 +15,6 @@
*/
import {
- MetricObservable,
ObserverResult as TypeObserverResult,
Labels,
} from '@opentelemetry/api';
@@ -24,17 +23,9 @@ import {
* Implementation of {@link TypeObserverResult}
*/
export class ObserverResult implements TypeObserverResult {
- callbackObservers: Map = new Map();
- observers: Map = new Map<
- Labels,
- MetricObservable
- >();
+ values: Map = new Map();
- observe(callback: Function | MetricObservable, labels: Labels): void {
- if (typeof callback === 'function') {
- this.callbackObservers.set(labels, callback);
- } else {
- this.observers.set(labels, callback);
- }
+ observe(value: number, labels: Labels): void {
+ this.values.set(labels, value);
}
}
diff --git a/packages/opentelemetry-metrics/src/UpDownCounterMetric.ts b/packages/opentelemetry-metrics/src/UpDownCounterMetric.ts
index 11a468aa6b..14eb1dc6f3 100644
--- a/packages/opentelemetry-metrics/src/UpDownCounterMetric.ts
+++ b/packages/opentelemetry-metrics/src/UpDownCounterMetric.ts
@@ -18,7 +18,6 @@ import * as api from '@opentelemetry/api';
import { Resource } from '@opentelemetry/resources';
import { InstrumentationLibrary } from '@opentelemetry/core';
import { BoundUpDownCounter } from './BoundInstrument';
-import { MetricOptions } from './types';
import { MetricKind } from './export/types';
import { Batcher } from './export/Batcher';
import { Metric } from './Metric';
@@ -28,7 +27,7 @@ export class UpDownCounterMetric extends Metric
implements api.UpDownCounter {
constructor(
name: string,
- options: MetricOptions,
+ options: api.MetricOptions,
private readonly _batcher: Batcher,
resource: Resource,
instrumentationLibrary: InstrumentationLibrary
diff --git a/packages/opentelemetry-metrics/src/ValueObserverMetric.ts b/packages/opentelemetry-metrics/src/ValueObserverMetric.ts
new file mode 100644
index 0000000000..14ca5418fe
--- /dev/null
+++ b/packages/opentelemetry-metrics/src/ValueObserverMetric.ts
@@ -0,0 +1,44 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as api from '@opentelemetry/api';
+import { InstrumentationLibrary } from '@opentelemetry/core';
+import { Resource } from '@opentelemetry/resources';
+import { BaseObserverMetric } from './BaseObserverMetric';
+import { Batcher } from './export/Batcher';
+import { MetricKind } from './export/types';
+
+/** This is a SDK implementation of Value Observer Metric. */
+export class ValueObserverMetric extends BaseObserverMetric
+ implements api.ValueObserver {
+ constructor(
+ name: string,
+ options: api.MetricOptions,
+ batcher: Batcher,
+ resource: Resource,
+ instrumentationLibrary: InstrumentationLibrary,
+ callback?: (observerResult: api.ObserverResult) => void
+ ) {
+ super(
+ name,
+ options,
+ batcher,
+ resource,
+ MetricKind.VALUE_OBSERVER,
+ instrumentationLibrary,
+ callback
+ );
+ }
+}
diff --git a/packages/opentelemetry-metrics/src/ValueRecorderMetric.ts b/packages/opentelemetry-metrics/src/ValueRecorderMetric.ts
new file mode 100644
index 0000000000..e23e3daa72
--- /dev/null
+++ b/packages/opentelemetry-metrics/src/ValueRecorderMetric.ts
@@ -0,0 +1,61 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as api from '@opentelemetry/api';
+import { InstrumentationLibrary } from '@opentelemetry/core';
+import { Resource } from '@opentelemetry/resources';
+import { BoundValueRecorder } from './BoundInstrument';
+import { Batcher } from './export/Batcher';
+import { MetricKind } from './export/types';
+import { Metric } from './Metric';
+
+/** This is a SDK implementation of Value Recorder Metric. */
+export class ValueRecorderMetric extends Metric
+ implements api.ValueRecorder {
+ protected readonly _absolute: boolean;
+
+ constructor(
+ name: string,
+ options: api.MetricOptions,
+ private readonly _batcher: Batcher,
+ resource: Resource,
+ instrumentationLibrary: InstrumentationLibrary
+ ) {
+ super(
+ name,
+ options,
+ MetricKind.VALUE_RECORDER,
+ resource,
+ instrumentationLibrary
+ );
+
+ this._absolute = options.absolute !== undefined ? options.absolute : true; // Absolute default is true
+ }
+ protected _makeInstrument(labels: api.Labels): BoundValueRecorder {
+ return new BoundValueRecorder(
+ labels,
+ this._disabled,
+ this._absolute,
+ this._valueType,
+ this._logger,
+ this._batcher.aggregatorFor(this._descriptor)
+ );
+ }
+
+ record(value: number, labels: api.Labels = {}) {
+ this.bind(labels).record(value);
+ }
+}
diff --git a/packages/opentelemetry-metrics/src/export/Batcher.ts b/packages/opentelemetry-metrics/src/export/Batcher.ts
index de7d32c1fe..9bcf574d02 100644
--- a/packages/opentelemetry-metrics/src/export/Batcher.ts
+++ b/packages/opentelemetry-metrics/src/export/Batcher.ts
@@ -14,11 +14,7 @@
* limitations under the License.
*/
-import {
- CounterSumAggregator,
- ValueRecorderExactAggregator,
- ObserverAggregator,
-} from './aggregators';
+import * as aggregators from './aggregators';
import {
MetricRecord,
MetricKind,
@@ -56,11 +52,14 @@ export class UngroupedBatcher extends Batcher {
switch (metricDescriptor.metricKind) {
case MetricKind.COUNTER:
case MetricKind.UP_DOWN_COUNTER:
- return new CounterSumAggregator();
- case MetricKind.OBSERVER:
- return new ObserverAggregator();
+ case MetricKind.SUM_OBSERVER:
+ case MetricKind.UP_DOWN_SUM_OBSERVER:
+ return new aggregators.SumAggregator();
+ case MetricKind.VALUE_RECORDER:
+ case MetricKind.VALUE_OBSERVER:
+ return new aggregators.LastValueAggregator();
default:
- return new ValueRecorderExactAggregator();
+ return new aggregators.MinMaxSumCountAggregator();
}
}
diff --git a/packages/opentelemetry-metrics/src/export/aggregators/observer.ts b/packages/opentelemetry-metrics/src/export/aggregators/LastValue.ts
similarity index 88%
rename from packages/opentelemetry-metrics/src/export/aggregators/observer.ts
rename to packages/opentelemetry-metrics/src/export/aggregators/LastValue.ts
index 90b193896a..20c049c842 100644
--- a/packages/opentelemetry-metrics/src/export/aggregators/observer.ts
+++ b/packages/opentelemetry-metrics/src/export/aggregators/LastValue.ts
@@ -18,8 +18,8 @@ import { Aggregator, Point } from '../types';
import { HrTime } from '@opentelemetry/api';
import { hrTime } from '@opentelemetry/core';
-/** Basic aggregator for Observer which keeps the last recorded value. */
-export class ObserverAggregator implements Aggregator {
+/** Basic aggregator for LastValue which keeps the last recorded value. */
+export class LastValueAggregator implements Aggregator {
private _current: number = 0;
private _lastUpdateTime: HrTime = [0, 0];
diff --git a/packages/opentelemetry-metrics/src/export/aggregators/ValueRecorderExact.ts b/packages/opentelemetry-metrics/src/export/aggregators/MinMaxSumCount.ts
similarity index 95%
rename from packages/opentelemetry-metrics/src/export/aggregators/ValueRecorderExact.ts
rename to packages/opentelemetry-metrics/src/export/aggregators/MinMaxSumCount.ts
index 5603ee45e7..9be0fe8350 100644
--- a/packages/opentelemetry-metrics/src/export/aggregators/ValueRecorderExact.ts
+++ b/packages/opentelemetry-metrics/src/export/aggregators/MinMaxSumCount.ts
@@ -20,7 +20,7 @@ import { hrTime } from '@opentelemetry/core';
import { Distribution } from '../types';
/** Basic aggregator keeping all raw values (events, sum, max and min). */
-export class ValueRecorderExactAggregator implements Aggregator {
+export class MinMaxSumCountAggregator implements Aggregator {
private _distribution: Distribution;
private _lastUpdateTime: HrTime = [0, 0];
diff --git a/packages/opentelemetry-metrics/src/export/aggregators/countersum.ts b/packages/opentelemetry-metrics/src/export/aggregators/Sum.ts
similarity index 95%
rename from packages/opentelemetry-metrics/src/export/aggregators/countersum.ts
rename to packages/opentelemetry-metrics/src/export/aggregators/Sum.ts
index 182e61ccb4..0475320e5c 100644
--- a/packages/opentelemetry-metrics/src/export/aggregators/countersum.ts
+++ b/packages/opentelemetry-metrics/src/export/aggregators/Sum.ts
@@ -19,7 +19,7 @@ import { HrTime } from '@opentelemetry/api';
import { hrTime } from '@opentelemetry/core';
/** Basic aggregator which calculates a Sum from individual measurements. */
-export class CounterSumAggregator implements Aggregator {
+export class SumAggregator implements Aggregator {
private _current: number = 0;
private _lastUpdateTime: HrTime = [0, 0];
diff --git a/packages/opentelemetry-metrics/src/export/aggregators/index.ts b/packages/opentelemetry-metrics/src/export/aggregators/index.ts
index a46b10a624..0d3f8155b7 100644
--- a/packages/opentelemetry-metrics/src/export/aggregators/index.ts
+++ b/packages/opentelemetry-metrics/src/export/aggregators/index.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-export * from './countersum';
-export * from './observer';
-export * from './ValueRecorderExact';
export * from './histogram';
+export * from './MinMaxSumCount';
+export * from './LastValue';
+export * from './Sum';
diff --git a/packages/opentelemetry-metrics/src/export/types.ts b/packages/opentelemetry-metrics/src/export/types.ts
index 295b1ebaf1..192cabd414 100644
--- a/packages/opentelemetry-metrics/src/export/types.ts
+++ b/packages/opentelemetry-metrics/src/export/types.ts
@@ -23,7 +23,6 @@ export enum MetricKind {
COUNTER,
UP_DOWN_COUNTER,
VALUE_RECORDER,
- OBSERVER, // @TODO remove later #1146
SUM_OBSERVER,
UP_DOWN_SUM_OBSERVER,
VALUE_OBSERVER,
diff --git a/packages/opentelemetry-metrics/src/index.ts b/packages/opentelemetry-metrics/src/index.ts
index cf3eff3026..0be805519a 100644
--- a/packages/opentelemetry-metrics/src/index.ts
+++ b/packages/opentelemetry-metrics/src/index.ts
@@ -15,11 +15,14 @@
*/
export * from './BoundInstrument';
+export * from './CounterMetric';
+export * from './ValueRecorderMetric';
export * from './Meter';
export * from './MeterProvider';
export * from './Metric';
-export * from './MetricObservable';
+export * from './ValueObserverMetric';
export * from './export/aggregators';
+export * from './export/Batcher';
export * from './export/ConsoleMetricExporter';
export * from './export/types';
export * from './UpDownCounterMetric';
diff --git a/packages/opentelemetry-metrics/src/types.ts b/packages/opentelemetry-metrics/src/types.ts
index ab05a53e83..0334bac69c 100644
--- a/packages/opentelemetry-metrics/src/types.ts
+++ b/packages/opentelemetry-metrics/src/types.ts
@@ -15,42 +15,15 @@
*/
import { LogLevel } from '@opentelemetry/core';
-import { Logger, ValueType } from '@opentelemetry/api';
+import * as api from '@opentelemetry/api';
import { MetricExporter } from './export/types';
import { Resource } from '@opentelemetry/resources';
import { Batcher } from './export/Batcher';
-/** Options needed for SDK metric creation. */
-export interface MetricOptions {
- /** The name of the component that reports the Metric. */
- component?: string;
-
- /** The description of the Metric. */
- description: string;
-
- /** The unit of the Metric values. */
- unit: string;
-
- /** The map of constant labels for the Metric. */
- constantLabels?: Map;
-
- /** Indicates the metric is a verbose metric that is disabled by default. */
- disabled: boolean;
-
- /** (Measure only) Asserts that this metric will only accept non-negative values. */
- absolute: boolean;
-
- /** User provided logger. */
- logger: Logger;
-
- /** Indicates the type of the recorded value. */
- valueType: ValueType;
-}
-
/** MeterConfig provides an interface for configuring a Meter. */
export interface MeterConfig {
/** User provided logger. */
- logger?: Logger;
+ logger?: api.Logger;
/** level of logger. */
logLevel?: LogLevel;
@@ -79,5 +52,5 @@ export const DEFAULT_METRIC_OPTIONS = {
absolute: false,
description: '',
unit: '1',
- valueType: ValueType.DOUBLE,
+ valueType: api.ValueType.DOUBLE,
};
diff --git a/packages/opentelemetry-metrics/test/Batcher.test.ts b/packages/opentelemetry-metrics/test/Batcher.test.ts
index b5daf3b454..f2d3e2d360 100644
--- a/packages/opentelemetry-metrics/test/Batcher.test.ts
+++ b/packages/opentelemetry-metrics/test/Batcher.test.ts
@@ -35,11 +35,11 @@ describe('Batcher', () => {
barCounter = counter.bind({ key: 'bar' });
});
- it('should process a batch', () => {
+ it('should process a batch', async () => {
fooCounter.add(1);
barCounter.add(1);
barCounter.add(2);
- meter.collect();
+ await meter.collect();
const checkPointSet = meter.getBatcher().checkPointSet();
assert.strictEqual(checkPointSet.length, 2);
for (const record of checkPointSet) {
diff --git a/packages/opentelemetry-metrics/test/Meter.test.ts b/packages/opentelemetry-metrics/test/Meter.test.ts
index 62e8860c0f..2cd8c42937 100644
--- a/packages/opentelemetry-metrics/test/Meter.test.ts
+++ b/packages/opentelemetry-metrics/test/Meter.test.ts
@@ -23,21 +23,17 @@ import {
Sum,
MeterProvider,
ValueRecorderMetric,
- Distribution,
- ObserverMetric,
+ ValueObserverMetric,
MetricRecord,
Aggregator,
- MetricObservable,
MetricDescriptor,
+ LastValueAggregator,
UpDownCounterMetric,
} from '../src';
import * as api from '@opentelemetry/api';
import { NoopLogger, hrTime, hrTimeToNanoseconds } from '@opentelemetry/core';
-import {
- CounterSumAggregator,
- ObserverAggregator,
-} from '../src/export/aggregators';
-import { ValueType } from '@opentelemetry/api';
+import { BatchObserverResult } from '../src/BatchObserverResult';
+import { SumAggregator } from '../src/export/aggregators';
import { Resource } from '@opentelemetry/resources';
import { hashLabels } from '../src/Utils';
import { Batcher } from '../src/export/Batcher';
@@ -71,10 +67,10 @@ describe('Meter', () => {
assert.ok(counter instanceof Metric);
});
- it('should be able to call add() directly on counter', () => {
+ it('should be able to call add() directly on counter', async () => {
const counter = meter.createCounter('name') as CounterMetric;
counter.add(10, labels);
- meter.collect();
+ await meter.collect();
const [record1] = meter.getBatcher().checkPointSet();
assert.strictEqual(record1.aggregator.toPoint().value, 10);
@@ -92,46 +88,46 @@ describe('Meter', () => {
);
});
- it('should be able to call add with no labels', () => {
+ it('should be able to call add with no labels', async () => {
const counter = meter.createCounter('name', {
description: 'desc',
unit: '1',
disabled: false,
});
counter.add(1);
- meter.collect();
+ await meter.collect();
const [record1] = meter.getBatcher().checkPointSet();
assert.strictEqual(record1.aggregator.toPoint().value, 1);
});
- it('should pipe through resource', () => {
+ 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] = counter.getMetricRecord();
+ const [record] = await counter.getMetricRecord();
assert.ok(record.resource instanceof Resource);
});
- it('should pipe through instrumentation library', () => {
+ 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] = counter.getMetricRecord();
+ const [record] = await counter.getMetricRecord();
const { name, version } = record.instrumentationLibrary;
assert.strictEqual(name, 'test-meter');
assert.strictEqual(version, '*');
});
describe('.bind()', () => {
- it('should create a counter instrument', () => {
+ it('should create a counter instrument', async () => {
const counter = meter.createCounter('name') as CounterMetric;
const boundCounter = counter.bind(labels);
boundCounter.add(10);
- meter.collect();
+ await meter.collect();
const [record1] = meter.getBatcher().checkPointSet();
assert.strictEqual(record1.aggregator.toPoint().value, 10);
@@ -143,16 +139,16 @@ describe('Meter', () => {
const counter = meter.createCounter('name') as CounterMetric;
const boundCounter = counter.bind(labels);
boundCounter.add(20);
- assert.ok(boundCounter.getAggregator() instanceof CounterSumAggregator);
+ assert.ok(boundCounter.getAggregator() instanceof SumAggregator);
assert.strictEqual(boundCounter.getLabels(), labels);
});
- it('should add positive values only', () => {
+ it('should add positive values only', async () => {
const counter = meter.createCounter('name') as CounterMetric;
const boundCounter = counter.bind(labels);
boundCounter.add(10);
assert.strictEqual(meter.getBatcher().checkPointSet().length, 0);
- meter.collect();
+ await meter.collect();
const [record1] = meter.getBatcher().checkPointSet();
assert.strictEqual(record1.aggregator.toPoint().value, 10);
@@ -160,24 +156,24 @@ describe('Meter', () => {
assert.strictEqual(record1.aggregator.toPoint().value, 10);
});
- it('should not add the instrument data when disabled', () => {
+ it('should not add the instrument data when disabled', async () => {
const counter = meter.createCounter('name', {
disabled: true,
}) as CounterMetric;
const boundCounter = counter.bind(labels);
boundCounter.add(10);
- meter.collect();
+ await meter.collect();
const [record1] = meter.getBatcher().checkPointSet();
assert.strictEqual(record1.aggregator.toPoint().value, 0);
});
- it('should return same instrument on same label values', () => {
+ it('should return same instrument on same label values', async () => {
const counter = meter.createCounter('name') as CounterMetric;
const boundCounter = counter.bind(labels);
boundCounter.add(10);
const boundCounter1 = counter.bind(labels);
boundCounter1.add(10);
- meter.collect();
+ await meter.collect();
const [record1] = meter.getBatcher().checkPointSet();
assert.strictEqual(record1.aggregator.toPoint().value, 20);
@@ -212,7 +208,7 @@ describe('Meter', () => {
});
describe('.registerMetric()', () => {
- it('skip already registered Metric', () => {
+ it('skip already registered Metric', async () => {
const counter1 = meter.createCounter('name1') as CounterMetric;
counter1.bind(labels).add(10);
@@ -222,7 +218,7 @@ describe('Meter', () => {
}) as CounterMetric;
counter2.bind(labels).add(500);
- meter.collect();
+ await meter.collect();
const record = meter.getBatcher().checkPointSet();
assert.strictEqual(record.length, 1);
@@ -231,7 +227,7 @@ describe('Meter', () => {
metricKind: MetricKind.COUNTER,
name: 'name1',
unit: '1',
- valueType: ValueType.DOUBLE,
+ valueType: api.ValueType.DOUBLE,
});
assert.strictEqual(record[0].aggregator.toPoint().value, 10);
});
@@ -283,10 +279,10 @@ describe('Meter', () => {
assert.ok(upDownCounter instanceof Metric);
});
- it('should be able to call add() directly on UpDownCounter', () => {
+ it('should be able to call add() directly on UpDownCounter', async () => {
const upDownCounter = meter.createUpDownCounter('name');
upDownCounter.add(10, labels);
- meter.collect();
+ await meter.collect();
const [record1] = meter.getBatcher().checkPointSet();
assert.strictEqual(record1.aggregator.toPoint().value, 10);
@@ -304,19 +300,19 @@ describe('Meter', () => {
);
});
- it('should be able to call add with no labels', () => {
+ it('should be able to call add with no labels', async () => {
const upDownCounter = meter.createUpDownCounter('name', {
description: 'desc',
unit: '1',
disabled: false,
});
upDownCounter.add(1);
- meter.collect();
+ await meter.collect();
const [record1] = meter.getBatcher().checkPointSet();
assert.strictEqual(record1.aggregator.toPoint().value, 1);
});
- it('should pipe through resource', () => {
+ it('should pipe through resource', async () => {
const upDownCounter = meter.createUpDownCounter(
'name'
) as UpDownCounterMetric;
@@ -324,16 +320,16 @@ describe('Meter', () => {
upDownCounter.add(1, { foo: 'bar' });
- const [record] = upDownCounter.getMetricRecord();
+ const [record] = await upDownCounter.getMetricRecord();
assert.ok(record.resource instanceof Resource);
});
describe('.bind()', () => {
- it('should create a UpDownCounter instrument', () => {
+ it('should create a UpDownCounter instrument', async () => {
const upDownCounter = meter.createUpDownCounter('name');
const boundCounter = upDownCounter.bind(labels);
boundCounter.add(10);
- meter.collect();
+ await meter.collect();
const [record1] = meter.getBatcher().checkPointSet();
assert.strictEqual(record1.aggregator.toPoint().value, 10);
@@ -347,28 +343,28 @@ describe('Meter', () => {
) as UpDownCounterMetric;
const boundCounter = upDownCounter.bind(labels);
boundCounter.add(20);
- assert.ok(boundCounter.getAggregator() instanceof CounterSumAggregator);
+ assert.ok(boundCounter.getAggregator() instanceof SumAggregator);
assert.strictEqual(boundCounter.getLabels(), labels);
});
- it('should not add the instrument data when disabled', () => {
+ it('should not add the instrument data when disabled', async () => {
const upDownCounter = meter.createUpDownCounter('name', {
disabled: true,
});
const boundCounter = upDownCounter.bind(labels);
boundCounter.add(10);
- meter.collect();
+ await meter.collect();
const [record1] = meter.getBatcher().checkPointSet();
assert.strictEqual(record1.aggregator.toPoint().value, 0);
});
- it('should return same instrument on same label values', () => {
+ it('should return same instrument on same label values', async () => {
const upDownCounter = meter.createUpDownCounter('name');
const boundCounter = upDownCounter.bind(labels);
boundCounter.add(10);
const boundCounter1 = upDownCounter.bind(labels);
boundCounter1.add(10);
- meter.collect();
+ await meter.collect();
const [record1] = meter.getBatcher().checkPointSet();
assert.strictEqual(record1.aggregator.toPoint().value, 20);
@@ -407,7 +403,7 @@ describe('Meter', () => {
});
describe('.registerMetric()', () => {
- it('skip already registered Metric', () => {
+ it('skip already registered Metric', async () => {
const counter1 = meter.createCounter('name1') as CounterMetric;
counter1.bind(labels).add(10);
@@ -417,7 +413,7 @@ describe('Meter', () => {
}) as CounterMetric;
counter2.bind(labels).add(500);
- meter.collect();
+ await meter.collect();
const record = meter.getBatcher().checkPointSet();
assert.strictEqual(record.length, 1);
@@ -426,7 +422,7 @@ describe('Meter', () => {
metricKind: MetricKind.COUNTER,
name: 'name1',
unit: '1',
- valueType: ValueType.DOUBLE,
+ valueType: api.ValueType.DOUBLE,
});
assert.strictEqual(record[0].aggregator.toPoint().value, 10);
});
@@ -501,7 +497,7 @@ describe('Meter', () => {
);
});
- it('should pipe through resource', () => {
+ it('should pipe through resource', async () => {
const valueRecorder = meter.createValueRecorder(
'name'
) as ValueRecorderMetric;
@@ -509,11 +505,11 @@ describe('Meter', () => {
valueRecorder.record(1, { foo: 'bar' });
- const [record] = valueRecorder.getMetricRecord();
+ const [record] = await valueRecorder.getMetricRecord();
assert.ok(record.resource instanceof Resource);
});
- it('should pipe through instrumentation library', () => {
+ it('should pipe through instrumentation library', async () => {
const valueRecorder = meter.createValueRecorder(
'name'
) as ValueRecorderMetric;
@@ -521,7 +517,7 @@ describe('Meter', () => {
valueRecorder.record(1, { foo: 'bar' });
- const [record] = valueRecorder.getMetricRecord();
+ const [record] = await valueRecorder.getMetricRecord();
const { name, version } = record.instrumentationLibrary;
assert.strictEqual(name, 'test-meter');
assert.strictEqual(version, '*');
@@ -559,70 +555,53 @@ describe('Meter', () => {
assert.doesNotThrow(() => boundValueRecorder.record(10));
});
- it('should not accept negative values by default', () => {
+ it('should not accept negative values by default', async () => {
const valueRecorder = meter.createValueRecorder('name');
const boundValueRecorder = valueRecorder.bind(labels);
boundValueRecorder.record(-10);
- meter.collect();
+ await meter.collect();
const [record1] = meter.getBatcher().checkPointSet();
- assert.deepStrictEqual(
- record1.aggregator.toPoint().value as Distribution,
- {
- count: 0,
- max: -Infinity,
- min: Infinity,
- sum: 0,
- }
- );
+ assert.deepStrictEqual(record1.aggregator.toPoint().value as number, 0);
});
- it('should not set the instrument data when disabled', () => {
+ it('should not set the instrument data when disabled', async () => {
const valueRecorder = meter.createValueRecorder('name', {
disabled: true,
}) as ValueRecorderMetric;
const boundValueRecorder = valueRecorder.bind(labels);
boundValueRecorder.record(10);
- meter.collect();
+ await meter.collect();
const [record1] = meter.getBatcher().checkPointSet();
- assert.deepStrictEqual(
- record1.aggregator.toPoint().value as Distribution,
- {
- count: 0,
- max: -Infinity,
- min: Infinity,
- sum: 0,
- }
- );
- });
-
- it('should accept negative (and positive) values when absolute is set to false', () => {
- const valueRecorder = meter.createValueRecorder('name', {
- absolute: false,
- });
- const boundValueRecorder = valueRecorder.bind(labels);
- boundValueRecorder.record(-10);
- boundValueRecorder.record(50);
-
- meter.collect();
- const [record1] = meter.getBatcher().checkPointSet();
- assert.deepStrictEqual(
- record1.aggregator.toPoint().value as Distribution,
- {
- count: 2,
- max: 50,
- min: -10,
- sum: 40,
- }
- );
- assert.ok(
- hrTimeToNanoseconds(record1.aggregator.toPoint().timestamp) >
- hrTimeToNanoseconds(performanceTimeOrigin)
- );
- });
+ assert.deepStrictEqual(record1.aggregator.toPoint().value as number, 0);
+ });
+
+ it(
+ 'should accept negative (and positive) values when absolute is set' +
+ ' to false',
+ async () => {
+ const valueRecorder = meter.createValueRecorder('name', {
+ absolute: false,
+ });
+ const boundValueRecorder = valueRecorder.bind(labels);
+ boundValueRecorder.record(-10);
+ boundValueRecorder.record(50);
+
+ await meter.collect();
+ const [record1] = meter.getBatcher().checkPointSet();
+ assert.deepStrictEqual(
+ record1.aggregator.toPoint().value as number,
+ 50
+ );
+ assert.ok(
+ hrTimeToNanoseconds(record1.aggregator.toPoint().timestamp) >
+ hrTimeToNanoseconds(performanceTimeOrigin)
+ );
+ }
+ );
- it('should return same instrument on same label values', () => {
+ it('should return same instrument on same label values', async () => {
const valueRecorder = meter.createValueRecorder(
'name'
) as ValueRecorderMetric;
@@ -630,16 +609,11 @@ describe('Meter', () => {
boundValueRecorder1.record(10);
const boundValueRecorder2 = valueRecorder.bind(labels);
boundValueRecorder2.record(100);
- meter.collect();
+ await meter.collect();
const [record1] = meter.getBatcher().checkPointSet();
assert.deepStrictEqual(
- record1.aggregator.toPoint().value as Distribution,
- {
- count: 2,
- max: 100,
- min: 10,
- sum: 110,
- }
+ record1.aggregator.toPoint().value as number,
+ 100
);
assert.strictEqual(boundValueRecorder1, boundValueRecorder2);
});
@@ -676,52 +650,48 @@ describe('Meter', () => {
});
});
- describe('#observer', () => {
- it('should create an observer', () => {
- const valueRecorder = meter.createObserver('name') as ObserverMetric;
- assert.ok(valueRecorder instanceof Metric);
+ describe('#valueObserver', () => {
+ it('should create a value observer', () => {
+ const valueObserver = meter.createValueObserver(
+ 'name'
+ ) as ValueObserverMetric;
+ assert.ok(valueObserver instanceof Metric);
});
it('should create observer with options', () => {
- const valueRecorder = meter.createObserver('name', {
+ const valueObserver = meter.createValueObserver('name', {
description: 'desc',
unit: '1',
disabled: false,
- }) as ObserverMetric;
- assert.ok(valueRecorder instanceof Metric);
- });
-
- it('should set callback and observe value ', () => {
- const valueRecorder = meter.createObserver('name', {
- description: 'desc',
- }) as ObserverMetric;
+ }) as ValueObserverMetric;
+ assert.ok(valueObserver instanceof Metric);
+ });
+
+ it('should set callback and observe value ', async () => {
+ const valueRecorder = meter.createValueObserver(
+ 'name',
+ {
+ description: 'desc',
+ },
+ (observerResult: api.ObserverResult) => {
+ observerResult.observe(getCpuUsage(), { pid: '123', core: '1' });
+ observerResult.observe(getCpuUsage(), { pid: '123', core: '2' });
+ observerResult.observe(getCpuUsage(), { pid: '123', core: '3' });
+ observerResult.observe(getCpuUsage(), { pid: '123', core: '4' });
+ }
+ ) as ValueObserverMetric;
function getCpuUsage() {
return Math.random();
}
- const metricObservable = new MetricObservable();
-
- valueRecorder.setCallback((observerResult: api.ObserverResult) => {
- observerResult.observe(getCpuUsage, { pid: '123', core: '1' });
- observerResult.observe(getCpuUsage, { pid: '123', core: '2' });
- observerResult.observe(getCpuUsage, { pid: '123', core: '3' });
- observerResult.observe(getCpuUsage, { pid: '123', core: '4' });
- observerResult.observe(metricObservable, { pid: '123', core: '5' });
- });
-
- metricObservable.next(0.123);
+ const metricRecords: MetricRecord[] = await valueRecorder.getMetricRecord();
+ assert.strictEqual(metricRecords.length, 4);
- const metricRecords: MetricRecord[] = valueRecorder.getMetricRecord();
- assert.strictEqual(metricRecords.length, 5);
-
- const metric5 = metricRecords[0];
- assert.strictEqual(hashLabels(metric5.labels), '|#core:5,pid:123');
-
- const metric1 = metricRecords[1];
- const metric2 = metricRecords[2];
- const metric3 = metricRecords[3];
- const metric4 = metricRecords[4];
+ const metric1 = metricRecords[0];
+ const metric2 = metricRecords[1];
+ const metric3 = metricRecords[2];
+ const metric4 = metricRecords[3];
assert.strictEqual(hashLabels(metric1.labels), '|#core:1,pid:123');
assert.strictEqual(hashLabels(metric2.labels), '|#core:2,pid:123');
assert.strictEqual(hashLabels(metric3.labels), '|#core:3,pid:123');
@@ -731,30 +701,187 @@ describe('Meter', () => {
ensureMetric(metric2);
ensureMetric(metric3);
ensureMetric(metric4);
- ensureMetric(metric5);
});
- it('should pipe through resource', () => {
- const observer = meter.createObserver('name') as ObserverMetric;
- assert.ok(observer.resource instanceof Resource);
+ it('should pipe through resource', async () => {
+ const valueObserver = meter.createValueObserver('name', {}, result => {
+ result.observe(42, { foo: 'bar' });
+ }) as ValueObserverMetric;
+ assert.ok(valueObserver.resource instanceof Resource);
- observer.setCallback(result => {
- result.observe(() => 42, { foo: 'bar' });
- });
-
- const [record] = observer.getMetricRecord();
+ const [record] = await valueObserver.getMetricRecord();
assert.ok(record.resource instanceof Resource);
});
+ });
- it('should pipe through instrumentation library', () => {
- const observer = meter.createObserver('name') as ObserverMetric;
- assert.ok(observer.instrumentationLibrary);
+ describe('#batchObserver', () => {
+ it('should create a batch observer', () => {
+ const measure = meter.createBatchObserver('name', () => {});
+ assert.ok(measure instanceof Metric);
+ });
- observer.setCallback(result => {
- result.observe(() => 42, { foo: 'bar' });
+ it('should create batch observer with options', () => {
+ const measure = meter.createBatchObserver('name', () => {}, {
+ description: 'desc',
+ unit: '1',
+ disabled: false,
+ maxTimeoutUpdateMS: 100,
});
+ assert.ok(measure instanceof Metric);
+ });
+
+ it('should use callback to observe values ', async () => {
+ const tempMetric = meter.createValueObserver('cpu_temp_per_app', {
+ description: 'desc',
+ }) as ValueObserverMetric;
+
+ const cpuUsageMetric = meter.createValueObserver('cpu_usage_per_app', {
+ description: 'desc',
+ }) as ValueObserverMetric;
+
+ meter.createBatchObserver(
+ 'metric_batch_observer',
+ observerBatchResult => {
+ interface StatItem {
+ usage: number;
+ temp: number;
+ }
+
+ interface Stat {
+ name: string;
+ core1: StatItem;
+ core2: StatItem;
+ }
+
+ function someAsyncMetrics() {
+ return new Promise(resolve => {
+ const stats: Stat[] = [
+ {
+ name: 'app1',
+ core1: { usage: 2.1, temp: 67 },
+ core2: { usage: 3.1, temp: 69 },
+ },
+ {
+ name: 'app2',
+ core1: { usage: 1.2, temp: 67 },
+ core2: { usage: 4.5, temp: 69 },
+ },
+ ];
+ resolve(stats);
+ });
+ }
+
+ Promise.all([
+ someAsyncMetrics(),
+ // simulate waiting
+ new Promise((resolve, reject) => {
+ setTimeout(resolve, 1);
+ }),
+ ]).then((stats: unknown[]) => {
+ const apps = (stats[0] as unknown) as Stat[];
+ apps.forEach(app => {
+ observerBatchResult.observe({ app: app.name, core: '1' }, [
+ tempMetric.observation(app.core1.temp),
+ cpuUsageMetric.observation(app.core1.usage),
+ ]);
+ observerBatchResult.observe({ app: app.name, core: '2' }, [
+ tempMetric.observation(app.core2.temp),
+ cpuUsageMetric.observation(app.core2.usage),
+ ]);
+ });
+ });
+ }
+ );
- const [record] = observer.getMetricRecord();
+ await meter.collect();
+
+ const tempMetricRecords: MetricRecord[] = await tempMetric.getMetricRecord();
+ const cpuUsageMetricRecords: MetricRecord[] = await cpuUsageMetric.getMetricRecord();
+ assert.strictEqual(tempMetricRecords.length, 4);
+ assert.strictEqual(cpuUsageMetricRecords.length, 4);
+
+ const metric1 = tempMetricRecords[0];
+ const metric2 = tempMetricRecords[1];
+ const metric3 = tempMetricRecords[2];
+ const metric4 = tempMetricRecords[3];
+ assert.strictEqual(hashLabels(metric1.labels), '|#app:app1,core:1');
+ assert.strictEqual(hashLabels(metric2.labels), '|#app:app1,core:2');
+ assert.strictEqual(hashLabels(metric3.labels), '|#app:app2,core:1');
+ assert.strictEqual(hashLabels(metric4.labels), '|#app:app2,core:2');
+
+ ensureMetric(metric1, 'cpu_temp_per_app', 67);
+ ensureMetric(metric2, 'cpu_temp_per_app', 69);
+ ensureMetric(metric3, 'cpu_temp_per_app', 67);
+ ensureMetric(metric4, 'cpu_temp_per_app', 69);
+
+ const metric5 = cpuUsageMetricRecords[0];
+ const metric6 = cpuUsageMetricRecords[1];
+ const metric7 = cpuUsageMetricRecords[2];
+ const metric8 = cpuUsageMetricRecords[3];
+ assert.strictEqual(hashLabels(metric1.labels), '|#app:app1,core:1');
+ assert.strictEqual(hashLabels(metric2.labels), '|#app:app1,core:2');
+ assert.strictEqual(hashLabels(metric3.labels), '|#app:app2,core:1');
+ assert.strictEqual(hashLabels(metric4.labels), '|#app:app2,core:2');
+
+ ensureMetric(metric5, 'cpu_usage_per_app', 2.1);
+ ensureMetric(metric6, 'cpu_usage_per_app', 3.1);
+ ensureMetric(metric7, 'cpu_usage_per_app', 1.2);
+ ensureMetric(metric8, 'cpu_usage_per_app', 4.5);
+ });
+
+ it('should not observe values when timeout', done => {
+ const cpuUsageMetric = meter.createValueObserver('cpu_usage_per_app', {
+ description: 'desc',
+ }) as ValueObserverMetric;
+
+ meter.createBatchObserver(
+ 'metric_batch_observer',
+ observerBatchResult => {
+ Promise.all([
+ // simulate waiting 11ms
+ new Promise((resolve, reject) => {
+ setTimeout(resolve, 11);
+ }),
+ ]).then(async () => {
+ // try to hack to be able to update
+ (observerBatchResult as BatchObserverResult).cancelled = false;
+ observerBatchResult.observe({ foo: 'bar' }, [
+ cpuUsageMetric.observation(123),
+ ]);
+
+ // simulate some waiting
+ await setTimeout(() => {}, 5);
+
+ const cpuUsageMetricRecords: MetricRecord[] = await cpuUsageMetric.getMetricRecord();
+ const value = cpuUsageMetric
+ .bind({ foo: 'bar' })
+ .getAggregator()
+ .toPoint().value as number;
+
+ assert.strictEqual(value, 0);
+ assert.strictEqual(cpuUsageMetricRecords.length, 0);
+ done();
+ });
+ },
+ {
+ maxTimeoutUpdateMS: 10, // timeout after 10ms
+ }
+ );
+
+ meter.collect();
+ });
+
+ it('should pipe through instrumentation library', async () => {
+ const observer = meter.createValueObserver(
+ 'name',
+ {},
+ (observerResult: api.ObserverResult) => {
+ observerResult.observe(42, { foo: 'bar' });
+ }
+ ) as ValueObserverMetric;
+ assert.ok(observer.instrumentationLibrary);
+
+ const [record] = await observer.getMetricRecord();
const { name, version } = record.instrumentationLibrary;
assert.strictEqual(name, 'test-meter');
assert.strictEqual(version, '*');
@@ -762,7 +889,7 @@ describe('Meter', () => {
});
describe('#getMetrics', () => {
- it('should create a DOUBLE counter', () => {
+ it('should create a DOUBLE counter', async () => {
const key = 'key';
const counter = meter.createCounter('counter', {
description: 'test',
@@ -771,7 +898,7 @@ describe('Meter', () => {
const boundCounter = counter.bind(labels);
boundCounter.add(10.45);
- meter.collect();
+ await meter.collect();
const record = meter.getBatcher().checkPointSet();
assert.strictEqual(record.length, 1);
@@ -780,14 +907,14 @@ describe('Meter', () => {
description: 'test',
metricKind: MetricKind.COUNTER,
unit: '1',
- valueType: ValueType.DOUBLE,
+ valueType: api.ValueType.DOUBLE,
});
assert.strictEqual(record[0].labels, labels);
const value = record[0].aggregator.toPoint().value as Sum;
assert.strictEqual(value, 10.45);
});
- it('should create a INT counter', () => {
+ it('should create an INT counter', async () => {
const key = 'key';
const counter = meter.createCounter('counter', {
description: 'test',
@@ -797,7 +924,7 @@ describe('Meter', () => {
const boundCounter = counter.bind(labels);
boundCounter.add(10.45);
- meter.collect();
+ await meter.collect();
const record = meter.getBatcher().checkPointSet();
assert.strictEqual(record.length, 1);
@@ -806,7 +933,7 @@ describe('Meter', () => {
description: 'test',
metricKind: MetricKind.COUNTER,
unit: '1',
- valueType: ValueType.INT,
+ valueType: api.ValueType.INT,
});
assert.strictEqual(record[0].labels, labels);
const value = record[0].aggregator.toPoint().value as Sum;
@@ -834,20 +961,18 @@ class CustomBatcher extends Batcher {
}
}
-function ensureMetric(metric: MetricRecord) {
- assert.ok(metric.aggregator instanceof ObserverAggregator);
- assert.ok(
- metric.aggregator.toPoint().value >= 0 &&
- metric.aggregator.toPoint().value <= 1
- );
- assert.ok(
- metric.aggregator.toPoint().value >= 0 &&
- metric.aggregator.toPoint().value <= 1
- );
+function ensureMetric(metric: MetricRecord, name?: string, value?: number) {
+ assert.ok(metric.aggregator instanceof LastValueAggregator);
+ const lastValue = metric.aggregator.toPoint().value;
+ if (typeof value === 'number') {
+ assert.strictEqual(lastValue, value);
+ } else {
+ assert.ok(lastValue >= 0 && lastValue <= 1);
+ }
const descriptor = metric.descriptor;
- assert.strictEqual(descriptor.name, 'name');
+ assert.strictEqual(descriptor.name, name || 'name');
assert.strictEqual(descriptor.description, 'desc');
assert.strictEqual(descriptor.unit, '1');
- assert.strictEqual(descriptor.metricKind, MetricKind.OBSERVER);
- assert.strictEqual(descriptor.valueType, ValueType.DOUBLE);
+ assert.strictEqual(descriptor.metricKind, MetricKind.VALUE_OBSERVER);
+ assert.strictEqual(descriptor.valueType, api.ValueType.DOUBLE);
}
diff --git a/packages/opentelemetry-metrics/test/export/ConsoleMetricExporter.test.ts b/packages/opentelemetry-metrics/test/export/ConsoleMetricExporter.test.ts
index 08790ea8c5..b23a14cd81 100644
--- a/packages/opentelemetry-metrics/test/export/ConsoleMetricExporter.test.ts
+++ b/packages/opentelemetry-metrics/test/export/ConsoleMetricExporter.test.ts
@@ -34,7 +34,7 @@ describe('ConsoleMetricExporter', () => {
});
describe('.export()', () => {
- it('should export information about metrics', () => {
+ it('should export information about metrics', async () => {
const spyConsole = sinon.spy(console, 'log');
const meter = new MeterProvider().getMeter(
@@ -49,7 +49,7 @@ describe('ConsoleMetricExporter', () => {
});
boundCounter.add(10);
- meter.collect();
+ await meter.collect();
consoleExporter.export(meter.getBatcher().checkPointSet(), () => {});
assert.strictEqual(spyConsole.args.length, 3);
const [descriptor, labels, value] = spyConsole.args;