Skip to content

Commit 628bf6b

Browse files
authored
[processor/transform] Optimize limit_buckets compaction divisor (#49021)
Compacts `merge_histogram_buckets(..., method="limit_buckets")` with the minimum divisor that keeps the output at or below the requested bucket limit, instead of repeatedly halving pairs. Updates docs and adds regression coverage for 10 buckets limited to 4. Fixes #49020
1 parent 53a2437 commit 628bf6b

4 files changed

Lines changed: 38 additions & 12 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
change_type: enhancement
2+
3+
component: processor/transform
4+
5+
note: Improve `merge_histogram_buckets` with `method="limit_buckets"` to compact buckets closer to the configured limit.
6+
7+
issues: [49020]
8+
9+
subtext:
10+
11+
change_logs: [user]

processor/transformprocessor/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,7 @@ The `merge_histogram_buckets` function merges explicit histogram buckets. The `m
671671

672672
`target_value` is interpreted according to `method`:
673673
- `remove_explicit_bound`: `target_value` is the explicit boundary to remove. The function merges the bucket ending at this boundary with the next bucket. This method uses floating-point tolerance (epsilon = 1e-12) when matching the boundary.
674-
- `limit_buckets`: `target_value` is the maximum number of buckets to keep. It must be a positive integer. The function reduces resolution in uniform compaction passes until the histogram has no more than `target_value` buckets. In each pass, it merges adjacent bucket pairs from lower to higher bucket order, combines their counts, and keeps an unpaired final bucket unchanged. Bucket count values and boundary widths do not affect which buckets are merged. Because each pass roughly halves the number of buckets, the resulting histogram may have fewer than `target_value` buckets.
674+
- `limit_buckets`: `target_value` is the maximum number of buckets to keep. It must be a positive integer. The function reduces resolution with a single uniform compaction pass. It chooses the smallest divisor that keeps the resulting bucket count at or below `target_value`, merges adjacent buckets in groups of that size from lower to higher bucket order, combines their counts, and keeps any partial final group. Bucket count values and boundary widths do not affect which buckets are merged. The resulting histogram may have fewer than `target_value` buckets when no smaller uniform divisor can stay within the limit.
675675

676676
The function:
677677
- Preserves the total count and sum of the histogram.

processor/transformprocessor/internal/metrics/func_merge_histogram_buckets.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -166,25 +166,29 @@ func limitHistogramBucketsFromDataPoint(dp pmetric.HistogramDataPoint, maxBucket
166166
return
167167
}
168168

169-
for int64(bucketCounts.Len()) > maxBuckets {
170-
compactHistogramBuckets(explicitBounds, bucketCounts)
171-
}
169+
divisor := ceilDiv(bucketCounts.Len(), int(maxBuckets))
170+
compactHistogramBuckets(explicitBounds, bucketCounts, divisor)
171+
}
172+
173+
func ceilDiv(dividend, divisor int) int {
174+
return (dividend-1)/divisor + 1
172175
}
173176

174-
func compactHistogramBuckets(bounds pcommon.Float64Slice, counts pcommon.UInt64Slice) {
177+
func compactHistogramBuckets(bounds pcommon.Float64Slice, counts pcommon.UInt64Slice, divisor int) {
175178
compactCounts := pcommon.NewUInt64Slice()
176-
compactCounts.EnsureCapacity((counts.Len() + 1) / 2)
177-
for i := 0; i < counts.Len(); i += 2 {
178-
if i+1 == counts.Len() {
179-
compactCounts.Append(counts.At(i))
180-
continue
179+
compactCounts.EnsureCapacity(ceilDiv(counts.Len(), divisor))
180+
for i := 0; i < counts.Len(); i += divisor {
181+
end := min(i+divisor, counts.Len())
182+
var count uint64
183+
for j := i; j < end; j++ {
184+
count += counts.At(j)
181185
}
182-
compactCounts.Append(counts.At(i) + counts.At(i+1))
186+
compactCounts.Append(count)
183187
}
184188

185189
compactBounds := pcommon.NewFloat64Slice()
186190
compactBounds.EnsureCapacity(compactCounts.Len() - 1)
187-
for i := 1; i < bounds.Len(); i += 2 {
191+
for i := divisor - 1; i < bounds.Len(); i += divisor {
188192
compactBounds.Append(bounds.At(i))
189193
}
190194

processor/transformprocessor/internal/metrics/func_merge_histogram_buckets_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,17 @@ func TestMergeHistogramBuckets(t *testing.T) {
140140
expectedBounds: []float64{0.2, 1.0, 5.0, 30.0},
141141
expectedCounts: []uint64{84, 126, 5, 50, 1},
142142
},
143+
{
144+
name: "limit buckets uses smallest divisor that stays within limit",
145+
inputBounds: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9},
146+
inputCounts: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
147+
inputCount: 55,
148+
inputSum: 385,
149+
targetValue: int64(4),
150+
method: ottl.NewTestingOptional(mergeHistogramBucketsMethodLimitBuckets),
151+
expectedBounds: []float64{3, 6, 9},
152+
expectedCounts: []uint64{6, 15, 24, 10},
153+
},
143154
{
144155
name: "limit buckets single compaction pass may reduce below limit",
145156
inputBounds: []float64{0.1, 0.5, 1.0},

0 commit comments

Comments
 (0)