Skip to content

Commit 2faa400

Browse files
authored
[SDK] Fix Base2ExponentialHistogramAggregation Merge with empty buckets (#3425)
1 parent 82ec2a5 commit 2faa400

File tree

2 files changed

+130
-23
lines changed

2 files changed

+130
-23
lines changed

sdk/src/metrics/aggregation/base2_exponential_histogram_aggregation.cc

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,16 @@ std::unique_ptr<Aggregation> Base2ExponentialHistogramAggregation::Merge(
260260
auto right = nostd::get<Base2ExponentialHistogramPointData>(
261261
(static_cast<const Base2ExponentialHistogramAggregation &>(delta).ToPoint()));
262262

263+
if (left.count_ == 0)
264+
{
265+
return std::make_unique<Base2ExponentialHistogramAggregation>(std::move(right));
266+
}
267+
268+
if (right.count_ == 0)
269+
{
270+
return std::make_unique<Base2ExponentialHistogramAggregation>(std::move(left));
271+
}
272+
263273
auto &low_res = left.scale_ < right.scale_ ? left : right;
264274
auto &high_res = left.scale_ < right.scale_ ? right : left;
265275

@@ -289,30 +299,33 @@ std::unique_ptr<Aggregation> Base2ExponentialHistogramAggregation::Merge(
289299
}
290300
}
291301

292-
auto pos_min_index =
293-
(std::min)(low_res.positive_buckets_->StartIndex(), high_res.positive_buckets_->StartIndex());
294-
auto pos_max_index =
295-
(std::max)(low_res.positive_buckets_->EndIndex(), high_res.positive_buckets_->EndIndex());
296-
auto neg_min_index =
297-
(std::min)(low_res.negative_buckets_->StartIndex(), high_res.negative_buckets_->StartIndex());
298-
auto neg_max_index =
299-
(std::max)(low_res.negative_buckets_->EndIndex(), high_res.negative_buckets_->EndIndex());
300-
301-
if (static_cast<size_t>(pos_max_index) >
302-
static_cast<size_t>(pos_min_index) + result_value.max_buckets_ ||
303-
static_cast<size_t>(neg_max_index) >
304-
static_cast<size_t>(neg_min_index) + result_value.max_buckets_)
302+
if (!low_res.positive_buckets_->Empty() && !high_res.positive_buckets_->Empty())
305303
{
306-
// We need to downscale the buckets to fit into the new max_buckets_.
307-
const uint32_t scale_reduction =
308-
GetScaleReduction(pos_min_index, pos_max_index, result_value.max_buckets_);
309-
DownscaleBuckets(low_res.positive_buckets_, scale_reduction);
310-
DownscaleBuckets(high_res.positive_buckets_, scale_reduction);
311-
DownscaleBuckets(low_res.negative_buckets_, scale_reduction);
312-
DownscaleBuckets(high_res.negative_buckets_, scale_reduction);
313-
low_res.scale_ -= scale_reduction;
314-
high_res.scale_ -= scale_reduction;
315-
result_value.scale_ -= scale_reduction;
304+
auto pos_min_index = (std::min)(low_res.positive_buckets_->StartIndex(),
305+
high_res.positive_buckets_->StartIndex());
306+
auto pos_max_index =
307+
(std::max)(low_res.positive_buckets_->EndIndex(), high_res.positive_buckets_->EndIndex());
308+
auto neg_min_index = (std::min)(low_res.negative_buckets_->StartIndex(),
309+
high_res.negative_buckets_->StartIndex());
310+
auto neg_max_index =
311+
(std::max)(low_res.negative_buckets_->EndIndex(), high_res.negative_buckets_->EndIndex());
312+
313+
if (static_cast<size_t>(pos_max_index) >
314+
static_cast<size_t>(pos_min_index) + result_value.max_buckets_ ||
315+
static_cast<size_t>(neg_max_index) >
316+
static_cast<size_t>(neg_min_index) + result_value.max_buckets_)
317+
{
318+
// We need to downscale the buckets to fit into the new max_buckets_.
319+
const uint32_t scale_reduction =
320+
GetScaleReduction(pos_min_index, pos_max_index, result_value.max_buckets_);
321+
DownscaleBuckets(low_res.positive_buckets_, scale_reduction);
322+
DownscaleBuckets(high_res.positive_buckets_, scale_reduction);
323+
DownscaleBuckets(low_res.negative_buckets_, scale_reduction);
324+
DownscaleBuckets(high_res.negative_buckets_, scale_reduction);
325+
low_res.scale_ -= scale_reduction;
326+
high_res.scale_ -= scale_reduction;
327+
result_value.scale_ -= scale_reduction;
328+
}
316329
}
317330

318331
result_value.positive_buckets_ = std::make_unique<AdaptingCircularBufferCounter>(MergeBuckets(

sdk/test/metrics/aggregation_test.cc

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
#include "opentelemetry/nostd/variant.h"
1111
#include "opentelemetry/sdk/metrics/aggregation/aggregation_config.h"
1212
#include "opentelemetry/sdk/metrics/aggregation/base2_exponential_histogram_aggregation.h"
13+
#include "opentelemetry/sdk/metrics/aggregation/default_aggregation.h"
1314
#include "opentelemetry/sdk/metrics/aggregation/histogram_aggregation.h"
1415
#include "opentelemetry/sdk/metrics/aggregation/lastvalue_aggregation.h"
1516
#include "opentelemetry/sdk/metrics/aggregation/sum_aggregation.h"
1617
#include "opentelemetry/sdk/metrics/data/circular_buffer.h"
1718
#include "opentelemetry/sdk/metrics/data/point_data.h"
19+
#include "opentelemetry/sdk/metrics/instruments.h"
1820

1921
using namespace opentelemetry::sdk::metrics;
2022
namespace nostd = opentelemetry::nostd;
@@ -357,3 +359,95 @@ TEST(Aggregation, Base2ExponentialHistogramAggregation)
357359
ASSERT_TRUE(diffd_point.negative_buckets_ != nullptr);
358360
EXPECT_EQ(diffd_point.positive_buckets_->Get(2), 1);
359361
}
362+
363+
TEST(Aggregation, Base2ExponentialHistogramAggregationMerge)
364+
{
365+
Base2ExponentialHistogramAggregationConfig config;
366+
config.max_scale_ = 10;
367+
config.max_buckets_ = 100;
368+
config.record_min_max_ = true;
369+
370+
Base2ExponentialHistogramAggregation aggr(&config);
371+
372+
int expected_count = 0;
373+
double expected_sum = 0.0;
374+
375+
// Aggregate some small values
376+
for (int i = 1; i < 10; ++i)
377+
{
378+
expected_count++;
379+
const double value = i * 1e-12;
380+
expected_sum += value;
381+
aggr.Aggregate(value);
382+
}
383+
384+
const auto aggr_point = nostd::get<Base2ExponentialHistogramPointData>(aggr.ToPoint());
385+
386+
ASSERT_EQ(aggr_point.count_, expected_count);
387+
ASSERT_DOUBLE_EQ(aggr_point.sum_, expected_sum);
388+
ASSERT_EQ(aggr_point.zero_count_, 0);
389+
ASSERT_GT(aggr_point.scale_, -10);
390+
ASSERT_EQ(aggr_point.max_buckets_, config.max_buckets_);
391+
392+
auto test_merge = [](const std::unique_ptr<Aggregation> &merged_aggr, int expected_count,
393+
double expected_sum, int expected_zero_count, int expected_scale,
394+
int expected_max_buckets) {
395+
auto merged_point = nostd::get<Base2ExponentialHistogramPointData>(merged_aggr->ToPoint());
396+
EXPECT_EQ(merged_point.count_, expected_count);
397+
EXPECT_DOUBLE_EQ(merged_point.sum_, expected_sum);
398+
EXPECT_EQ(merged_point.zero_count_, expected_zero_count);
399+
EXPECT_EQ(merged_point.scale_, expected_scale);
400+
EXPECT_EQ(merged_point.max_buckets_, expected_max_buckets);
401+
};
402+
403+
// default aggregation merge
404+
{
405+
InstrumentDescriptor descriptor;
406+
descriptor.type_ = InstrumentType::kHistogram;
407+
descriptor.unit_ = "unit";
408+
descriptor.name_ = "histogram";
409+
descriptor.description_ = "a histogram";
410+
descriptor.value_type_ = InstrumentValueType::kDouble;
411+
412+
auto default_aggr = DefaultAggregation::CreateAggregation(
413+
AggregationType::kBase2ExponentialHistogram, descriptor);
414+
auto default_point = nostd::get<Base2ExponentialHistogramPointData>(default_aggr->ToPoint());
415+
416+
const int expected_scale =
417+
aggr_point.scale_ < default_point.scale_ ? aggr_point.scale_ : default_point.scale_;
418+
const int expected_max_buckets = aggr_point.max_buckets_ < default_point.max_buckets_
419+
? aggr_point.max_buckets_
420+
: default_point.max_buckets_;
421+
const int expected_zero_count = 0;
422+
423+
auto merged_from_default = aggr.Merge(*default_aggr);
424+
test_merge(merged_from_default, expected_count, expected_sum, expected_zero_count,
425+
expected_scale, expected_max_buckets);
426+
427+
auto merged_to_default = default_aggr->Merge(aggr);
428+
test_merge(merged_to_default, expected_count, expected_sum, expected_zero_count, expected_scale,
429+
expected_max_buckets);
430+
}
431+
432+
// zero count aggregation merge (Zero is a special case and does not increment the buckets)
433+
{
434+
Base2ExponentialHistogramAggregation zero_aggr(&config);
435+
zero_aggr.Aggregate(0.0);
436+
437+
const auto zero_point = nostd::get<Base2ExponentialHistogramPointData>(zero_aggr.ToPoint());
438+
const int expected_scale =
439+
aggr_point.scale_ < zero_point.scale_ ? aggr_point.scale_ : zero_point.scale_;
440+
const int expected_max_buckets = aggr_point.max_buckets_ < zero_point.max_buckets_
441+
? aggr_point.max_buckets_
442+
: zero_point.max_buckets_;
443+
const int expected_zero_count = 1;
444+
445+
auto merged_from_zero = aggr.Merge(zero_aggr);
446+
test_merge(merged_from_zero, expected_count + 1, expected_sum, expected_zero_count,
447+
expected_scale, expected_max_buckets);
448+
449+
auto merged_to_zero = zero_aggr.Merge(aggr);
450+
test_merge(merged_to_zero, expected_count + 1, expected_sum, expected_zero_count,
451+
expected_scale, expected_max_buckets);
452+
}
453+
}

0 commit comments

Comments
 (0)