diff --git a/examples/metrics/README.md b/examples/metrics/README.md
new file mode 100644
index 0000000000..b5b09885f3
--- /dev/null
+++ b/examples/metrics/README.md
@@ -0,0 +1,41 @@
+# Overview
+
+OpenTelemetry metrics allow a user to collect data and export it to a metrics backend like Prometheus.
+
+This is a simple example that demonstrates basic metrics collection and exports those metrics to a Prometheus compatible endpoint.
+
+## Installation
+
+```sh
+$ # from this directory
+$ npm install
+```
+
+How to setup [Prometheus](https://prometheus.io/docs/prometheus/latest/getting_started/) please check
+[Setup Prometheus](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-prometheus)
+
+## Run the Application
+- Run the example
+
+### Observer
+```sh
+$ npm run start:observer
+```
+
+### Prometheus
+1. In prometheus search for "metric_observer"
+
+### Links
+1. Prometheus Scrape Endpoint http://localhost:9464/metrics
+2. Prometheus graph http://localhost:9090/graph
+
+### Example
+
+
+## Useful links
+- For more information on OpenTelemetry, visit:
+- For more information on OpenTelemetry metrics, visit:
+
+## LICENSE
+
+Apache License 2.0
diff --git a/examples/metrics/metrics/observer.js b/examples/metrics/metrics/observer.js
new file mode 100644
index 0000000000..bfc28707a7
--- /dev/null
+++ b/examples/metrics/metrics/observer.js
@@ -0,0 +1,35 @@
+'use strict';
+
+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: 1000,
+}).getMeter('example-observer');
+
+const otelCpuUsage = meter.createObserver('metric_observer', {
+ monotonic: false,
+ labelKeys: ['pid', 'core'],
+ description: 'Example of a observer',
+});
+
+function getCpuUsage() {
+ return Math.random();
+}
+
+otelCpuUsage.setCallback((observerResult) => {
+ observerResult.observe(getCpuUsage, meter.labels({ pid: process.pid, core: '1' }));
+ observerResult.observe(getCpuUsage, meter.labels({ pid: process.pid, core: '2' }));
+ observerResult.observe(getCpuUsage, meter.labels({ pid: process.pid, core: '3' }));
+ observerResult.observe(getCpuUsage, meter.labels({ pid: process.pid, core: '4' }));
+});
diff --git a/examples/metrics/metrics/observer.png b/examples/metrics/metrics/observer.png
new file mode 100644
index 0000000000..7a9184af13
Binary files /dev/null and b/examples/metrics/metrics/observer.png differ
diff --git a/examples/metrics/package.json b/examples/metrics/package.json
new file mode 100644
index 0000000000..60a61d0fcc
--- /dev/null
+++ b/examples/metrics/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "example-metrics",
+ "private": true,
+ "version": "0.4.0",
+ "description": "Example of using @opentelemetry/metrics",
+ "main": "index.js",
+ "scripts": {
+ "start:observer": "node metrics/observer.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+ssh://git@github.com/open-telemetry/opentelemetry-js.git"
+ },
+ "keywords": [
+ "opentelemetry",
+ "http",
+ "tracing",
+ "metrics"
+ ],
+ "engines": {
+ "node": ">=8"
+ },
+ "author": "OpenTelemetry Authors",
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://github.com/open-telemetry/opentelemetry-js/issues"
+ },
+ "dependencies": {
+ "@opentelemetry/exporter-prometheus": "^0.4.0",
+ "@opentelemetry/metrics": "^0.4.0"
+ },
+ "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme"
+}
diff --git a/packages/opentelemetry-api/package.json b/packages/opentelemetry-api/package.json
index 93eb52e62a..d9a44de09a 100644
--- a/packages/opentelemetry-api/package.json
+++ b/packages/opentelemetry-api/package.json
@@ -18,7 +18,8 @@
"compile": "npm run version:update && tsc -p .",
"docs-test": "linkinator docs/out --silent --skip david-dm.org",
"docs": "typedoc --tsconfig tsconfig.json --exclude test/**/*.ts",
- "prepare": "npm run compile"
+ "prepare": "npm run compile",
+ "watch": "tsc -w"
},
"keywords": [
"opentelemetry",
diff --git a/packages/opentelemetry-api/src/index.ts b/packages/opentelemetry-api/src/index.ts
index 04b0876e0a..2653d5af56 100644
--- a/packages/opentelemetry-api/src/index.ts
+++ b/packages/opentelemetry-api/src/index.ts
@@ -26,6 +26,7 @@ export * from './metrics/MeterProvider';
export * from './metrics/Metric';
export * from './metrics/NoopMeter';
export * from './metrics/NoopMeterProvider';
+export * from './metrics/ObserverResult';
export * from './trace/attributes';
export * from './trace/Event';
export * from './trace/instrumentation/Plugin';
diff --git a/packages/opentelemetry-api/src/metrics/BoundInstrument.ts b/packages/opentelemetry-api/src/metrics/BoundInstrument.ts
index 07302bd22f..11204029d6 100644
--- a/packages/opentelemetry-api/src/metrics/BoundInstrument.ts
+++ b/packages/opentelemetry-api/src/metrics/BoundInstrument.ts
@@ -16,6 +16,7 @@
import { CorrelationContext } from '../correlation_context/CorrelationContext';
import { SpanContext } from '../trace/span_context';
+import { ObserverResult } from './ObserverResult';
/** An Instrument for Counter Metric. */
export interface BoundCounter {
@@ -44,3 +45,14 @@ export interface BoundMeasure {
spanContext: SpanContext
): void;
}
+
+/** Base interface for the Observer metrics. */
+export interface BoundObserver {
+ /**
+ * Sets callback for the observer. The callback is called once and then it
+ * sets observers for values. The observers are called periodically to
+ * retrieve the value.
+ * @param callback
+ */
+ setCallback(callback: (observerResult: ObserverResult) => {}): void;
+}
diff --git a/packages/opentelemetry-api/src/metrics/Meter.ts b/packages/opentelemetry-api/src/metrics/Meter.ts
index 54f09118d2..4348370fd0 100644
--- a/packages/opentelemetry-api/src/metrics/Meter.ts
+++ b/packages/opentelemetry-api/src/metrics/Meter.ts
@@ -15,7 +15,7 @@
*/
import { Metric, MetricOptions, Labels, LabelSet } from './Metric';
-import { BoundCounter, BoundMeasure } from './BoundInstrument';
+import { BoundCounter, BoundMeasure, BoundObserver } from './BoundInstrument';
/**
* An interface to allow the recording metrics.
@@ -33,7 +33,7 @@ export interface Meter {
createMeasure(name: string, options?: MetricOptions): Metric;
/**
- * Creates a new `counter` metric. Generally, this kind of metric when the
+ * Creates a new `Counter` metric. Generally, this kind of metric when the
* value is a quantity, the sum is of primary interest, and the event count
* and value distribution are not of primary interest.
* @param name the name of the metric.
@@ -41,6 +41,13 @@ export interface Meter {
*/
createCounter(name: string, options?: MetricOptions): Metric;
+ /**
+ * Creates a new `Observer` metric.
+ * @param name the name of the metric.
+ * @param [options] the metric options.
+ */
+ createObserver(name: string, options?: MetricOptions): Metric;
+
/**
* Provide a pre-computed re-useable LabelSet by
* converting the unordered labels into a canonicalized
diff --git a/packages/opentelemetry-api/src/metrics/Metric.ts b/packages/opentelemetry-api/src/metrics/Metric.ts
index e6e711353f..23f19f6af2 100644
--- a/packages/opentelemetry-api/src/metrics/Metric.ts
+++ b/packages/opentelemetry-api/src/metrics/Metric.ts
@@ -16,6 +16,7 @@
import { CorrelationContext } from '../correlation_context/CorrelationContext';
import { SpanContext } from '../trace/span_context';
+import { ObserverResult } from './ObserverResult';
/**
* Options needed for metric creation
@@ -105,6 +106,13 @@ export interface MetricUtils {
*/
add(value: number, labelSet: LabelSet): void;
+ /**
+ * Sets a callback where user can observe value for certain labels
+ * @param callback a function that will be called once to set observers
+ * for values
+ */
+ setCallback(callback: (observerResult: ObserverResult) => void): void;
+
/**
* Sets the given value. Values can be negative.
*/
diff --git a/packages/opentelemetry-api/src/metrics/NoopMeter.ts b/packages/opentelemetry-api/src/metrics/NoopMeter.ts
index b3ab749831..2a48c68c96 100644
--- a/packages/opentelemetry-api/src/metrics/NoopMeter.ts
+++ b/packages/opentelemetry-api/src/metrics/NoopMeter.ts
@@ -16,9 +16,10 @@
import { Meter } from './Meter';
import { MetricOptions, Metric, Labels, LabelSet, MetricUtils } from './Metric';
-import { BoundMeasure, BoundCounter } from './BoundInstrument';
+import { BoundMeasure, BoundCounter, BoundObserver } from './BoundInstrument';
import { CorrelationContext } from '../correlation_context/CorrelationContext';
import { SpanContext } from '../trace/span_context';
+import { ObserverResult } from './ObserverResult';
/**
* NoopMeter is a noop implementation of the {@link Meter} interface. It reuses
@@ -45,6 +46,15 @@ export class NoopMeter implements Meter {
return NOOP_COUNTER_METRIC;
}
+ /**
+ * Returns constant noop observer.
+ * @param name the name of the metric.
+ * @param [options] the metric options.
+ */
+ createObserver(name: string, options?: MetricOptions): Metric {
+ return NOOP_OBSERVER_METRIC;
+ }
+
labels(labels: Labels): LabelSet {
return NOOP_LABEL_SET;
}
@@ -120,6 +130,11 @@ export class NoopMeasureMetric extends NoopMetric
}
}
+export class NoopObserverMetric extends NoopMetric
+ implements Pick {
+ setCallback(callback: (observerResult: ObserverResult) => void): void {}
+}
+
export class NoopBoundCounter implements BoundCounter {
add(value: number): void {
return;
@@ -136,6 +151,10 @@ export class NoopBoundMeasure implements BoundMeasure {
}
}
+export class NoopBoundObserver implements BoundObserver {
+ setCallback(callback: (observerResult: ObserverResult) => {}): void {}
+}
+
export const NOOP_METER = new NoopMeter();
export const NOOP_BOUND_COUNTER = new NoopBoundCounter();
export const NOOP_COUNTER_METRIC = new NoopCounterMetric(NOOP_BOUND_COUNTER);
@@ -143,4 +162,7 @@ export const NOOP_COUNTER_METRIC = new NoopCounterMetric(NOOP_BOUND_COUNTER);
export const NOOP_BOUND_MEASURE = new NoopBoundMeasure();
export const NOOP_MEASURE_METRIC = new NoopMeasureMetric(NOOP_BOUND_MEASURE);
+export const NOOP_BOUND_OBSERVER = new NoopBoundObserver();
+export const NOOP_OBSERVER_METRIC = new NoopObserverMetric(NOOP_BOUND_OBSERVER);
+
export const NOOP_LABEL_SET = {} as LabelSet;
diff --git a/packages/opentelemetry-api/src/metrics/ObserverResult.ts b/packages/opentelemetry-api/src/metrics/ObserverResult.ts
new file mode 100644
index 0000000000..f3eb082fd4
--- /dev/null
+++ b/packages/opentelemetry-api/src/metrics/ObserverResult.ts
@@ -0,0 +1,25 @@
+/*!
+ * Copyright 2020, 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 { LabelSet } from './Metric';
+
+/**
+ * Interface that is being used in function setCallback for Observer Metric
+ */
+export interface ObserverResult {
+ observers: Map;
+ observe(callback: Function, labelSet: LabelSet): void;
+}
diff --git a/packages/opentelemetry-exporter-prometheus/package.json b/packages/opentelemetry-exporter-prometheus/package.json
index 6b4a8c40af..2f0462b96c 100644
--- a/packages/opentelemetry-exporter-prometheus/package.json
+++ b/packages/opentelemetry-exporter-prometheus/package.json
@@ -15,7 +15,8 @@
"precompile": "tsc --version",
"version:update": "node ../../scripts/version-update.js",
"compile": "npm run version:update && tsc -p .",
- "prepare": "npm run compile"
+ "prepare": "npm run compile",
+ "watch": "tsc -w"
},
"keywords": [
"opentelemetry",
diff --git a/packages/opentelemetry-exporter-prometheus/src/prometheus.ts b/packages/opentelemetry-exporter-prometheus/src/prometheus.ts
index 6217993672..27c4bcf7a1 100644
--- a/packages/opentelemetry-exporter-prometheus/src/prometheus.ts
+++ b/packages/opentelemetry-exporter-prometheus/src/prometheus.ts
@@ -17,10 +17,13 @@
import { ExportResult } from '@opentelemetry/base';
import { NoopLogger } from '@opentelemetry/core';
import {
+ CounterSumAggregator,
+ LastValue,
MetricExporter,
MetricRecord,
MetricDescriptor,
MetricKind,
+ ObserverAggregator,
Sum,
} from '@opentelemetry/metrics';
import * as types from '@opentelemetry/api';
@@ -28,7 +31,6 @@ import { createServer, IncomingMessage, Server, ServerResponse } from 'http';
import { Counter, Gauge, labelValues, Metric, Registry } from 'prom-client';
import * as url from 'url';
import { ExporterConfig } from './export/types';
-import { CounterSumAggregator, LabelSet } from '@opentelemetry/metrics';
export class PrometheusExporter implements MetricExporter {
static readonly DEFAULT_OPTIONS = {
@@ -138,13 +140,19 @@ export class PrometheusExporter implements MetricExporter {
this._getLabelValues(labelKeys, record.labels),
value as Sum
);
+ } else if (record.aggregator instanceof ObserverAggregator) {
+ metric.set(
+ this._getLabelValues(labelKeys, record.labels),
+ value as LastValue,
+ new Date()
+ );
}
}
// TODO: only counter and gauge are implemented in metrics so far
}
- private _getLabelValues(keys: string[], values: LabelSet) {
+ private _getLabelValues(keys: string[], values: types.LabelSet) {
const labelValues: labelValues = {};
const labels = values.labels;
for (let i = 0; i < keys.length; i++) {
@@ -191,7 +199,7 @@ export class PrometheusExporter implements MetricExporter {
return record.descriptor.monotonic
? new Counter(metricObject)
: new Gauge(metricObject);
- case MetricKind.GAUGE:
+ case MetricKind.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 9a6cc2d9cf..263e624ade 100644
--- a/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts
+++ b/packages/opentelemetry-exporter-prometheus/test/prometheus.test.ts
@@ -14,7 +14,13 @@
* limitations under the License.
*/
-import { CounterMetric, Meter, MeterProvider } from '@opentelemetry/metrics';
+import { ObserverResult } from '@opentelemetry/api';
+import {
+ CounterMetric,
+ Meter,
+ MeterProvider,
+ ObserverMetric,
+} from '@opentelemetry/metrics';
import * as assert from 'assert';
import * as http from 'http';
import { PrometheusExporter } from '../src';
@@ -215,6 +221,54 @@ describe('PrometheusExporter', () => {
});
});
+ it('should export an observer aggregation', done => {
+ function getCpuUsage() {
+ return Math.random();
+ }
+
+ const observer = meter.createObserver('metric_observer', {
+ description: 'a test description',
+ labelKeys: ['pid'],
+ }) as ObserverMetric;
+
+ observer.setCallback((observerResult: ObserverResult) => {
+ observerResult.observe(
+ getCpuUsage,
+ meter.labels({ pid: String(123), core: '1' })
+ );
+ });
+
+ meter.collect();
+ 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"}');
+ assert.ok(
+ parseFloat(line3[1]) >= 0 && parseFloat(line3[1]) <= 1
+ );
+ assert.ok(parseInt(line3[2], 10) <= new Date().getTime());
+
+ done();
+ });
+ })
+ .on('error', errorHandler(done));
+ });
+ });
+ });
+
it('should export multiple aggregations', done => {
const counter = meter.createCounter('counter', {
description: 'a test description',
diff --git a/packages/opentelemetry-metrics/package.json b/packages/opentelemetry-metrics/package.json
index 57882be849..6a7543fe1b 100644
--- a/packages/opentelemetry-metrics/package.json
+++ b/packages/opentelemetry-metrics/package.json
@@ -15,7 +15,8 @@
"precompile": "tsc --version",
"version:update": "node ../../scripts/version-update.js",
"compile": "npm run version:update && tsc -p .",
- "prepare": "npm run compile"
+ "prepare": "npm run compile",
+ "watch": "tsc -w"
},
"keywords": [
"opentelemetry",
diff --git a/packages/opentelemetry-metrics/src/BoundInstrument.ts b/packages/opentelemetry-metrics/src/BoundInstrument.ts
index 5d441910f5..7a98698dba 100644
--- a/packages/opentelemetry-metrics/src/BoundInstrument.ts
+++ b/packages/opentelemetry-metrics/src/BoundInstrument.ts
@@ -16,6 +16,7 @@
import * as types from '@opentelemetry/api';
import { Aggregator } from './export/types';
+import { ObserverResult } from './ObserverResult';
/**
* This class represent the base to BoundInstrument, which is responsible for generating
@@ -131,3 +132,25 @@ export class BoundMeasure extends BaseBoundInstrument
this.update(value);
}
}
+
+/**
+ * BoundObserver is an implementation of the {@link BoundObserver} interface.
+ */
+export class BoundObserver extends BaseBoundInstrument
+ implements types.BoundObserver {
+ constructor(
+ labelSet: types.LabelSet,
+ disabled: boolean,
+ monotonic: boolean,
+ valueType: types.ValueType,
+ logger: types.Logger,
+ aggregator: Aggregator
+ ) {
+ super(labelSet, logger, monotonic, disabled, valueType, aggregator);
+ }
+
+ setCallback(callback: (observerResult: types.ObserverResult) => {}): void {
+ const observerResult = new ObserverResult();
+ callback(observerResult);
+ }
+}
diff --git a/packages/opentelemetry-metrics/src/Meter.ts b/packages/opentelemetry-metrics/src/Meter.ts
index a0117ede00..693aa450af 100644
--- a/packages/opentelemetry-metrics/src/Meter.ts
+++ b/packages/opentelemetry-metrics/src/Meter.ts
@@ -17,7 +17,7 @@
import * as types from '@opentelemetry/api';
import { ConsoleLogger } from '@opentelemetry/core';
import { BaseBoundInstrument } from './BoundInstrument';
-import { Metric, CounterMetric, MeasureMetric } from './Metric';
+import { Metric, CounterMetric, MeasureMetric, ObserverMetric } from './Metric';
import {
MetricOptions,
DEFAULT_METRIC_OPTIONS,
@@ -107,6 +107,33 @@ export class Meter implements types.Meter {
return counter;
}
+ /**
+ * Creates a new observer metric.
+ * @param name the name of the metric.
+ * @param [options] the metric options.
+ */
+ createObserver(
+ name: string,
+ options?: types.MetricOptions
+ ): types.Metric {
+ if (!this._isValidName(name)) {
+ this._logger.warn(
+ `Invalid metric name ${name}. Defaulting to noop metric implementation.`
+ );
+ return types.NOOP_OBSERVER_METRIC;
+ }
+ const opt: MetricOptions = {
+ monotonic: false, // Observers are defined as non-monotonic by default
+ absolute: false, // not applicable to observer, set to false
+ logger: this._logger,
+ ...DEFAULT_METRIC_OPTIONS,
+ ...options,
+ };
+ const observer = new ObserverMetric(name, opt, this._batcher);
+ this._registerMetric(name, observer);
+ return observer;
+ }
+
/**
* Collects all the metrics created with this `Meter` for export.
*
diff --git a/packages/opentelemetry-metrics/src/Metric.ts b/packages/opentelemetry-metrics/src/Metric.ts
index 1e38d7410e..179a481828 100644
--- a/packages/opentelemetry-metrics/src/Metric.ts
+++ b/packages/opentelemetry-metrics/src/Metric.ts
@@ -19,7 +19,9 @@ import {
BoundCounter,
BaseBoundInstrument,
BoundMeasure,
+ BoundObserver,
} from './BoundInstrument';
+import { ObserverResult } from './ObserverResult';
import { MetricOptions } from './types';
import { MetricKind, MetricDescriptor, MetricRecord } from './export/types';
import { Batcher } from './export/Batcher';
@@ -161,3 +163,44 @@ export class MeasureMetric extends Metric
this.bind(labelSet).record(value);
}
}
+
+/** This is a SDK implementation of Observer Metric. */
+export class ObserverMetric extends Metric
+ implements Pick {
+ private _observerResult: types.ObserverResult = new ObserverResult();
+
+ constructor(
+ name: string,
+ options: MetricOptions,
+ private readonly _batcher: Batcher
+ ) {
+ super(name, options, MetricKind.OBSERVER);
+ }
+
+ protected _makeInstrument(labelSet: types.LabelSet): BoundObserver {
+ return new BoundObserver(
+ labelSet,
+ this._disabled,
+ this._monotonic,
+ this._valueType,
+ this._logger,
+ this._batcher.aggregatorFor(MetricKind.OBSERVER)
+ );
+ }
+
+ getMetricRecord(): MetricRecord[] {
+ this._observerResult.observers.forEach((callback, labelSet) => {
+ const instrument = this.bind(labelSet);
+ instrument.update(callback());
+ });
+ return super.getMetricRecord();
+ }
+
+ /**
+ * Sets a callback where user can observe value for certain labels
+ * @param callback
+ */
+ setCallback(callback: (observerResult: types.ObserverResult) => void): void {
+ callback(this._observerResult);
+ }
+}
diff --git a/packages/opentelemetry-metrics/src/ObserverResult.ts b/packages/opentelemetry-metrics/src/ObserverResult.ts
new file mode 100644
index 0000000000..6f7e629269
--- /dev/null
+++ b/packages/opentelemetry-metrics/src/ObserverResult.ts
@@ -0,0 +1,28 @@
+/*!
+ * Copyright 2019, 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 { ObserverResult as TypeObserverResult } from '@opentelemetry/api';
+import { LabelSet } from './LabelSet';
+
+/**
+ * Implementation of {@link TypeObserverResult}
+ */
+export class ObserverResult implements TypeObserverResult {
+ observers = new Map();
+ observe(callback: any, labelSet: LabelSet): void {
+ this.observers.set(labelSet, callback);
+ }
+}
diff --git a/packages/opentelemetry-metrics/src/export/Aggregator.ts b/packages/opentelemetry-metrics/src/export/Aggregator.ts
index dbf1d08fac..f9edf8ef93 100644
--- a/packages/opentelemetry-metrics/src/export/Aggregator.ts
+++ b/packages/opentelemetry-metrics/src/export/Aggregator.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { Distribution, Sum, Aggregator } from './types';
+import { Aggregator, Distribution, LastValue, Sum } from './types';
/** Basic aggregator which calculates a Sum from individual measurements. */
export class CounterSumAggregator implements Aggregator {
@@ -29,6 +29,19 @@ export class CounterSumAggregator implements Aggregator {
}
}
+/** Basic aggregator for Observer which keeps the last recorded value. */
+export class ObserverAggregator implements Aggregator {
+ private _current: number = 0;
+
+ update(value: number): void {
+ this._current = value;
+ }
+
+ value(): LastValue {
+ return this._current;
+ }
+}
+
/** Basic aggregator keeping all raw values (events, sum, max and min). */
export class MeasureExactAggregator implements Aggregator {
private _distribution: Distribution;
diff --git a/packages/opentelemetry-metrics/src/export/Batcher.ts b/packages/opentelemetry-metrics/src/export/Batcher.ts
index 911abfada3..85ecbfb7fe 100644
--- a/packages/opentelemetry-metrics/src/export/Batcher.ts
+++ b/packages/opentelemetry-metrics/src/export/Batcher.ts
@@ -14,7 +14,11 @@
* limitations under the License.
*/
-import { CounterSumAggregator, MeasureExactAggregator } from './Aggregator';
+import {
+ CounterSumAggregator,
+ MeasureExactAggregator,
+ ObserverAggregator,
+} from './Aggregator';
import { MetricRecord, MetricKind, Aggregator } from './types';
/**
@@ -47,6 +51,8 @@ export class UngroupedBatcher extends Batcher {
switch (metricKind) {
case MetricKind.COUNTER:
return new CounterSumAggregator();
+ case MetricKind.OBSERVER:
+ return new ObserverAggregator();
default:
return new MeasureExactAggregator();
}
diff --git a/packages/opentelemetry-metrics/src/export/types.ts b/packages/opentelemetry-metrics/src/export/types.ts
index 916e17b23a..b57ab562b7 100644
--- a/packages/opentelemetry-metrics/src/export/types.ts
+++ b/packages/opentelemetry-metrics/src/export/types.ts
@@ -21,13 +21,16 @@ import { LabelSet } from '../LabelSet';
/** The kind of metric. */
export enum MetricKind {
COUNTER,
- GAUGE,
MEASURE,
+ OBSERVER,
}
/** Sum returns an aggregated sum. */
export type Sum = number;
+/** LastValue returns last value. */
+export type LastValue = number;
+
export interface Distribution {
min: number;
max: number;
diff --git a/packages/opentelemetry-metrics/src/index.ts b/packages/opentelemetry-metrics/src/index.ts
index 2ecd8b3395..2ecbb7c751 100644
--- a/packages/opentelemetry-metrics/src/index.ts
+++ b/packages/opentelemetry-metrics/src/index.ts
@@ -19,6 +19,7 @@ export * from './BoundInstrument';
export * from './Meter';
export * from './Metric';
export * from './MeterProvider';
+export * from './export/Aggregator';
export * from './export/ConsoleMetricExporter';
export * from './export/types';
export * from './export/Aggregator';
diff --git a/packages/opentelemetry-metrics/test/Meter.test.ts b/packages/opentelemetry-metrics/test/Meter.test.ts
index 7a85f16023..87ade69ce1 100644
--- a/packages/opentelemetry-metrics/test/Meter.test.ts
+++ b/packages/opentelemetry-metrics/test/Meter.test.ts
@@ -24,11 +24,16 @@ import {
MeterProvider,
MeasureMetric,
Distribution,
+ ObserverMetric,
+ MetricRecord,
} from '../src';
import * as types from '@opentelemetry/api';
import { LabelSet } from '../src/LabelSet';
import { NoopLogger } from '@opentelemetry/core';
-import { CounterSumAggregator } from '../src/export/Aggregator';
+import {
+ CounterSumAggregator,
+ ObserverAggregator,
+} from '../src/export/Aggregator';
import { ValueType } from '@opentelemetry/api';
describe('Meter', () => {
@@ -391,6 +396,69 @@ describe('Meter', () => {
});
});
+ describe('#observer', () => {
+ it('should create an observer', () => {
+ const measure = meter.createObserver('name') as ObserverMetric;
+ assert.ok(measure instanceof Metric);
+ });
+
+ it('should create observer with options', () => {
+ const measure = meter.createObserver('name', {
+ description: 'desc',
+ unit: '1',
+ disabled: false,
+ }) as ObserverMetric;
+ assert.ok(measure instanceof Metric);
+ });
+ it('should set callback and observe value ', () => {
+ const measure = meter.createObserver('name', {
+ description: 'desc',
+ labelKeys: ['pid', 'core'],
+ }) as ObserverMetric;
+
+ function getCpuUsage() {
+ return Math.random();
+ }
+
+ measure.setCallback((observerResult: types.ObserverResult) => {
+ observerResult.observe(
+ getCpuUsage,
+ meter.labels({ pid: '123', core: '1' })
+ );
+ observerResult.observe(
+ getCpuUsage,
+ meter.labels({ pid: '123', core: '2' })
+ );
+ observerResult.observe(
+ getCpuUsage,
+ meter.labels({ pid: '123', core: '3' })
+ );
+ observerResult.observe(
+ getCpuUsage,
+ meter.labels({ pid: '123', core: '4' })
+ );
+ });
+
+ const metricRecords: MetricRecord[] = measure.getMetricRecord();
+ assert.strictEqual(metricRecords.length, 4);
+
+ const metric1 = metricRecords[0];
+ const metric2 = metricRecords[1];
+ const metric3 = metricRecords[2];
+ const metric4 = metricRecords[3];
+
+ assert.ok(metric1.labels.identifier.indexOf('|#core:1,pid:123') === 0);
+ assert.ok(metric2.labels.identifier.indexOf('|#core:2,pid:123') === 0);
+ assert.ok(metric3.labels.identifier.indexOf('|#core:3,pid:123') === 0);
+ assert.ok(metric4.labels.identifier.indexOf('|#core:4,pid:123') === 0);
+
+ ensureMetric(metric1);
+ ensureMetric(metric2);
+ ensureMetric(metric3);
+ ensureMetric(metric4);
+ });
+ });
+
describe('#getMetrics', () => {
it('should create a DOUBLE counter', () => {
const key = 'key';
@@ -450,3 +518,16 @@ describe('Meter', () => {
});
});
});
+
+function ensureMetric(metric: MetricRecord) {
+ assert.ok(metric.aggregator instanceof ObserverAggregator);
+ assert.ok(metric.aggregator.value() >= 0 && metric.aggregator.value() <= 1);
+ assert.ok(metric.aggregator.value() >= 0 && metric.aggregator.value() <= 1);
+ const descriptor = metric.descriptor;
+ assert.strictEqual(descriptor.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.monotonic, false);
+}