Skip to content

Commit eba8204

Browse files
committed
feat(sdk-metrics): apply binary search in histogram
1 parent 5127371 commit eba8204

File tree

4 files changed

+63
-11
lines changed

4 files changed

+63
-11
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ For experimental package changes, see the [experimental CHANGELOG](experimental/
1111

1212
### :rocket: (Enhancement)
1313

14+
* feat(sdk-metrics): apply binary search in histogram recording [#3539](https://github.com/open-telemetry/opentelemetry-js/pull/3539) @legendecas
15+
1416
### :bug: (Bug Fix)
1517

1618
### :books: (Refine Doc)

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

+4-10
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
import { DataPointType, HistogramMetricData } from '../export/MetricData';
2424
import { HrTime } from '@opentelemetry/api';
2525
import { InstrumentDescriptor, InstrumentType } from '../InstrumentDescriptor';
26-
import { Maybe } from '../utils';
26+
import { binarySearchLB, Maybe } from '../utils';
2727
import { AggregationTemporality } from '../export/AggregationTemporality';
2828

2929
/**
@@ -77,14 +77,8 @@ export class HistogramAccumulation implements Accumulation {
7777
this._current.hasMinMax = true;
7878
}
7979

80-
for (let i = 0; i < this._boundaries.length; i++) {
81-
if (value < this._boundaries[i]) {
82-
this._current.buckets.counts[i] += 1;
83-
return;
84-
}
85-
}
86-
// value is above all observed boundaries
87-
this._current.buckets.counts[this._boundaries.length] += 1;
80+
const idx = binarySearchLB(this._boundaries, value);
81+
this._current.buckets.counts[idx + 1] += 1;
8882
}
8983

9084
setStartTime(startTime: HrTime): void {
@@ -104,7 +98,7 @@ export class HistogramAggregator implements Aggregator<HistogramAccumulation> {
10498
public kind: AggregatorKind.HISTOGRAM = AggregatorKind.HISTOGRAM;
10599

106100
/**
107-
* @param _boundaries upper bounds of recorded values.
101+
* @param _boundaries sorted upper bounds of recorded values.
108102
* @param _recordMinMax If set to true, min and max will be recorded. Otherwise, min and max will not be recorded.
109103
*/
110104
constructor(

packages/sdk-metrics/src/utils.ts

+27
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,30 @@ export function setEquals(lhs: Set<unknown>, rhs: Set<unknown>): boolean {
163163
}
164164
return true;
165165
}
166+
167+
/**
168+
* Binary search the sorted array to the find lower bound for the value.
169+
* @param arr
170+
* @param value
171+
* @returns
172+
*/
173+
export function binarySearchLB(arr: number[], value: number): number {
174+
let lo = 0;
175+
let hi = arr.length - 1;
176+
177+
while (hi - lo > 1) {
178+
const mid = Math.trunc((hi + lo) / 2);
179+
if (arr[mid] <= value) {
180+
lo = mid;
181+
} else {
182+
hi = mid - 1;
183+
}
184+
}
185+
186+
if (arr[hi] <= value) {
187+
return hi;
188+
} else if (arr[lo] <= value) {
189+
return lo;
190+
}
191+
return -1;
192+
}

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

+30-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616

1717
import * as sinon from 'sinon';
1818
import * as assert from 'assert';
19-
import { callWithTimeout, hashAttributes, TimeoutError } from '../src/utils';
19+
import {
20+
binarySearchLB,
21+
callWithTimeout,
22+
hashAttributes,
23+
TimeoutError,
24+
} from '../src/utils';
2025
import { assertRejects } from './test-utils';
2126
import { MetricAttributes } from '@opentelemetry/api';
2227

@@ -60,4 +65,28 @@ describe('utils', () => {
6065
}
6166
});
6267
});
68+
69+
describe('binarySearchLB', () => {
70+
const tests = [
71+
/** [ arr, value, expected lb idx ] */
72+
[[0, 10, 100, 1000], -1, -1],
73+
[[0, 10, 100, 1000], 0, 0],
74+
[[0, 10, 100, 1000], 1, 0],
75+
[[0, 10, 100, 1000], 10, 1],
76+
[[0, 10, 100, 1000], 1000, 3],
77+
[[0, 10, 100, 1000], 1001, 3],
78+
79+
[[0, 10, 100, 1000, 10_000], -1, -1],
80+
[[0, 10, 100, 1000, 10_000], 0, 0],
81+
[[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+
] as [number[], number, number][];
85+
86+
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]);
89+
});
90+
}
91+
});
6392
});

0 commit comments

Comments
 (0)