Skip to content

Commit c302df8

Browse files
committed
[processor/transform] Optimize limit_buckets compaction divisor
Use the ceiling bucket-count-to-limit divisor to compact limit_buckets histograms in one pass, keeping the result within the configured limit while avoiding an extra halving pass. Update the docs and add regression coverage for the 10 buckets to 4 buckets case. Assisted-by: OpenAI Codex
1 parent 4f11f9d commit c302df8

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
@@ -667,7 +667,7 @@ The `merge_histogram_buckets` function merges explicit histogram buckets. The `m
667667

668668
`target_value` is interpreted according to `method`:
669669
- `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.
670-
- `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.
670+
- `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.
671671

672672
The function:
673673
- 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)