Skip to content

Commit

Permalink
feat: metric observer (#828)
Browse files Browse the repository at this point in the history
* feat: metric observer

* chore: update after review

* chore: reviews

Co-authored-by: Mayur Kale <mayurkale@google.com>
  • Loading branch information
mayurkale22 authored Mar 5, 2020
1 parent 151106a commit d16c691
Show file tree
Hide file tree
Showing 24 changed files with 489 additions and 15 deletions.
41 changes: 41 additions & 0 deletions examples/metrics/README.md
Original file line number Diff line number Diff line change
@@ -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
<p align="center"><img src="metrics/observer.png"/></p>

## Useful links
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
- For more information on OpenTelemetry metrics, visit: <https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-metrics>

## LICENSE

Apache License 2.0
35 changes: 35 additions & 0 deletions examples/metrics/metrics/observer.js
Original file line number Diff line number Diff line change
@@ -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' }));
});
Binary file added examples/metrics/metrics/observer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions examples/metrics/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
3 changes: 2 additions & 1 deletion packages/opentelemetry-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions packages/opentelemetry-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
12 changes: 12 additions & 0 deletions packages/opentelemetry-api/src/metrics/BoundInstrument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
11 changes: 9 additions & 2 deletions packages/opentelemetry-api/src/metrics/Meter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -33,14 +33,21 @@ export interface Meter {
createMeasure(name: string, options?: MetricOptions): Metric<BoundMeasure>;

/**
* 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.
* @param [options] the metric options.
*/
createCounter(name: string, options?: MetricOptions): Metric<BoundCounter>;

/**
* Creates a new `Observer` metric.
* @param name the name of the metric.
* @param [options] the metric options.
*/
createObserver(name: string, options?: MetricOptions): Metric<BoundObserver>;

/**
* Provide a pre-computed re-useable LabelSet by
* converting the unordered labels into a canonicalized
Expand Down
8 changes: 8 additions & 0 deletions packages/opentelemetry-api/src/metrics/Metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*/
Expand Down
24 changes: 23 additions & 1 deletion packages/opentelemetry-api/src/metrics/NoopMeter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<BoundObserver> {
return NOOP_OBSERVER_METRIC;
}

labels(labels: Labels): LabelSet {
return NOOP_LABEL_SET;
}
Expand Down Expand Up @@ -120,6 +130,11 @@ export class NoopMeasureMetric extends NoopMetric<BoundMeasure>
}
}

export class NoopObserverMetric extends NoopMetric<BoundObserver>
implements Pick<MetricUtils, 'setCallback'> {
setCallback(callback: (observerResult: ObserverResult) => void): void {}
}

export class NoopBoundCounter implements BoundCounter {
add(value: number): void {
return;
Expand All @@ -136,11 +151,18 @@ 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);

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;
25 changes: 25 additions & 0 deletions packages/opentelemetry-api/src/metrics/ObserverResult.ts
Original file line number Diff line number Diff line change
@@ -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<LabelSet, Function>;
observe(callback: Function, labelSet: LabelSet): void;
}
3 changes: 2 additions & 1 deletion packages/opentelemetry-exporter-prometheus/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 11 additions & 3 deletions packages/opentelemetry-exporter-prometheus/src/prometheus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@
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';
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 = {
Expand Down Expand Up @@ -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++) {
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit d16c691

Please sign in to comment.