Skip to content

Commit 63845ec

Browse files
authored
fix(sdk-metrics): allow single bucket histograms (#4456)
* fix(sdk-metrics): allow single bucket histograms * test(sdk-metrics): undefined and null inputs for bucket boundaries * fixup! test(sdk-metrics): undefined and null inputs for bucket boundaries
1 parent f6712fd commit 63845ec

File tree

5 files changed

+135
-2
lines changed

5 files changed

+135
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ For experimental package changes, see the [experimental CHANGELOG](experimental/
1111

1212
### :rocket: (Enhancement)
1313

14+
* feat(sdk-metrics): allow single bucket histograms [#4456](https://github.com/open-telemetry/opentelemetry-js/pull/4456) @pichlermarc
1415
* feat(instrumentation): Make `init()` method public [#4418](https://github.com/open-telemetry/opentelemetry-js/pull/4418)
1516

1617
### :bug: (Bug Fix)
1718

1819
* fix(sdk-metrics): handle zero bucket counts in exponential histogram merge [#4459](https://github.com/open-telemetry/opentelemetry-js/pull/4459) @mwear
1920
* fix(sdk-metrics): ignore `NaN` value recordings in Histograms [#4455](https://github.com/open-telemetry/opentelemetry-js/pull/4455) @pichlermarc
2021
* fixes a bug where recording `NaN` on a histogram would result in the sum of bucket count values not matching the overall count
22+
* fix(sdk-metrics): allow single bucket histograms [#4456](https://github.com/open-telemetry/opentelemetry-js/pull/4456) @pichlermarc
23+
* fixes a bug where `Meter.createHistogram()` with the advice `explicitBucketBoundaries: []` would throw
2124

2225
### :books: (Refine Doc)
2326

packages/sdk-metrics/src/view/Aggregation.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,10 @@ export class ExplicitBucketHistogramAggregation extends Aggregation {
130130
private readonly _recordMinMax = true
131131
) {
132132
super();
133-
if (boundaries === undefined || boundaries.length === 0) {
134-
throw new Error('HistogramAggregator should be created with boundaries.');
133+
if (boundaries == null) {
134+
throw new Error(
135+
'ExplicitBucketHistogramAggregation should be created with explicit boundaries, if a single bucket histogram is required, please pass an empty array'
136+
);
135137
}
136138
// Copy the boundaries array for modification.
137139
boundaries = boundaries.concat();

packages/sdk-metrics/test/Instruments.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,20 @@ describe('Instruments', () => {
407407
});
408408
});
409409

410+
it('should allow metric advice with empty explicit boundaries', function () {
411+
const meter = new MeterProvider({
412+
readers: [new TestMetricReader()],
413+
}).getMeter('meter');
414+
415+
assert.doesNotThrow(() => {
416+
meter.createHistogram('histogram', {
417+
advice: {
418+
explicitBucketBoundaries: [],
419+
},
420+
});
421+
});
422+
});
423+
410424
it('should collect min and max', async () => {
411425
const { meter, deltaReader, cumulativeReader } = setup();
412426
const histogram = meter.createHistogram('test', {

packages/sdk-metrics/test/aggregator/Histogram.test.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,30 @@ describe('HistogramAggregator', () => {
8181
sum: -65,
8282
});
8383
});
84+
85+
it('with single bucket', function () {
86+
const aggregator = new HistogramAggregator([], true);
87+
const prev = aggregator.createAccumulation([0, 0]);
88+
prev.record(0);
89+
prev.record(1);
90+
91+
const delta = aggregator.createAccumulation([1, 1]);
92+
delta.record(2);
93+
delta.record(11);
94+
95+
const expected = new HistogramAccumulation([0, 0], [], true, {
96+
buckets: {
97+
boundaries: [],
98+
counts: [4],
99+
},
100+
count: 4,
101+
sum: 14,
102+
hasMinMax: true,
103+
min: 0,
104+
max: 11,
105+
});
106+
assert.deepStrictEqual(aggregator.merge(prev, delta), expected);
107+
});
84108
});
85109

86110
describe('diff', () => {
@@ -112,6 +136,35 @@ describe('HistogramAggregator', () => {
112136

113137
assert.deepStrictEqual(aggregator.diff(prev, curr), expected);
114138
});
139+
140+
it('with single bucket', function () {
141+
const aggregator = new HistogramAggregator([], true);
142+
const prev = aggregator.createAccumulation([0, 0]);
143+
prev.record(0);
144+
prev.record(1);
145+
146+
const curr = aggregator.createAccumulation([1, 1]);
147+
// replay actions on prev
148+
curr.record(0);
149+
curr.record(1);
150+
// perform new actions
151+
curr.record(2);
152+
curr.record(11);
153+
154+
const expected = new HistogramAccumulation([1, 1], [], true, {
155+
buckets: {
156+
boundaries: [],
157+
counts: [2],
158+
},
159+
count: 2,
160+
sum: 13,
161+
hasMinMax: false,
162+
min: Infinity,
163+
max: -Infinity,
164+
});
165+
166+
assert.deepStrictEqual(aggregator.diff(prev, curr), expected);
167+
});
115168
});
116169

117170
describe('toMetricData', () => {
@@ -199,6 +252,48 @@ describe('HistogramAggregator', () => {
199252
);
200253
});
201254

255+
it('should transform to expected data with empty boundaries', () => {
256+
const aggregator = new HistogramAggregator([], false);
257+
258+
const startTime: HrTime = [0, 0];
259+
const endTime: HrTime = [1, 1];
260+
const accumulation = aggregator.createAccumulation(startTime);
261+
accumulation.record(0);
262+
accumulation.record(1);
263+
264+
const expected: MetricData = {
265+
descriptor: defaultInstrumentDescriptor,
266+
aggregationTemporality: AggregationTemporality.CUMULATIVE,
267+
dataPointType: DataPointType.HISTOGRAM,
268+
dataPoints: [
269+
{
270+
attributes: {},
271+
startTime,
272+
endTime,
273+
value: {
274+
buckets: {
275+
boundaries: [],
276+
counts: [2],
277+
},
278+
count: 2,
279+
sum: 1,
280+
min: undefined,
281+
max: undefined,
282+
},
283+
},
284+
],
285+
};
286+
assert.deepStrictEqual(
287+
aggregator.toMetricData(
288+
defaultInstrumentDescriptor,
289+
AggregationTemporality.CUMULATIVE,
290+
[[{}, accumulation]],
291+
endTime
292+
),
293+
expected
294+
);
295+
});
296+
202297
function testSum(instrumentType: InstrumentType, expectSum: boolean) {
203298
const aggregator = new HistogramAggregator([1, 10, 100], true);
204299

packages/sdk-metrics/test/view/Aggregation.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,25 @@ describe('ExplicitBucketHistogramAggregation', () => {
158158
}
159159
});
160160

161+
it('construct with empty boundaries', function () {
162+
const boundaries: number[] = [];
163+
const aggregation = new ExplicitBucketHistogramAggregation(boundaries);
164+
assert.ok(aggregation instanceof ExplicitBucketHistogramAggregation);
165+
assert.deepStrictEqual(aggregation['_boundaries'], []);
166+
});
167+
168+
it('construct with undefined boundaries should throw', function () {
169+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
170+
// @ts-ignore simulate how a JS user could pass undefined
171+
assert.throws(() => new ExplicitBucketHistogramAggregation(undefined));
172+
});
173+
174+
it('construct with null boundaries should throw', function () {
175+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
176+
// @ts-ignore simulate how a JS user could pass null
177+
assert.throws(() => new ExplicitBucketHistogramAggregation(null));
178+
});
179+
161180
it('constructor should not modify inputs', () => {
162181
const boundaries = [100, 10, 1];
163182
const aggregation = new ExplicitBucketHistogramAggregation(boundaries);

0 commit comments

Comments
 (0)