Skip to content

Commit f6712fd

Browse files
authored
fix(sdk-metrics): ignore NaN value recordings for histograms (#4455)
* fix(sdk-metrics): ignore NaN value recordings * fix(changelog): add changelog entry * test(exporter-prometheus): adapt tests * fix(sdk-metrics): ignore in accumulation instead * fix(changelog): update changelog
1 parent 6d276f4 commit f6712fd

File tree

8 files changed

+143
-3
lines changed

8 files changed

+143
-3
lines changed

CHANGELOG.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ For experimental package changes, see the [experimental CHANGELOG](experimental/
1515

1616
### :bug: (Bug Fix)
1717

18-
* fix(sdk-metrics): handle zero bucket counts in exponential histogram merge
19-
[#4459](https://github.com/open-telemetry/opentelemetry-js/pull/4459) @mwear
18+
* fix(sdk-metrics): handle zero bucket counts in exponential histogram merge [#4459](https://github.com/open-telemetry/opentelemetry-js/pull/4459) @mwear
19+
* fix(sdk-metrics): ignore `NaN` value recordings in Histograms [#4455](https://github.com/open-telemetry/opentelemetry-js/pull/4455) @pichlermarc
20+
* fixes a bug where recording `NaN` on a histogram would result in the sum of bucket count values not matching the overall count
2021

2122
### :books: (Refine Doc)
2223

experimental/packages/opentelemetry-exporter-prometheus/test/PrometheusSerializer.test.ts

-1
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,6 @@ describe('PrometheusSerializer', () => {
622622
it('should serialize non-finite values', async () => {
623623
const serializer = new PrometheusSerializer();
624624
const cases = [
625-
[NaN, 'NaN'],
626625
[-Infinity, '-Inf'],
627626
[+Infinity, '+Inf'],
628627
] as [number, string][];

packages/sdk-metrics/src/aggregator/ExponentialHistogram.ts

+6
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,12 @@ export class ExponentialHistogramAccumulation implements Accumulation {
193193
* @param increment
194194
*/
195195
updateByIncrement(value: number, increment: number) {
196+
// NaN does not fall into any bucket, is not zero and should not be counted,
197+
// NaN is never greater than max nor less than min, therefore return as there's nothing for us to do.
198+
if (Number.isNaN(value)) {
199+
return;
200+
}
201+
196202
if (value > this._max) {
197203
this._max = value;
198204
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ export class HistogramAccumulation implements Accumulation {
7272
) {}
7373

7474
record(value: number): void {
75+
// NaN does not fall into any bucket, is not zero and should not be counted,
76+
// NaN is never greater than max nor less than min, therefore return as there's nothing for us to do.
77+
if (Number.isNaN(value)) {
78+
return;
79+
}
80+
7581
this._current.count += 1;
7682
this._current.sum += value;
7783

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

+2
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ describe('Instruments', () => {
247247
upDownCounter.add(1.1, { foo: 'bar' });
248248
// non-number values should be ignored.
249249
upDownCounter.add('1' as any);
250+
250251
await validateExport(deltaReader, {
251252
dataPointType: DataPointType.SUM,
252253
isMonotonic: false,
@@ -506,6 +507,7 @@ describe('Instruments', () => {
506507
histogram.record(0.1, { foo: 'bar' });
507508
// non-number values should be ignored.
508509
histogram.record('1' as any);
510+
histogram.record(NaN);
509511

510512
await validateExport(deltaReader, {
511513
dataPointType: DataPointType.HISTOGRAM,

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

+14
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,20 @@ describe('ExponentialHistogramAccumulation', () => {
215215
}
216216
});
217217
});
218+
219+
it('ignores NaN', () => {
220+
const accumulation = new ExponentialHistogramAccumulation([0, 0], 1);
221+
222+
accumulation.record(NaN);
223+
224+
assert.strictEqual(accumulation.scale, 0);
225+
assert.strictEqual(accumulation.max, -Infinity);
226+
assert.strictEqual(accumulation.min, Infinity);
227+
assert.strictEqual(accumulation.sum, 0);
228+
assert.strictEqual(accumulation.count, 0);
229+
assert.deepStrictEqual(getCounts(accumulation.positive), []);
230+
assert.deepStrictEqual(getCounts(accumulation.negative), []);
231+
});
218232
});
219233
describe('merge', () => {
220234
it('handles simple (even) case', () => {

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

+13
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,19 @@ describe('HistogramAccumulation', () => {
257257
accumulation.record(value);
258258
}
259259
});
260+
261+
it('ignores NaN', () => {
262+
const accumulation = new HistogramAccumulation([0, 0], [1, 10, 100]);
263+
264+
accumulation.record(NaN);
265+
266+
const pointValue = accumulation.toPointValue();
267+
assert.strictEqual(pointValue.max, -Infinity);
268+
assert.strictEqual(pointValue.min, Infinity);
269+
assert.strictEqual(pointValue.sum, 0);
270+
assert.strictEqual(pointValue.count, 0);
271+
assert.deepStrictEqual(pointValue.buckets.counts, [0, 0, 0, 0]);
272+
});
260273
});
261274

262275
describe('setStartTime', () => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as assert from 'assert';
18+
import {
19+
Aggregation,
20+
AggregationTemporality,
21+
MeterProvider,
22+
MetricReader,
23+
DataPoint,
24+
ExponentialHistogram,
25+
Histogram,
26+
} from '../../src';
27+
import { TestMetricReader } from '../export/TestMetricReader';
28+
29+
describe('histogram-recording-nan', () => {
30+
it('exponential histogram should not count NaN', async () => {
31+
const reader = new TestMetricReader({
32+
aggregationTemporalitySelector() {
33+
return AggregationTemporality.CUMULATIVE;
34+
},
35+
aggregationSelector(type) {
36+
return Aggregation.ExponentialHistogram();
37+
},
38+
});
39+
const meterProvider = new MeterProvider({
40+
readers: [reader],
41+
});
42+
43+
const meter = meterProvider.getMeter('my-meter');
44+
const hist = meter.createHistogram('testhist');
45+
46+
hist.record(1);
47+
hist.record(2);
48+
hist.record(4);
49+
hist.record(NaN);
50+
51+
const resourceMetrics1 = await collectNoErrors(reader);
52+
const dataPoint1 = resourceMetrics1.scopeMetrics[0].metrics[0]
53+
.dataPoints[0] as DataPoint<ExponentialHistogram>;
54+
55+
assert.deepStrictEqual(
56+
dataPoint1.value.count,
57+
3,
58+
'Sum of bucket count values should match overall count'
59+
);
60+
});
61+
62+
it('explicit bucket histogram should not count NaN', async () => {
63+
const reader = new TestMetricReader({
64+
aggregationTemporalitySelector() {
65+
return AggregationTemporality.CUMULATIVE;
66+
},
67+
aggregationSelector(type) {
68+
return Aggregation.Histogram();
69+
},
70+
});
71+
const meterProvider = new MeterProvider({
72+
readers: [reader],
73+
});
74+
75+
const meter = meterProvider.getMeter('my-meter');
76+
const hist = meter.createHistogram('testhist');
77+
78+
hist.record(1);
79+
hist.record(2);
80+
hist.record(4);
81+
hist.record(NaN);
82+
83+
const resourceMetrics1 = await collectNoErrors(reader);
84+
const dataPoint1 = resourceMetrics1.scopeMetrics[0].metrics[0]
85+
.dataPoints[0] as DataPoint<Histogram>;
86+
87+
assert.deepStrictEqual(
88+
dataPoint1.value.count,
89+
3,
90+
'Sum of bucket count values should match overall count'
91+
);
92+
});
93+
94+
const collectNoErrors = async (reader: MetricReader) => {
95+
const { resourceMetrics, errors } = await reader.collect();
96+
assert.strictEqual(errors.length, 0);
97+
return resourceMetrics;
98+
};
99+
});

0 commit comments

Comments
 (0)