Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: metric observer #828

Merged
merged 6 commits into from Mar 5, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 41 additions & 0 deletions 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
<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
@@ -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
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
@@ -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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some reason this is start:observer instead of just start?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have created metrics example to be a place for other metrics too, so next step when working on documentation and examples should be to add start:counter, start:measure

},
"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
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
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
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
dyladan marked this conversation as resolved.
Show resolved Hide resolved
* sets observers for values. The observers are called periodically to
* retrieve the value.
* @param callback
obecny marked this conversation as resolved.
Show resolved Hide resolved
*/
setCallback(callback: (observerResult: ObserverResult) => {}): void;
}
11 changes: 9 additions & 2 deletions packages/opentelemetry-api/src/metrics/Meter.ts
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
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
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
@@ -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
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
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