Skip to content

Commit

Permalink
feat(prometheus): serialize resource as target_info gauge (#3300)
Browse files Browse the repository at this point in the history
  • Loading branch information
pichlermarc authored Oct 17, 2022
1 parent 92f2359 commit 3ab8bfc
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 27 deletions.
1 change: 1 addition & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ All notable changes to experimental packages in this project will be documented
### :rocket: (Enhancement)

* feat(sdk-node): configure trace exporter with environment variables [#3143](https://github.com/open-telemetry/opentelemetry-js/pull/3143) @svetlanabrennan
* feat(prometheus): serialize resource as target_info gauge [#3300](https://github.com/open-telemetry/opentelemetry-js/pull/3300) @pichlermarc

### :bug: (Bug Fix)

Expand Down
6 changes: 3 additions & 3 deletions experimental/examples/prometheus/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "prometheus-example",
"version": "0.32.0",
"version": "0.33.0",
"description": "Example of using @opentelemetry/sdk-metrics and @opentelemetry/exporter-prometheus",
"main": "index.js",
"scripts": {
Expand All @@ -10,7 +10,7 @@
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/api": "^1.0.2",
"@opentelemetry/exporter-prometheus": "0.32.0",
"@opentelemetry/sdk-metrics": "0.32.0"
"@opentelemetry/exporter-prometheus": "0.33.0",
"@opentelemetry/sdk-metrics": "0.33.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@
"dependencies": {
"@opentelemetry/api-metrics": "0.33.0",
"@opentelemetry/core": "1.7.0",
"@opentelemetry/sdk-metrics": "0.33.0"
"@opentelemetry/sdk-metrics": "0.33.0",
"@opentelemetry/resources": "1.7.0"
},
"homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-prometheus"
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,22 @@ import { diag } from '@opentelemetry/api';
import {
globalErrorHandler,
} from '@opentelemetry/core';
import { Aggregation, AggregationTemporality, MetricReader } from '@opentelemetry/sdk-metrics';
import { createServer, IncomingMessage, Server, ServerResponse } from 'http';
import {
Aggregation,
AggregationTemporality,
MetricReader
} from '@opentelemetry/sdk-metrics';
import {
createServer,
IncomingMessage,
Server,
ServerResponse
} from 'http';
import { ExporterConfig } from './export/types';
import { PrometheusSerializer } from './PrometheusSerializer';
/** Node.js v8.x compat */
import { URL } from 'url';

const NO_REGISTERED_METRICS = '# no registered metrics';

export class PrometheusExporter extends MetricReader {
static readonly DEFAULT_OPTIONS = {
host: undefined,
Expand Down Expand Up @@ -154,7 +161,7 @@ export class PrometheusExporter extends MetricReader {

/**
* Request handler that responds with the current state of metrics
* @param request Incoming HTTP request of server instance
* @param _request Incoming HTTP request of server instance
* @param response HTTP response objet used to response to request
*/
public getMetricsRequestHandler(
Expand Down Expand Up @@ -195,11 +202,7 @@ export class PrometheusExporter extends MetricReader {
if (errors.length) {
diag.error('PrometheusExporter: metrics collection errors', ...errors);
}
let result = this._serializer.serialize(resourceMetrics);
if (result === '') {
result = NO_REGISTERED_METRICS;
}
response.end(result);
response.end(this._serializer.serialize(resourceMetrics));
},
err => {
response.end(`# failed to export metrics: ${err}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type {
MetricAttributeValue
} from '@opentelemetry/api-metrics';
import { hrTimeToMilliseconds } from '@opentelemetry/core';
import { Resource } from '@opentelemetry/resources';

type PrometheusDataTypeLiteral =
| 'counter'
Expand Down Expand Up @@ -167,6 +168,8 @@ function stringify(
}\n`;
}

const NO_REGISTERED_METRICS = '# no registered metrics';

export class PrometheusSerializer {
private _prefix: string | undefined;
private _appendTimestamp: boolean;
Expand All @@ -180,10 +183,16 @@ export class PrometheusSerializer {

serialize(resourceMetrics: ResourceMetrics): string {
let str = '';

for (const scopeMetrics of resourceMetrics.scopeMetrics) {
str += this._serializeScopeMetrics(scopeMetrics);
}
return str;

if (str === '') {
str += NO_REGISTERED_METRICS;
}

return this._serializeResource(resourceMetrics.resource) + str;
}

private _serializeScopeMetrics(scopeMetrics: ScopeMetrics) {
Expand Down Expand Up @@ -311,4 +320,13 @@ export class PrometheusSerializer {

return results;
}

protected _serializeResource(resource: Resource): string {
const name = 'target_info';
const help = `# HELP ${name} Target metadata`;
const type = `# TYPE ${name} gauge`;

const results = stringify(name, resource.attributes, 1).trim();
return `${help}\n${type}\n${results}\n`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,28 @@
* limitations under the License.
*/

import { Meter, ObservableResult } from '@opentelemetry/api-metrics';
import {
Meter,
ObservableResult
} from '@opentelemetry/api-metrics';
import { MeterProvider } from '@opentelemetry/sdk-metrics';
import * as assert from 'assert';
import * as sinon from 'sinon';
import * as http from 'http';
import { PrometheusExporter } from '../src';
import { mockedHrTimeMs, mockHrTime } from './util';
import {
mockedHrTimeMs,
mockHrTime
} from './util';
import { SinonStubbedInstance } from 'sinon';
import { Counter } from '@opentelemetry/api-metrics';

const serializedEmptyResourceLines = [
'# HELP target_info Target metadata',
'# TYPE target_info gauge',
'target_info 1'
];

describe('PrometheusExporter', () => {
beforeEach(() => {
mockHrTime();
Expand Down Expand Up @@ -249,11 +261,12 @@ describe('PrometheusExporter', () => {
const lines = body.split('\n');

assert.strictEqual(
lines[0],
lines[serializedEmptyResourceLines.length],
'# HELP counter_total a test description'
);

assert.deepStrictEqual(lines, [
...serializedEmptyResourceLines,
'# HELP counter_total a test description',
'# TYPE counter_total counter',
`counter_total{key1="attributeValue1"} 10 ${mockedHrTimeMs}`,
Expand Down Expand Up @@ -283,6 +296,7 @@ describe('PrometheusExporter', () => {
const lines = body.split('\n');

assert.deepStrictEqual(lines, [
...serializedEmptyResourceLines,
'# HELP metric_observable_gauge a test description',
'# TYPE metric_observable_gauge gauge',
`metric_observable_gauge{pid="123",core="1"} 0.999 ${mockedHrTimeMs}`,
Expand All @@ -302,6 +316,7 @@ describe('PrometheusExporter', () => {
const lines = body.split('\n');

assert.deepStrictEqual(lines, [
...serializedEmptyResourceLines,
'# HELP counter_total a test description',
'# TYPE counter_total counter',
`counter_total{counterKey1="attributeValue1"} 10 ${mockedHrTimeMs}`,
Expand Down Expand Up @@ -331,11 +346,14 @@ describe('PrometheusExporter', () => {
});
});

it('should export a comment if no metrics are registered', async () => {
it('should export resource even if no metrics are registered', async () => {
const body = await request('http://localhost:9464/metrics');
const lines = body.split('\n');

assert.deepStrictEqual(lines, ['# no registered metrics']);
assert.deepStrictEqual(lines, [
...serializedEmptyResourceLines,
'# no registered metrics'
]);
});

it('should add a description if missing', async () => {
Expand All @@ -347,6 +365,7 @@ describe('PrometheusExporter', () => {
const lines = body.split('\n');

assert.deepStrictEqual(lines, [
...serializedEmptyResourceLines,
'# HELP counter_total description missing',
'# TYPE counter_total counter',
`counter_total{key1="attributeValue1"} 10 ${mockedHrTimeMs}`,
Expand All @@ -363,6 +382,7 @@ describe('PrometheusExporter', () => {
const lines = body.split('\n');

assert.deepStrictEqual(lines, [
...serializedEmptyResourceLines,
'# HELP counter_bad_name_total description missing',
'# TYPE counter_bad_name_total counter',
`counter_bad_name_total{key1="attributeValue1"} 10 ${mockedHrTimeMs}`,
Expand All @@ -380,14 +400,15 @@ describe('PrometheusExporter', () => {
const body = await request('http://localhost:9464/metrics');
const lines = body.split('\n');
assert.deepStrictEqual(lines, [
...serializedEmptyResourceLines,
'# HELP counter a test description',
'# TYPE counter gauge',
`counter{key1="attributeValue1"} 20 ${mockedHrTimeMs}`,
'',
]);
});

it('should export an ObservableCounter as a counter', async() => {
it('should export an ObservableCounter as a counter', async () => {
function getValue() {
return 20;
}
Expand All @@ -408,6 +429,7 @@ describe('PrometheusExporter', () => {
const lines = body.split('\n');

assert.deepStrictEqual(lines, [
...serializedEmptyResourceLines,
'# HELP metric_observable_counter a test description',
'# TYPE metric_observable_counter counter',
`metric_observable_counter{key1="attributeValue1"} 20 ${mockedHrTimeMs}`,
Expand Down Expand Up @@ -436,14 +458,15 @@ describe('PrometheusExporter', () => {
const lines = body.split('\n');

assert.deepStrictEqual(lines, [
...serializedEmptyResourceLines,
'# HELP metric_observable_up_down_counter a test description',
'# TYPE metric_observable_up_down_counter gauge',
`metric_observable_up_down_counter{key1="attributeValue1"} 20 ${mockedHrTimeMs}`,
'',
]);
});

it('should export a Histogram as a summary', async() => {
it('should export a Histogram as a summary', async () => {
const histogram = meter.createHistogram('test_histogram', {
description: 'a test description',
});
Expand All @@ -454,6 +477,7 @@ describe('PrometheusExporter', () => {
const lines = body.split('\n');

assert.deepStrictEqual(lines, [
...serializedEmptyResourceLines,
'# HELP test_histogram a test description',
'# TYPE test_histogram histogram',
`test_histogram_count{key1="attributeValue1"} 1 ${mockedHrTimeMs}`,
Expand Down Expand Up @@ -507,6 +531,7 @@ describe('PrometheusExporter', () => {
const lines = body.split('\n');

assert.deepStrictEqual(lines, [
...serializedEmptyResourceLines,
'# HELP test_prefix_counter_total description missing',
'# TYPE test_prefix_counter_total counter',
`test_prefix_counter_total{key1="attributeValue1"} 10 ${mockedHrTimeMs}`,
Expand Down Expand Up @@ -535,6 +560,7 @@ describe('PrometheusExporter', () => {
const lines = body.split('\n');

assert.deepStrictEqual(lines, [
...serializedEmptyResourceLines,
'# HELP counter_total description missing',
'# TYPE counter_total counter',
`counter_total{key1="attributeValue1"} 10 ${mockedHrTimeMs}`,
Expand Down Expand Up @@ -563,6 +589,7 @@ describe('PrometheusExporter', () => {
const lines = body.split('\n');

assert.deepStrictEqual(lines, [
...serializedEmptyResourceLines,
'# HELP counter_total description missing',
'# TYPE counter_total counter',
`counter_total{key1="attributeValue1"} 10 ${mockedHrTimeMs}`,
Expand Down Expand Up @@ -591,6 +618,7 @@ describe('PrometheusExporter', () => {
const lines = body.split('\n');

assert.deepStrictEqual(lines, [
...serializedEmptyResourceLines,
'# HELP counter_total description missing',
'# TYPE counter_total counter',
'counter_total{key1="attributeValue1"} 10',
Expand Down
Loading

0 comments on commit 3ab8bfc

Please sign in to comment.