Skip to content

Commit

Permalink
feat(sdk-metrics-base): implement min/max recording for Histograms (#…
Browse files Browse the repository at this point in the history
…3032)

* feat(sdk-metrics-base): prepare min/max collection.

* chore: updating submodule for otlp-grpc-exporter-base

* chore: updating submodule for otlp-proto-exporter-base

* feat(sdk-trace-base): implement min/max recording, add tests.

* feat(docs): update metrics exporter compatibility.

* fix(changelog): add changelog entry.

* fix(changelog): add more detail to changelog.

* fix(changelog): reword, appease linter.

* refactor(sdk-metrics-base): inline hasMinMax(), reformat.

* fix(otlp-transformer): simplify distinction with for data with min/max and without.

Co-authored-by: Daniel Dyla <[email protected]>
Co-authored-by: Gerhard Stöbich <[email protected]>
  • Loading branch information
3 people authored Jun 24, 2022
1 parent fa295a3 commit d2de661
Show file tree
Hide file tree
Showing 19 changed files with 323 additions and 148 deletions.
2 changes: 1 addition & 1 deletion examples/otlp-exporter-node/docker/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: "3"
services:
# Collector
collector:
image: otel/opentelemetry-collector-contrib:0.50.0
image: otel/opentelemetry-collector-contrib:0.53.0
# image: otel/opentelemetry-collector-contrib:latest
command: ["--config=/conf/collector-config.yaml"]
volumes:
Expand Down
5 changes: 5 additions & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ All notable changes to experimental packages in this project will be documented
### :boom: Breaking Change

* fix: remove aws and gcp detector from SDK #3024 @flarna
* feat(sdk-metrics-base): implement min/max recording for Histograms #3032 @pichlermarc
* adds `min`/`max` recording to Histograms
* updates [opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto) to `0.18` so that `min` and
`max` can be exported. This change breaks the OTLP/JSON Metric Exporter for all collector versions `<0.52` due to
[open-telemetry/opentelemetry-collector#5312](https://github.com/open-telemetry/opentelemetry-collector/issues/5312).

### :rocket: (Enhancement)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Apache License][license-image]][license-image]

This module provides exporter for web and node to be used with [opentelemetry-collector][opentelemetry-collector-url].
Compatible with [opentelemetry-collector][opentelemetry-collector-url] versions `>=0.16 <=0.50`.
Compatible with [opentelemetry-collector][opentelemetry-collector-url] versions `>=0.16 <=0.53`.

## Installation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,12 @@ export function ensureExportedHistogramIsCorrect(
exemplars: [],
flags: 0,
_sum: 'sum',
_min: 'min',
_max: 'max',
sum: 21,
count: '2',
min: 7,
max: 14,
startTimeUnixNano: String(startTime),
timeUnixNano: String(time),
bucketCounts,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Apache License][license-image]][license-image]

This module provides exporter for web and node to be used with [opentelemetry-collector][opentelemetry-collector-url].
Compatible with [opentelemetry-collector][opentelemetry-collector-url] versions `>=0.48 <=0.50`.
Compatible with [opentelemetry-collector][opentelemetry-collector-url] versions `>=0.52 <=0.53`.

## Installation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,8 @@ export function ensureHistogramIsCorrect(
attributes: [],
sum: 21,
count: 2,
min: 7,
max: 14,
startTimeUnixNano: startTime,
timeUnixNano: time,
bucketCounts,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Apache License][license-image]][license-image]

This module provides exporter for node to be used with [opentelemetry-collector][opentelemetry-collector-url].
Compatible with [opentelemetry-collector][opentelemetry-collector-url] versions `>=0.32 <=0.50`.
Compatible with [opentelemetry-collector][opentelemetry-collector-url] versions `>=0.32 <=0.53`.

## Installation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ export function ensureExportedHistogramIsCorrect(
{
sum: 21,
count: '2',
min: 7,
max: 14,
startTimeUnixNano: String(startTime),
timeUnixNano: String(time),
bucketCounts,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,29 @@ function createNewEmptyCheckpoint(boundaries: number[]): Histogram {
},
sum: 0,
count: 0,
hasMinMax: false,
min: Infinity,
max: -1
};
}

export class HistogramAccumulation implements Accumulation {
constructor(
private readonly _boundaries: number[],
private _recordMinMax = true,
private _current: Histogram = createNewEmptyCheckpoint(_boundaries)
) {}

record(value: number): void {
this._current.count += 1;
this._current.sum += value;

if (this._recordMinMax) {
this._current.min = Math.min(value, this._current.min);
this._current.max = Math.max(value, this._current.max);
this._current.hasMinMax = true;
}

for (let i = 0; i < this._boundaries.length; i++) {
if (value < this._boundaries[i]) {
this._current.buckets.counts[i] += 1;
Expand All @@ -74,11 +84,12 @@ export class HistogramAggregator implements Aggregator<HistogramAccumulation> {

/**
* @param _boundaries upper bounds of recorded values.
* @param _recordMinMax If set to true, min and max will be recorded. Otherwise, min and max will not be recorded.
*/
constructor(private readonly _boundaries: number[]) {}
constructor(private readonly _boundaries: number[], private readonly _recordMinMax: boolean) {}

createAccumulation() {
return new HistogramAccumulation(this._boundaries);
return new HistogramAccumulation(this._boundaries, this._recordMinMax);
}

/**
Expand All @@ -98,13 +109,32 @@ export class HistogramAggregator implements Aggregator<HistogramAccumulation> {
mergedCounts[idx] = previousCounts[idx] + deltaCounts[idx];
}

return new HistogramAccumulation(previousValue.buckets.boundaries, {
let min = Infinity;
let max = -1;

if (this._recordMinMax) {
if (previousValue.hasMinMax && deltaValue.hasMinMax) {
min = Math.min(previousValue.min, deltaValue.min);
max = Math.max(previousValue.max, deltaValue.max);
} else if (previousValue.hasMinMax) {
min = previousValue.min;
max = previousValue.max;
} else if (deltaValue.hasMinMax) {
min = deltaValue.min;
max = deltaValue.max;
}
}

return new HistogramAccumulation(previousValue.buckets.boundaries, this._recordMinMax, {
buckets: {
boundaries: previousValue.buckets.boundaries,
counts: mergedCounts,
},
count: previousValue.count + deltaValue.count,
sum: previousValue.sum + deltaValue.sum,
hasMinMax: this._recordMinMax && (previousValue.hasMinMax || deltaValue.hasMinMax),
min: min,
max: max
});
}

Expand All @@ -123,13 +153,16 @@ export class HistogramAggregator implements Aggregator<HistogramAccumulation> {
diffedCounts[idx] = currentCounts[idx] - previousCounts[idx];
}

return new HistogramAccumulation(previousValue.buckets.boundaries, {
return new HistogramAccumulation(previousValue.buckets.boundaries, this._recordMinMax, {
buckets: {
boundaries: previousValue.buckets.boundaries,
counts: diffedCounts,
},
count: currentValue.count - previousValue.count,
sum: currentValue.sum - previousValue.sum,
hasMinMax: false,
min: Infinity,
max: -1
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ export interface Histogram {
};
sum: number;
count: number;
hasMinMax: boolean;
min: number;
max: number;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class LastValueAggregation extends Aggregation {
* The default histogram aggregation.
*/
export class HistogramAggregation extends Aggregation {
private static DEFAULT_INSTANCE = new HistogramAggregator([0, 5, 10, 25, 50, 75, 100, 250, 500, 1000]);
private static DEFAULT_INSTANCE = new HistogramAggregator([0, 5, 10, 25, 50, 75, 100, 250, 500, 1000], true);
createAggregator(_instrument: InstrumentDescriptor) {
return HistogramAggregation.DEFAULT_INSTANCE;
}
Expand All @@ -94,10 +94,12 @@ export class HistogramAggregation extends Aggregation {
*/
export class ExplicitBucketHistogramAggregation extends Aggregation {
private _boundaries: number[];

/**
* @param boundaries the bucket boundaries of the histogram aggregation
* @param _recordMinMax If set to true, min and max will be recorded. Otherwise, min and max will not be recorded.
*/
constructor(boundaries: number[]) {
constructor(boundaries: number[], private readonly _recordMinMax = true) {
super();
if (boundaries === undefined || boundaries.length === 0) {
throw new Error('HistogramAggregator should be created with boundaries.');
Expand All @@ -117,7 +119,7 @@ export class ExplicitBucketHistogramAggregation extends Aggregation {
}

createAggregator(_instrument: InstrumentDescriptor) {
return new HistogramAggregator(this._boundaries);
return new HistogramAggregator(this._boundaries, this._recordMinMax);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,9 @@ describe('Instruments', () => {
},
count: 2,
sum: 10,
hasMinMax: true,
max: 10,
min: 0
},
},
{
Expand All @@ -306,12 +309,86 @@ describe('Instruments', () => {
},
count: 2,
sum: 100,
hasMinMax: true,
max: 100,
min: 0
},
},
],
});
});

it('should collect min and max', async () => {
const { meter, deltaReader, cumulativeReader } = setup();
const histogram = meter.createHistogram('test', {
valueType: ValueType.INT,
});

histogram.record(10);
histogram.record(100);
await deltaReader.collect();
await cumulativeReader.collect();

histogram.record(20);
histogram.record(90);

// Delta should only have min/max of values recorded after the collection.
await validateExport(deltaReader, {
descriptor: {
name: 'test',
description: '',
unit: '',
type: InstrumentType.HISTOGRAM,
valueType: ValueType.INT,
},
dataPointType: DataPointType.HISTOGRAM,
dataPoints: [
{
attributes: {},
value: {
buckets: {
boundaries: [0, 5, 10, 25, 50, 75, 100, 250, 500, 1000],
counts: [0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0],
},
count: 2,
sum: 110,
hasMinMax: true,
min: 20,
max: 90
},
}
],
});

// Cumulative should have min/max of all recorded values.
await validateExport(cumulativeReader, {
descriptor: {
name: 'test',
description: '',
unit: '',
type: InstrumentType.HISTOGRAM,
valueType: ValueType.INT,
},
dataPointType: DataPointType.HISTOGRAM,
dataPoints: [
{
attributes: {},
value: {
buckets: {
boundaries: [0, 5, 10, 25, 50, 75, 100, 250, 500, 1000],
counts: [0, 0, 0, 2, 0, 0, 1, 1, 0, 0, 0],
},
count: 4,
sum: 220,
hasMinMax: true,
min: 10,
max: 100
},
}
],
});
});

it('should not record negative INT values', async () => {
const { meter, deltaReader } = setup();
const histogram = meter.createHistogram('test', {
Expand Down Expand Up @@ -347,6 +424,9 @@ describe('Instruments', () => {
},
count: 2,
sum: 10.1,
hasMinMax: true,
max: 10,
min: 0.1
},
},
{
Expand All @@ -358,6 +438,9 @@ describe('Instruments', () => {
},
count: 2,
sum: 100.1,
hasMinMax: true,
max: 100,
min: 0.1
},
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ import { commonValues, defaultInstrumentDescriptor } from '../util';
describe('HistogramAggregator', () => {
describe('createAccumulation', () => {
it('no exceptions on createAccumulation', () => {
const aggregator = new HistogramAggregator([1, 10, 100]);
const aggregator = new HistogramAggregator([1, 10, 100], true);
const accumulation = aggregator.createAccumulation();
assert(accumulation instanceof HistogramAccumulation);
});
});

describe('merge', () => {
it('no exceptions', () => {
const aggregator = new HistogramAggregator([1, 10, 100]);
const aggregator = new HistogramAggregator([1, 10, 100], true);
const prev = aggregator.createAccumulation();
prev.record(0);
prev.record(1);
Expand All @@ -55,7 +55,7 @@ describe('HistogramAggregator', () => {

describe('diff', () => {
it('no exceptions', () => {
const aggregator = new HistogramAggregator([1, 10, 100]);
const aggregator = new HistogramAggregator([1, 10, 100], true);
const prev = aggregator.createAccumulation();
prev.record(0);
prev.record(1);
Expand All @@ -68,13 +68,16 @@ describe('HistogramAggregator', () => {
curr.record(2);
curr.record(11);

const expected = new HistogramAccumulation([1, 10, 100], {
const expected = new HistogramAccumulation([1, 10, 100], true, {
buckets: {
boundaries: [1, 10, 100],
counts: [0, 1, 1, 0],
},
count: 2,
sum: 13,
hasMinMax: false,
min: Infinity,
max: -1
});

assert.deepStrictEqual(aggregator.diff(prev, curr), expected);
Expand All @@ -83,7 +86,7 @@ describe('HistogramAggregator', () => {

describe('toMetricData', () => {
it('transform without exception', () => {
const aggregator = new HistogramAggregator([1, 10, 100]);
const aggregator = new HistogramAggregator([1, 10, 100], true);

const accumulation = aggregator.createAccumulation();
accumulation.record(0);
Expand All @@ -108,6 +111,9 @@ describe('HistogramAggregator', () => {
},
count: 2,
sum: 1,
hasMinMax: true,
min: 0,
max: 1
},
},
],
Expand Down
Loading

0 comments on commit d2de661

Please sign in to comment.