Skip to content

Commit 0b8d310

Browse files
fix(sdk-metrics): use inclusive upper bounds in histogram (#4935)
Co-authored-by: Daniel Patrick <[email protected]>
1 parent 0ee398d commit 0b8d310

File tree

7 files changed

+43
-36
lines changed

7 files changed

+43
-36
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ For semantic convention package changes, see the [semconv CHANGELOG](packages/se
2424
* fix(sdk-node): avoid spurious diag errors for unknown OTEL_NODE_RESOURCE_DETECTORS values [#4879](https://github.com/open-telemetry/opentelemetry-js/pull/4879) @trentm
2525
* deps(opentelemetry-instrumentation): Bump `shimmer` types to 1.2.0 [#4865](https://github.com/open-telemetry/opentelemetry-js/pull/4865) @lforst
2626
* fix(instrumentation): Fix optional property types [#4833](https://github.com/open-telemetry/opentelemetry-js/pull/4833) @alecmev
27+
* fix(sdk-metrics): fix(sdk-metrics): use inclusive upper bounds in histogram [#4829](https://github.com/open-telemetry/opentelemetry-js/pull/4829)
2728

2829
### :books: (Refine Doc)
2930

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
} from '../export/MetricData';
2828
import { HrTime } from '@opentelemetry/api';
2929
import { InstrumentType } from '../InstrumentDescriptor';
30-
import { binarySearchLB, Maybe } from '../utils';
30+
import { binarySearchUB, Maybe } from '../utils';
3131
import { AggregationTemporality } from '../export/AggregationTemporality';
3232

3333
/**
@@ -87,8 +87,8 @@ export class HistogramAccumulation implements Accumulation {
8787
this._current.hasMinMax = true;
8888
}
8989

90-
const idx = binarySearchLB(this._boundaries, value);
91-
this._current.buckets.counts[idx + 1] += 1;
90+
const idx = binarySearchUB(this._boundaries, value);
91+
this._current.buckets.counts[idx] += 1;
9292
}
9393

9494
setStartTime(startTime: HrTime): void {

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,18 @@ export type LastValue = number;
3838
export interface Histogram {
3939
/**
4040
* Buckets are implemented using two different arrays:
41-
* - boundaries: contains every finite bucket boundary, which are inclusive lower bounds
41+
* - boundaries: contains every finite bucket boundary, which are inclusive upper bounds
4242
* - counts: contains event counts for each bucket
4343
*
4444
* Note that we'll always have n+1 buckets, where n is the number of boundaries.
45-
* This is because we need to count events that are below the lowest boundary.
45+
* This is because we need to count events that are higher than the upper boundary.
4646
*
4747
* Example: if we measure the values: [5, 30, 5, 40, 5, 15, 15, 15, 25]
4848
* with the boundaries [ 10, 20, 30 ], we will have the following state:
4949
*
5050
* buckets: {
5151
* boundaries: [10, 20, 30],
52-
* counts: [3, 3, 1, 2],
52+
* counts: [3, 3, 2, 1],
5353
* }
5454
*/
5555
buckets: {

packages/sdk-metrics/src/utils.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -165,30 +165,27 @@ export function setEquals(lhs: Set<unknown>, rhs: Set<unknown>): boolean {
165165
}
166166

167167
/**
168-
* Binary search the sorted array to the find lower bound for the value.
168+
* Binary search the sorted array to the find upper bound for the value.
169169
* @param arr
170170
* @param value
171171
* @returns
172172
*/
173-
export function binarySearchLB(arr: number[], value: number): number {
173+
export function binarySearchUB(arr: number[], value: number): number {
174174
let lo = 0;
175175
let hi = arr.length - 1;
176+
let ret = arr.length;
176177

177-
while (hi - lo > 1) {
178-
const mid = Math.trunc((hi + lo) / 2);
179-
if (arr[mid] <= value) {
180-
lo = mid;
178+
while (hi >= lo) {
179+
const mid = lo + Math.trunc((hi - lo) / 2);
180+
if (arr[mid] < value) {
181+
lo = mid + 1;
181182
} else {
183+
ret = mid;
182184
hi = mid - 1;
183185
}
184186
}
185187

186-
if (arr[hi] <= value) {
187-
return hi;
188-
} else if (arr[lo] <= value) {
189-
return lo;
190-
}
191-
return -1;
188+
return ret;
192189
}
193190

194191
export function equalsCaseInsensitive(lhs: string, rhs: string): boolean {

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ describe('Instruments', () => {
325325
0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000,
326326
7500, 10000,
327327
],
328-
counts: [0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
328+
counts: [1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
329329
},
330330
count: 2,
331331
sum: 10,
@@ -341,7 +341,7 @@ describe('Instruments', () => {
341341
0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000,
342342
7500, 10000,
343343
],
344-
counts: [0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
344+
counts: [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
345345
},
346346
count: 2,
347347
sum: 100,
@@ -395,7 +395,7 @@ describe('Instruments', () => {
395395
value: {
396396
buckets: {
397397
boundaries: [1, 9, 100],
398-
counts: [1, 0, 0, 1],
398+
counts: [1, 0, 1, 0],
399399
},
400400
count: 2,
401401
sum: 100,
@@ -484,7 +484,7 @@ describe('Instruments', () => {
484484
0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000,
485485
7500, 10000,
486486
],
487-
counts: [0, 0, 0, 2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
487+
counts: [0, 0, 1, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0],
488488
},
489489
count: 4,
490490
sum: 220,
@@ -534,7 +534,7 @@ describe('Instruments', () => {
534534
0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000,
535535
7500, 10000,
536536
],
537-
counts: [0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
537+
counts: [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
538538
},
539539
count: 2,
540540
sum: 10.1,
@@ -550,7 +550,7 @@ describe('Instruments', () => {
550550
0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000,
551551
7500, 10000,
552552
],
553-
counts: [0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
553+
counts: [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
554554
},
555555
count: 2,
556556
sum: 100.1,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ describe('HistogramAggregator', () => {
189189
value: {
190190
buckets: {
191191
boundaries: [1, 10, 100],
192-
counts: [1, 1, 0, 0],
192+
counts: [2, 0, 0, 0],
193193
},
194194
count: 2,
195195
sum: 1,
@@ -231,7 +231,7 @@ describe('HistogramAggregator', () => {
231231
value: {
232232
buckets: {
233233
boundaries: [1, 10, 100],
234-
counts: [1, 1, 0, 0],
234+
counts: [2, 0, 0, 0],
235235
},
236236
count: 2,
237237
sum: 1,

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

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import * as sinon from 'sinon';
1818
import * as assert from 'assert';
1919
import {
20-
binarySearchLB,
20+
binarySearchUB,
2121
callWithTimeout,
2222
hashAttributes,
2323
TimeoutError,
@@ -66,26 +66,35 @@ describe('utils', () => {
6666
});
6767
});
6868

69-
describe('binarySearchLB', () => {
69+
describe('binarySearchUB', () => {
7070
const tests = [
7171
/** [ arr, value, expected lb idx ] */
72-
[[0, 10, 100, 1000], -1, -1],
72+
[[0, 10, 100, 1000], -1, 0],
7373
[[0, 10, 100, 1000], 0, 0],
74-
[[0, 10, 100, 1000], 1, 0],
74+
[[0, 10, 100, 1000], 1, 1],
7575
[[0, 10, 100, 1000], 10, 1],
76+
[[0, 10, 100, 1000], 100, 2],
77+
[[0, 10, 100, 1000], 101, 3],
7678
[[0, 10, 100, 1000], 1000, 3],
77-
[[0, 10, 100, 1000], 1001, 3],
79+
[[0, 10, 100, 1000], 1001, 4],
7880

79-
[[0, 10, 100, 1000, 10_000], -1, -1],
81+
[[0, 10, 100, 1000, 10_000], -1, 0],
8082
[[0, 10, 100, 1000, 10_000], 0, 0],
8183
[[0, 10, 100, 1000, 10_000], 10, 1],
82-
[[0, 10, 100, 1000, 10_000], 1001, 3],
83-
[[0, 10, 100, 1000, 10_000], 10_001, 4],
84+
[[0, 10, 100, 1000, 10_000], 100, 2],
85+
[[0, 10, 100, 1000, 10_000], 101, 3],
86+
[[0, 10, 100, 1000, 10_000], 1001, 4],
87+
[[0, 10, 100, 1000, 10_000], 10_001, 5],
88+
89+
[[], 1, 0],
90+
[[1], 0, 0],
91+
[[1], 1, 0],
92+
[[1], 2, 1],
8493
] as [number[], number, number][];
8594

8695
for (const [idx, test] of tests.entries()) {
87-
it(`test idx(${idx}): find lb of ${test[1]} in [${test[0]}]`, () => {
88-
assert.strictEqual(binarySearchLB(test[0], test[1]), test[2]);
96+
it(`test idx(${idx}): find ub of ${test[1]} in [${test[0]}]`, () => {
97+
assert.strictEqual(binarySearchUB(test[0], test[1]), test[2]);
8998
});
9099
}
91100
});

0 commit comments

Comments
 (0)