opentelemetry-js/packages/opentelemetry-exporter-prom.../test/PrometheusSerializer.test.ts

493 lines
17 KiB
TypeScript

/*
* 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 {
SumAggregator,
HistogramAggregator,
LastValueAggregator,
MeterProvider,
CounterMetric,
ValueRecorderMetric,
UpDownCounterMetric,
ValueObserverMetric,
} from '@opentelemetry/metrics';
import * as assert from 'assert';
import { Labels } from '@opentelemetry/api';
import { PrometheusSerializer } from '../src/PrometheusSerializer';
import { PrometheusLabelsBatcher } from '../src/PrometheusLabelsBatcher';
import { ExactProcessor } from './ExactProcessor';
import { mockedHrTimeMs, mockAggregator } from './util';
const labels = {
foo1: 'bar1',
foo2: 'bar2',
};
describe('PrometheusSerializer', () => {
describe('constructor', () => {
it('should construct a serializer', () => {
const serializer = new PrometheusSerializer();
assert(serializer instanceof PrometheusSerializer);
});
});
describe('serialize a metric record', () => {
describe('with SumAggregator', () => {
mockAggregator(SumAggregator);
it('should serialize metric record with sum aggregator', async () => {
const serializer = new PrometheusSerializer();
const meter = new MeterProvider({
processor: new ExactProcessor(SumAggregator),
}).getMeter('test');
const counter = meter.createCounter('test') as CounterMetric;
counter.bind(labels).add(1);
const records = await counter.getMetricRecord();
const record = records[0];
const result = serializer.serializeRecord(
record.descriptor.name,
record
);
assert.strictEqual(
result,
`test{foo1="bar1",foo2="bar2"} 1 ${mockedHrTimeMs}\n`
);
});
it('serialize metric record with sum aggregator without timestamp', async () => {
const serializer = new PrometheusSerializer(undefined, false);
const meter = new MeterProvider({
processor: new ExactProcessor(SumAggregator),
}).getMeter('test');
const counter = meter.createCounter('test') as CounterMetric;
counter.bind(labels).add(1);
const records = await counter.getMetricRecord();
const record = records[0];
const result = serializer.serializeRecord(
record.descriptor.name,
record
);
assert.strictEqual(result, 'test{foo1="bar1",foo2="bar2"} 1\n');
});
});
describe('with LastValueAggregator', () => {
mockAggregator(LastValueAggregator);
it('should serialize metric record with LastValue aggregator', async () => {
const serializer = new PrometheusSerializer();
const meter = new MeterProvider({
processor: new ExactProcessor(LastValueAggregator),
}).getMeter('test');
const observer = meter.createValueObserver(
'test',
{},
observerResult => {
observerResult.observe(1, labels);
}
) as ValueObserverMetric;
await meter.collect();
const records = await observer.getMetricRecord();
const record = records[0];
const result = serializer.serializeRecord(
record.descriptor.name,
record
);
assert.strictEqual(
result,
`test{foo1="bar1",foo2="bar2"} 1 ${mockedHrTimeMs}\n`
);
});
it('serialize metric record with sum aggregator without timestamp', async () => {
const serializer = new PrometheusSerializer(undefined, false);
const meter = new MeterProvider({
processor: new ExactProcessor(LastValueAggregator),
}).getMeter('test');
const observer = meter.createValueObserver(
'test',
{},
observerResult => {
observerResult.observe(1, labels);
}
) as ValueObserverMetric;
await meter.collect();
const records = await observer.getMetricRecord();
const record = records[0];
const result = serializer.serializeRecord(
record.descriptor.name,
record
);
assert.strictEqual(result, 'test{foo1="bar1",foo2="bar2"} 1\n');
});
});
describe('with HistogramAggregator', () => {
mockAggregator(HistogramAggregator);
it('should serialize metric record with sum aggregator', async () => {
const serializer = new PrometheusSerializer();
const processor = new ExactProcessor(HistogramAggregator, [1, 10, 100]);
const meter = new MeterProvider({ processor }).getMeter('test');
const recorder = meter.createValueRecorder('test', {
description: 'foobar',
}) as ValueRecorderMetric;
recorder.bind(labels).record(5);
const records = await recorder.getMetricRecord();
const record = records[0];
const result = serializer.serializeRecord(
record.descriptor.name,
record
);
assert.strictEqual(
result,
`test_count{foo1="bar1",foo2="bar2"} 1 ${mockedHrTimeMs}\n` +
`test_sum{foo1="bar1",foo2="bar2"} 5 ${mockedHrTimeMs}\n` +
`test_bucket{foo1="bar1",foo2="bar2",le="1"} 0 ${mockedHrTimeMs}\n` +
`test_bucket{foo1="bar1",foo2="bar2",le="10"} 1 ${mockedHrTimeMs}\n` +
`test_bucket{foo1="bar1",foo2="bar2",le="100"} 1 ${mockedHrTimeMs}\n` +
`test_bucket{foo1="bar1",foo2="bar2",le="+Inf"} 1 ${mockedHrTimeMs}\n`
);
});
it('should serialize metric record with sum aggregator, boundaries defined in constructor', async () => {
const serializer = new PrometheusSerializer();
const meter = new MeterProvider().getMeter('test');
const recorder = meter.createValueRecorder('test', {
description: 'foobar',
boundaries: [1, 10, 100],
}) as ValueRecorderMetric;
recorder.bind(labels).record(5);
const records = await recorder.getMetricRecord();
const record = records[0];
const result = serializer.serializeRecord(
record.descriptor.name,
record
);
assert.strictEqual(
result,
`test_count{foo1="bar1",foo2="bar2"} 1 ${mockedHrTimeMs}\n` +
`test_sum{foo1="bar1",foo2="bar2"} 5 ${mockedHrTimeMs}\n` +
`test_bucket{foo1="bar1",foo2="bar2",le="1"} 0 ${mockedHrTimeMs}\n` +
`test_bucket{foo1="bar1",foo2="bar2",le="10"} 1 ${mockedHrTimeMs}\n` +
`test_bucket{foo1="bar1",foo2="bar2",le="100"} 1 ${mockedHrTimeMs}\n` +
`test_bucket{foo1="bar1",foo2="bar2",le="+Inf"} 1 ${mockedHrTimeMs}\n`
);
});
it('serialize metric record with sum aggregator without timestamp', async () => {
const serializer = new PrometheusSerializer(undefined, false);
const processor = new ExactProcessor(HistogramAggregator, [1, 10, 100]);
const meter = new MeterProvider({ processor }).getMeter('test');
const recorder = meter.createValueRecorder('test', {
description: 'foobar',
}) as ValueRecorderMetric;
recorder.bind(labels).record(5);
const records = await recorder.getMetricRecord();
const record = records[0];
const result = serializer.serializeRecord(
record.descriptor.name,
record
);
assert.strictEqual(
result,
'test_count{foo1="bar1",foo2="bar2"} 1\n' +
'test_sum{foo1="bar1",foo2="bar2"} 5\n' +
'test_bucket{foo1="bar1",foo2="bar2",le="1"} 0\n' +
'test_bucket{foo1="bar1",foo2="bar2",le="10"} 1\n' +
'test_bucket{foo1="bar1",foo2="bar2",le="100"} 1\n' +
'test_bucket{foo1="bar1",foo2="bar2",le="+Inf"} 1\n'
);
});
});
});
describe('serialize a checkpoint set', () => {
describe('with SumAggregator', () => {
mockAggregator(SumAggregator);
it('should serialize metric record with sum aggregator', async () => {
const serializer = new PrometheusSerializer();
const meter = new MeterProvider({
processor: new ExactProcessor(SumAggregator),
}).getMeter('test');
const processor = new PrometheusLabelsBatcher();
const counter = meter.createCounter('test', {
description: 'foobar',
}) as CounterMetric;
counter.bind({ val: '1' }).add(1);
counter.bind({ val: '2' }).add(1);
const records = await counter.getMetricRecord();
records.forEach(it => processor.process(it));
const checkPointSet = processor.checkPointSet();
const result = serializer.serialize(checkPointSet);
assert.strictEqual(
result,
'# HELP test foobar\n' +
'# TYPE test counter\n' +
`test{val="1"} 1 ${mockedHrTimeMs}\n` +
`test{val="2"} 1 ${mockedHrTimeMs}\n`
);
});
it('serialize metric record with sum aggregator without timestamp', async () => {
const serializer = new PrometheusSerializer(undefined, false);
const meter = new MeterProvider({
processor: new ExactProcessor(SumAggregator),
}).getMeter('test');
const processor = new PrometheusLabelsBatcher();
const counter = meter.createCounter('test', {
description: 'foobar',
}) as CounterMetric;
counter.bind({ val: '1' }).add(1);
counter.bind({ val: '2' }).add(1);
const records = await counter.getMetricRecord();
records.forEach(it => processor.process(it));
const checkPointSet = processor.checkPointSet();
const result = serializer.serialize(checkPointSet);
assert.strictEqual(
result,
'# HELP test foobar\n' +
'# TYPE test counter\n' +
'test{val="1"} 1\n' +
'test{val="2"} 1\n'
);
});
});
describe('with LastValueAggregator', () => {
mockAggregator(LastValueAggregator);
it('serialize metric record with LastValue aggregator', async () => {
const serializer = new PrometheusSerializer();
const meter = new MeterProvider({
processor: new ExactProcessor(LastValueAggregator),
}).getMeter('test');
const processor = new PrometheusLabelsBatcher();
const observer = meter.createValueObserver(
'test',
{
description: 'foobar',
},
observerResult => {
observerResult.observe(1, labels);
}
) as ValueObserverMetric;
await meter.collect();
const records = await observer.getMetricRecord();
records.forEach(it => processor.process(it));
const checkPointSet = processor.checkPointSet();
const result = serializer.serialize(checkPointSet);
assert.strictEqual(
result,
'# HELP test foobar\n' +
'# TYPE test gauge\n' +
`test{foo1="bar1",foo2="bar2"} 1 ${mockedHrTimeMs}\n`
);
});
});
describe('with HistogramAggregator', () => {
mockAggregator(HistogramAggregator);
it('serialize metric record with HistogramAggregator aggregator, cumulative', async () => {
const serializer = new PrometheusSerializer();
const processor = new ExactProcessor(HistogramAggregator, [1, 10, 100]);
const meter = new MeterProvider({ processor }).getMeter('test');
const recorder = meter.createValueRecorder('test', {
description: 'foobar',
}) as ValueRecorderMetric;
recorder.bind({ val: '1' }).record(5);
recorder.bind({ val: '1' }).record(50);
recorder.bind({ val: '1' }).record(120);
recorder.bind({ val: '2' }).record(5);
const records = await recorder.getMetricRecord();
const labelBatcher = new PrometheusLabelsBatcher();
records.forEach(it => labelBatcher.process(it));
const checkPointSet = labelBatcher.checkPointSet();
const result = serializer.serialize(checkPointSet);
assert.strictEqual(
result,
'# HELP test foobar\n' +
'# TYPE test histogram\n' +
`test_count{val="1"} 3 ${mockedHrTimeMs}\n` +
`test_sum{val="1"} 175 ${mockedHrTimeMs}\n` +
`test_bucket{val="1",le="1"} 0 ${mockedHrTimeMs}\n` +
`test_bucket{val="1",le="10"} 1 ${mockedHrTimeMs}\n` +
`test_bucket{val="1",le="100"} 2 ${mockedHrTimeMs}\n` +
`test_bucket{val="1",le="+Inf"} 3 ${mockedHrTimeMs}\n` +
`test_count{val="2"} 1 ${mockedHrTimeMs}\n` +
`test_sum{val="2"} 5 ${mockedHrTimeMs}\n` +
`test_bucket{val="2",le="1"} 0 ${mockedHrTimeMs}\n` +
`test_bucket{val="2",le="10"} 1 ${mockedHrTimeMs}\n` +
`test_bucket{val="2",le="100"} 1 ${mockedHrTimeMs}\n` +
`test_bucket{val="2",le="+Inf"} 1 ${mockedHrTimeMs}\n`
);
});
});
});
describe('serialize non-normalized values', () => {
describe('with SumAggregator', () => {
mockAggregator(SumAggregator);
it('should serialize records without labels', async () => {
const serializer = new PrometheusSerializer();
const meter = new MeterProvider({
processor: new ExactProcessor(SumAggregator),
}).getMeter('test');
const counter = meter.createCounter('test') as CounterMetric;
counter.bind({}).add(1);
const records = await counter.getMetricRecord();
const record = records[0];
const result = serializer.serializeRecord(
record.descriptor.name,
record
);
assert.strictEqual(result, `test 1 ${mockedHrTimeMs}\n`);
});
it('should serialize non-string label values', async () => {
const serializer = new PrometheusSerializer();
const meter = new MeterProvider({
processor: new ExactProcessor(SumAggregator),
}).getMeter('test');
const counter = meter.createCounter('test') as CounterMetric;
counter
.bind(({
object: {},
NaN: NaN,
null: null,
undefined: undefined,
} as unknown) as Labels)
.add(1);
const records = await counter.getMetricRecord();
const record = records[0];
const result = serializer.serializeRecord(
record.descriptor.name,
record
);
assert.strictEqual(
result,
`test{object="[object Object]",NaN="NaN",null="null",undefined="undefined"} 1 ${mockedHrTimeMs}\n`
);
});
it('should serialize non-finite values', async () => {
const serializer = new PrometheusSerializer();
const cases = [
[NaN, 'Nan'],
[-Infinity, '-Inf'],
[+Infinity, '+Inf'],
] as [number, string][];
for (const esac of cases) {
const meter = new MeterProvider({
processor: new ExactProcessor(SumAggregator),
}).getMeter('test');
const counter = meter.createUpDownCounter(
'test'
) as UpDownCounterMetric;
counter.bind(labels).add(esac[0]);
const records = await counter.getMetricRecord();
const record = records[0];
const result = serializer.serializeRecord(
record.descriptor.name,
record
);
assert.strictEqual(
result,
`test{foo1="bar1",foo2="bar2"} ${esac[1]} ${mockedHrTimeMs}\n`
);
}
});
it('should escape backslash (\\), double-quote ("), and line feed (\\n) in label values', async () => {
const serializer = new PrometheusSerializer();
const meter = new MeterProvider({
processor: new ExactProcessor(SumAggregator),
}).getMeter('test');
const counter = meter.createCounter('test') as CounterMetric;
counter
.bind(({
backslash: '\u005c', // \ => \\ (\u005c\u005c)
doubleQuote: '\u0022', // " => \" (\u005c\u0022)
lineFeed: '\u000a', // ↵ => \n (\u005c\u006e)
backslashN: '\u005c\u006e', // \n => \\n (\u005c\u005c\u006e)
backslashDoubleQuote: '\u005c\u0022', // \" => \\\" (\u005c\u005c\u005c\u0022)
backslashLineFeed: '\u005c\u000a', // \↵ => \\\n (\u005c\u005c\u005c\u006e)
} as unknown) as Labels)
.add(1);
const records = await counter.getMetricRecord();
const record = records[0];
const result = serializer.serializeRecord(
record.descriptor.name,
record
);
assert.strictEqual(
result,
'test{' +
'backslash="\u005c\u005c",' +
'doubleQuote="\u005c\u0022",' +
'lineFeed="\u005c\u006e",' +
'backslashN="\u005c\u005c\u006e",' +
'backslashDoubleQuote="\u005c\u005c\u005c\u0022",' +
'backslashLineFeed="\u005c\u005c\u005c\u006e"' +
`} 1 ${mockedHrTimeMs}\n`
);
});
});
});
});