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(sdk-metrics-base): distinguish between Sum and Gauge in MetricData #3079

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to experimental packages in this project will be documented
### :boom: Breaking Change

* feature(views): move views registration to MeterProvider constructor [#3066](https://github.com/open-telemetry/opentelemetry-js/pull/3066) @pichlermarc
* feat(sdk-metrics-base): distinguish between Sum and Gauge in MetricData [#3079](https://github.com/open-telemetry/opentelemetry-js/pull/3079) @pichlermarc
dyladan marked this conversation as resolved.
Show resolved Hide resolved

### :rocket: (Enhancement)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,23 +104,15 @@ function valueString(value: number) {
}

function toPrometheusType(
instrumentType: InstrumentType,
dataPointType: DataPointType,
metricData: MetricData,
): PrometheusDataTypeLiteral {
switch (dataPointType) {
case DataPointType.SINGULAR:
if (
instrumentType === InstrumentType.COUNTER ||
instrumentType === InstrumentType.OBSERVABLE_COUNTER
) {
switch (metricData.dataPointType) {
case DataPointType.SUM:
if (metricData.isMonotonic) {
return 'counter';
}
/**
* - HISTOGRAM
* - UP_DOWN_COUNTER
* - OBSERVABLE_GAUGE
* - OBSERVABLE_UP_DOWN_COUNTER
*/
return 'gauge';
case DataPointType.GAUGE:
return 'gauge';
case DataPointType.HISTOGRAM:
return 'histogram';
Expand Down Expand Up @@ -210,13 +202,13 @@ export class PrometheusSerializer {
metricData.descriptor.description || 'description missing'
)}`;
const type = `# TYPE ${name} ${toPrometheusType(
metricData.descriptor.type,
dataPointType
metricData
)}`;

let results = '';
switch (dataPointType) {
case DataPointType.SINGULAR: {
case DataPointType.SUM:
case DataPointType.GAUGE: {
results = metricData.dataPoints
.map(it => this._serializeSingularDataPoint(name, metricData.descriptor.type, it))
.join('');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ import * as assert from 'assert';
import { MetricAttributes, UpDownCounter } from '@opentelemetry/api-metrics';
import {
AggregationTemporality,
MeterProvider,
MetricReader,
DataPoint,
DataPointType,
ExplicitBucketHistogramAggregation,
SumAggregation,
Histogram,
LastValueAggregation,
MeterProvider,
MetricReader,
SumAggregation,
View,
} from '@opentelemetry/sdk-metrics-base';
import * as sinon from 'sinon';
Expand Down Expand Up @@ -85,7 +86,7 @@ describe('PrometheusSerializer', () => {
assert.strictEqual(resourceMetrics.scopeMetrics.length, 1);
assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1);
const metric = resourceMetrics.scopeMetrics[0].metrics[0];
assert.strictEqual(metric.dataPointType, DataPointType.SINGULAR);
assert.strictEqual(metric.dataPointType, DataPointType.SUM);
const pointData = metric.dataPoints as DataPoint<number>[];
assert.strictEqual(pointData.length, 1);

Expand Down Expand Up @@ -171,7 +172,7 @@ describe('PrometheusSerializer', () => {
});

describe('serialize an instrumentation metrics', () => {
describe('Singular', () => {
pichlermarc marked this conversation as resolved.
Show resolved Hide resolved
describe('monotonic Sum', () => {
async function testSerializer(serializer: PrometheusSerializer) {
const reader = new TestMetricReader();
const meterProvider = new MeterProvider({
Expand All @@ -196,7 +197,7 @@ describe('PrometheusSerializer', () => {
return result;
}

it('should serialize metric record with sum aggregator', async () => {
it('should serialize metric record', async () => {
const serializer = new PrometheusSerializer();
const result = await testSerializer(serializer);
assert.strictEqual(
Expand All @@ -208,7 +209,7 @@ describe('PrometheusSerializer', () => {
);
});

it('serialize metric record with sum aggregator without timestamp', async () => {
it('should serialize metric record without timestamp', async () => {
const serializer = new PrometheusSerializer(undefined, false);
const result = await testSerializer(serializer);
assert.strictEqual(
Expand All @@ -221,6 +222,108 @@ describe('PrometheusSerializer', () => {
});
});

describe('non-monotonic Sum', () => {
async function testSerializer(serializer: PrometheusSerializer) {
const reader = new TestMetricReader();
const meterProvider = new MeterProvider({
views: [
new View({ aggregation: new SumAggregation(), instrumentName: '*' })
]
});
meterProvider.addMetricReader(reader);
const meter = meterProvider.getMeter('test');

const counter = meter.createUpDownCounter('test_total', {
description: 'foobar',
});
counter.add(1, { val: '1' });
counter.add(1, { val: '2' });

const { resourceMetrics, errors } = await reader.collect();
assert.strictEqual(errors.length, 0);
assert.strictEqual(resourceMetrics.scopeMetrics.length, 1);
assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1);
const scopeMetrics = resourceMetrics.scopeMetrics[0];

return serializer['_serializeScopeMetrics'](scopeMetrics);
}

it('should serialize metric record', async () => {
const serializer = new PrometheusSerializer();
const result = await testSerializer(serializer);
assert.strictEqual(
result,
'# HELP test_total foobar\n' +
'# TYPE test_total gauge\n' +
`test_total{val="1"} 1 ${mockedHrTimeMs}\n` +
`test_total{val="2"} 1 ${mockedHrTimeMs}\n`
);
});

it('serialize metric record without timestamp', async () => {
const serializer = new PrometheusSerializer(undefined, false);
const result = await testSerializer(serializer);
assert.strictEqual(
result,
'# HELP test_total foobar\n' +
'# TYPE test_total gauge\n' +
'test_total{val="1"} 1\n' +
'test_total{val="2"} 1\n'
);
});
});

describe('Gauge', () => {
async function testSerializer(serializer: PrometheusSerializer) {
const reader = new TestMetricReader();
const meterProvider = new MeterProvider({
views: [
new View({aggregation: new LastValueAggregation(), instrumentName: '*' })
]
});
meterProvider.addMetricReader(reader);
const meter = meterProvider.getMeter('test');

const counter = meter.createUpDownCounter('test_total', {
description: 'foobar',
});
counter.add(1, { val: '1' });
counter.add(1, { val: '2' });

const { resourceMetrics, errors } = await reader.collect();
assert.strictEqual(errors.length, 0);
assert.strictEqual(resourceMetrics.scopeMetrics.length, 1);
assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1);
const scopeMetrics = resourceMetrics.scopeMetrics[0];

return serializer['_serializeScopeMetrics'](scopeMetrics);
}

it('should serialize metric record', async () => {
const serializer = new PrometheusSerializer();
const result = await testSerializer(serializer);
assert.strictEqual(
result,
'# HELP test_total foobar\n' +
'# TYPE test_total gauge\n' +
`test_total{val="1"} 1 ${mockedHrTimeMs}\n` +
`test_total{val="2"} 1 ${mockedHrTimeMs}\n`
);
});

it('serialize metric record without timestamp', async () => {
const serializer = new PrometheusSerializer(undefined, false);
const result = await testSerializer(serializer);
assert.strictEqual(
result,
'# HELP test_total foobar\n' +
'# TYPE test_total gauge\n' +
'test_total{val="1"} 1\n' +
'test_total{val="2"} 1\n'
);
});
});

describe('with ExplicitBucketHistogramAggregation', () => {
async function testSerializer(serializer: PrometheusSerializer) {
const reader = new TestMetricReader();
Expand Down Expand Up @@ -293,7 +396,7 @@ describe('PrometheusSerializer', () => {
assert.strictEqual(resourceMetrics.scopeMetrics.length, 1);
assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1);
const metric = resourceMetrics.scopeMetrics[0].metrics[0];
assert.strictEqual(metric.dataPointType, DataPointType.SINGULAR);
assert.strictEqual(metric.dataPointType, DataPointType.SUM);
const pointData = metric.dataPoints as DataPoint<number>[];
assert.strictEqual(pointData.length, 1);

Expand Down Expand Up @@ -333,7 +436,7 @@ describe('PrometheusSerializer', () => {
assert.strictEqual(resourceMetrics.scopeMetrics.length, 1);
assert.strictEqual(resourceMetrics.scopeMetrics[0].metrics.length, 1);
const metric = resourceMetrics.scopeMetrics[0].metrics[0];
assert.strictEqual(metric.dataPointType, DataPointType.SINGULAR);
assert.strictEqual(metric.dataPointType, DataPointType.SUM);
const pointData = metric.dataPoints as DataPoint<number>[];
assert.strictEqual(pointData.length, 1);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
* limitations under the License.
*/

import { LastValue, AggregatorKind, Aggregator, Accumulation, AccumulationRecord } from './types';
import { Accumulation, AccumulationRecord, Aggregator, AggregatorKind, LastValue } from './types';
import { HrTime } from '@opentelemetry/api';
import { hrTime, hrTimeToMicroseconds } from '@opentelemetry/core';
import { DataPointType, SingularMetricData } from '../export/MetricData';
import { DataPointType, GaugeMetricData } from '../export/MetricData';
import { InstrumentDescriptor } from '../InstrumentDescriptor';
import { Maybe } from '../utils';
import { AggregationTemporality } from '../export/AggregationTemporality';
Expand Down Expand Up @@ -74,11 +74,11 @@ export class LastValueAggregator implements Aggregator<LastValueAccumulation> {
descriptor: InstrumentDescriptor,
aggregationTemporality: AggregationTemporality,
accumulationByAttributes: AccumulationRecord<LastValueAccumulation>[],
endTime: HrTime): Maybe<SingularMetricData> {
endTime: HrTime): Maybe<GaugeMetricData> {
return {
descriptor,
aggregationTemporality,
dataPointType: DataPointType.SINGULAR,
dataPointType: DataPointType.GAUGE,
dataPoints: accumulationByAttributes.map(([attributes, accumulation]) => {
return {
attributes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { Sum, AggregatorKind, Aggregator, Accumulation, AccumulationRecord } from './types';
import { HrTime } from '@opentelemetry/api';
import { DataPointType, SingularMetricData } from '../export/MetricData';
import { DataPointType, SumMetricData } from '../export/MetricData';
import { InstrumentDescriptor } from '../InstrumentDescriptor';
import { Maybe } from '../utils';
import { AggregationTemporality } from '../export/AggregationTemporality';
Expand Down Expand Up @@ -83,19 +83,20 @@ export class SumAggregator implements Aggregator<SumAccumulation> {
descriptor: InstrumentDescriptor,
aggregationTemporality: AggregationTemporality,
accumulationByAttributes: AccumulationRecord<SumAccumulation>[],
endTime: HrTime): Maybe<SingularMetricData> {
endTime: HrTime): Maybe<SumMetricData> {
return {
descriptor,
aggregationTemporality,
dataPointType: DataPointType.SINGULAR,
dataPointType: DataPointType.SUM,
dataPoints: accumulationByAttributes.map(([attributes, accumulation]) => {
return {
attributes,
startTime: accumulation.startTime,
endTime,
value: accumulation.toPointValue(),
};
})
}),
isMonotonic: this.monotonic
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,14 @@ export interface BaseMetricData {
* Represents a metric data aggregated by either a LastValueAggregation or
* SumAggregation.
*/
export interface SingularMetricData extends BaseMetricData {
readonly dataPointType: DataPointType.SINGULAR;
export interface SumMetricData extends BaseMetricData {
readonly dataPointType: DataPointType.SUM;
readonly dataPoints: DataPoint<number>[];
readonly isMonotonic: boolean;
}

export interface GaugeMetricData extends BaseMetricData {
readonly dataPointType: DataPointType.GAUGE;
readonly dataPoints: DataPoint<number>[];
}

Expand All @@ -54,7 +60,7 @@ export interface HistogramMetricData extends BaseMetricData {
/**
* Represents an aggregated metric data.
*/
export type MetricData = SingularMetricData | HistogramMetricData;
export type MetricData = SumMetricData | GaugeMetricData | HistogramMetricData;

export interface ScopeMetrics {
scope: InstrumentationScope;
Expand Down Expand Up @@ -87,9 +93,6 @@ export interface CollectionResult {
* The aggregated point data type.
*/
export enum DataPointType {
/**
* A singular metric data point has only a single numeric value.
*/
SINGULAR,
pichlermarc marked this conversation as resolved.
Show resolved Hide resolved
/**
* A histogram data point contains a histogram statistics of collected
Expand All @@ -104,6 +107,15 @@ export enum DataPointType {
* and sum of all collected values.
*/
EXPONENTIAL_HISTOGRAM,
/**
* A gauge metric data point has only a single numeric value.
*/
GAUGE,
/**
* A sum metric data point has a single numeric value and a
* monotonicity-indicator.
*/
SUM
}

/**
Expand Down
Loading