Skip to content

Commit

Permalink
[SDK] Add base2 exponential histogram indexer (open-telemetry#2173)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ruslan Nigmatullin authored Jun 3, 2023
1 parent 41416ce commit 082bb93
Show file tree
Hide file tree
Showing 6 changed files with 312 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include "opentelemetry/version.h"

#include <cstdint>

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
{
namespace metrics
{

/*
* An indexer for base2 exponential histograms. It is used to calculate index for a given value and
* scale.
*/
class Base2ExponentialHistogramIndexer
{
public:
/*
* Construct a new indexer for a given scale.
*/
explicit Base2ExponentialHistogramIndexer(int32_t scale = 0);
Base2ExponentialHistogramIndexer(const Base2ExponentialHistogramIndexer &other) = default;
Base2ExponentialHistogramIndexer &operator=(const Base2ExponentialHistogramIndexer &other) =
default;

/**
* Compute the index for the given value.
*
* @param value Measured value (must be non-zero).
* @return the index of the bucket which the value maps to.
*/
int32_t ComputeIndex(double value) const;

private:
int32_t scale_;
double scale_factor_;
};

} // namespace metrics
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
1 change: 1 addition & 0 deletions sdk/src/metrics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ add_library(
state/observable_registry.cc
state/sync_metric_storage.cc
state/temporal_metric_storage.cc
aggregation/base2_exponential_histogram_indexer.cc
aggregation/histogram_aggregation.cc
aggregation/lastvalue_aggregation.cc
aggregation/sum_aggregation.cc
Expand Down
71 changes: 71 additions & 0 deletions sdk/src/metrics/aggregation/base2_exponential_histogram_indexer.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#include "opentelemetry/sdk/metrics/aggregation/base2_exponential_histogram_indexer.h"

#include <cmath>

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
{
namespace metrics
{

namespace
{

const double kLogBase2E = 1.0 / std::log(2.0);

double ComputeScaleFactor(int32_t scale)
{
return std::scalbn(kLogBase2E, scale);
}

// Compute the bucket index using a logarithm based approach.
int32_t GetIndexByLogarithm(double value, double scale_factor)
{
return static_cast<int32_t>(std::ceil(std::log(value) * scale_factor)) - 1;
}

// Compute the bucket index at scale 0.
int32_t MapToIndexScaleZero(double value)
{
// Note: std::frexp() rounds submnormal values to the smallest normal value and returns an
// exponent corresponding to fractions in the range [0.5, 1), whereas an exponent for the range
// [1, 2), so subtract 1 from the exponent immediately.
int exp;
double frac = std::frexp(value, &exp);
exp--;

if (frac == 0.5)
{
// Special case for powers of two: they fall into the bucket numbered one less.
exp--;
}
return exp;
}

} // namespace

Base2ExponentialHistogramIndexer::Base2ExponentialHistogramIndexer(int32_t scale)
: scale_(scale), scale_factor_(scale > 0 ? ComputeScaleFactor(scale) : 0)
{}

int32_t Base2ExponentialHistogramIndexer::ComputeIndex(double value) const
{
const double abs_value = std::fabs(value);
// For positive scales, compute the index by logarithm, which is simpler but may be
// inaccurate near bucket boundaries
if (scale_ > 0)
{
return GetIndexByLogarithm(abs_value, scale_factor_);
}
// For scale zero, compute the exact index by extracting the exponent.
// For negative scales, compute the exact index by extracting the exponent and shifting it to
// the right by -scale
return MapToIndexScaleZero(abs_value) >> -scale_;
}

} // namespace metrics
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
15 changes: 15 additions & 0 deletions sdk/test/metrics/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,21 @@ cc_test(
],
)

cc_test(
name = "base2_exponential_histogram_indexer_test",
srcs = [
"base2_exponential_histogram_indexer_test.cc",
],
tags = [
"metrics",
"test",
],
deps = [
"metrics_common_test_utils",
"@com_google_googletest//:gtest_main",
],
)

cc_test(
name = "histogram_aggregation_test",
srcs = [
Expand Down
1 change: 1 addition & 0 deletions sdk/test/metrics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ foreach(
histogram_aggregation_test
attributes_processor_test
attributes_hashmap_test
base2_exponential_histogram_indexer_test
circular_buffer_counter_test
histogram_test
sync_metric_storage_counter_test
Expand Down
178 changes: 178 additions & 0 deletions sdk/test/metrics/base2_exponential_histogram_indexer_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#include "opentelemetry/sdk/metrics/aggregation/base2_exponential_histogram_indexer.h"

#include <gtest/gtest.h>

using namespace opentelemetry::sdk::metrics;

TEST(Base2ExponentialHistogramIndexerTest, ScaleOne)
{
const Base2ExponentialHistogramIndexer indexer{1};
auto compute_index = [indexer](double value) { return indexer.ComputeIndex(value); };

EXPECT_EQ(compute_index(std::numeric_limits<double>::max()), 2047);
EXPECT_EQ(compute_index(strtod("0x1p1023", nullptr)), 2045);
EXPECT_EQ(compute_index(strtod("0x1.1p1023", nullptr)), 2046);
EXPECT_EQ(compute_index(strtod("0x1p1022", nullptr)), 2043);
EXPECT_EQ(compute_index(strtod("0x1.1p1022", nullptr)), 2044);
EXPECT_EQ(compute_index(strtod("0x1p-1022", nullptr)), -2045);
EXPECT_EQ(compute_index(strtod("0x1.1p-1022", nullptr)), -2044);
EXPECT_EQ(compute_index(strtod("0x1p-1021", nullptr)), -2043);
EXPECT_EQ(compute_index(strtod("0x1.1p-1021", nullptr)), -2042);
EXPECT_EQ(compute_index(std::numeric_limits<double>::min()), -2045);
EXPECT_EQ(compute_index(std::numeric_limits<double>::denorm_min()), -2149);
EXPECT_EQ(compute_index(15.0), 7);
EXPECT_EQ(compute_index(9.0), 6);
EXPECT_EQ(compute_index(7.0), 5);
EXPECT_EQ(compute_index(5.0), 4);
EXPECT_EQ(compute_index(3.0), 3);
EXPECT_EQ(compute_index(2.5), 2);
EXPECT_EQ(compute_index(1.5), 1);
EXPECT_EQ(compute_index(1.2), 0);
EXPECT_EQ(compute_index(1.0), -1);
EXPECT_EQ(compute_index(0.75), -1);
EXPECT_EQ(compute_index(0.55), -2);
EXPECT_EQ(compute_index(0.45), -3);
}

TEST(Base2ExponentialHistogramIndexerTest, ScaleZero)
{
const Base2ExponentialHistogramIndexer indexer{0};
auto compute_index = [indexer](double value) { return indexer.ComputeIndex(value); };

// Near +Inf.
// Use constant for exp, as max_exponent constant includes bias.
EXPECT_EQ(compute_index(std::numeric_limits<double>::max()), 1023);
EXPECT_EQ(compute_index(strtod("0x1p1023", nullptr)), 1022);
EXPECT_EQ(compute_index(strtod("0x1.1p1023", nullptr)), 1023);
EXPECT_EQ(compute_index(strtod("0x1p1022", nullptr)), 1021);
EXPECT_EQ(compute_index(strtod("0x1.1p1022", nullptr)), 1022);
// Near 0.
EXPECT_EQ(compute_index(strtod("0x1p-1022", nullptr)), -1023);
EXPECT_EQ(compute_index(strtod("0x1.1p-1022", nullptr)), -1022);
EXPECT_EQ(compute_index(strtod("0x1p-1021", nullptr)), -1022);
EXPECT_EQ(compute_index(strtod("0x1.1p-1021", nullptr)), -1021);
EXPECT_EQ(compute_index(std::numeric_limits<double>::min()), -1023);
EXPECT_EQ(compute_index(std::numeric_limits<double>::denorm_min()), -1075);
// Near 1.
EXPECT_EQ(compute_index(4.0), 1);
EXPECT_EQ(compute_index(3.0), 1);
EXPECT_EQ(compute_index(2.0), 0);
EXPECT_EQ(compute_index(1.5), 0);
EXPECT_EQ(compute_index(1.0), -1);
EXPECT_EQ(compute_index(0.75), -1);
EXPECT_EQ(compute_index(0.51), -1);
EXPECT_EQ(compute_index(0.5), -2);
EXPECT_EQ(compute_index(0.26), -2);
EXPECT_EQ(compute_index(0.25), -3);
EXPECT_EQ(compute_index(0.126), -3);
EXPECT_EQ(compute_index(0.125), -4);
}

TEST(Base2ExponentialHistogramIndexerTest, ScaleNegativeOne)
{
const Base2ExponentialHistogramIndexer indexer{-1};
auto compute_index = [indexer](double value) { return indexer.ComputeIndex(value); };

EXPECT_EQ(compute_index(17.0), 2);
EXPECT_EQ(compute_index(16.0), 1);
EXPECT_EQ(compute_index(15.0), 1);
EXPECT_EQ(compute_index(9.0), 1);
EXPECT_EQ(compute_index(8.0), 1);
EXPECT_EQ(compute_index(5.0), 1);
EXPECT_EQ(compute_index(4.0), 0);
EXPECT_EQ(compute_index(3.0), 0);
EXPECT_EQ(compute_index(2.0), 0);
EXPECT_EQ(compute_index(1.5), 0);
EXPECT_EQ(compute_index(1.0), -1);
EXPECT_EQ(compute_index(0.75), -1);
EXPECT_EQ(compute_index(0.5), -1);
EXPECT_EQ(compute_index(0.25), -2);
EXPECT_EQ(compute_index(0.20), -2);
EXPECT_EQ(compute_index(0.13), -2);
EXPECT_EQ(compute_index(0.125), -2);
EXPECT_EQ(compute_index(0.10), -2);
EXPECT_EQ(compute_index(0.0625), -3);
EXPECT_EQ(compute_index(0.06), -3);
}

TEST(Base2ExponentialHistogramIndexerTest, ScaleNegativeFour)
{
const Base2ExponentialHistogramIndexer indexer{-4};
auto compute_index = [indexer](double value) { return indexer.ComputeIndex(value); };

EXPECT_EQ(compute_index(strtod("0x1p0", nullptr)), -1);
EXPECT_EQ(compute_index(strtod("0x10p0", nullptr)), 0);
EXPECT_EQ(compute_index(strtod("0x100p0", nullptr)), 0);
EXPECT_EQ(compute_index(strtod("0x1000p0", nullptr)), 0);
EXPECT_EQ(compute_index(strtod("0x10000p0", nullptr)), 0); // Base == 2**16
EXPECT_EQ(compute_index(strtod("0x100000p0", nullptr)), 1);
EXPECT_EQ(compute_index(strtod("0x1000000p0", nullptr)), 1);
EXPECT_EQ(compute_index(strtod("0x10000000p0", nullptr)), 1);
EXPECT_EQ(compute_index(strtod("0x100000000p0", nullptr)), 1); // == 2**32
EXPECT_EQ(compute_index(strtod("0x1000000000p0", nullptr)), 2);
EXPECT_EQ(compute_index(strtod("0x10000000000p0", nullptr)), 2);
EXPECT_EQ(compute_index(strtod("0x100000000000p0", nullptr)), 2);
EXPECT_EQ(compute_index(strtod("0x1000000000000p0", nullptr)), 2); // 2**48
EXPECT_EQ(compute_index(strtod("0x10000000000000p0", nullptr)), 3);
EXPECT_EQ(compute_index(strtod("0x100000000000000p0", nullptr)), 3);
EXPECT_EQ(compute_index(strtod("0x1000000000000000p0", nullptr)), 3);
EXPECT_EQ(compute_index(strtod("0x10000000000000000p0", nullptr)), 3); // 2**64
EXPECT_EQ(compute_index(strtod("0x100000000000000000p0", nullptr)), 4);
EXPECT_EQ(compute_index(strtod("0x1000000000000000000p0", nullptr)), 4);
EXPECT_EQ(compute_index(strtod("0x10000000000000000000p0", nullptr)), 4);
EXPECT_EQ(compute_index(strtod("0x100000000000000000000p0", nullptr)), 4);
EXPECT_EQ(compute_index(strtod("0x1000000000000000000000p0", nullptr)), 5);
EXPECT_EQ(compute_index(1 / strtod("0x1p0", nullptr)), -1);
EXPECT_EQ(compute_index(1 / strtod("0x10p0", nullptr)), -1);
EXPECT_EQ(compute_index(1 / strtod("0x100p0", nullptr)), -1);
EXPECT_EQ(compute_index(1 / strtod("0x1000p0", nullptr)), -1);
EXPECT_EQ(compute_index(1 / strtod("0x10000p0", nullptr)), -2); // 2**-16
EXPECT_EQ(compute_index(1 / strtod("0x100000p0", nullptr)), -2);
EXPECT_EQ(compute_index(1 / strtod("0x1000000p0", nullptr)), -2);
EXPECT_EQ(compute_index(1 / strtod("0x10000000p0", nullptr)), -2);
EXPECT_EQ(compute_index(1 / strtod("0x100000000p0", nullptr)), -3); // 2**-32
EXPECT_EQ(compute_index(1 / strtod("0x1000000000p0", nullptr)), -3);
EXPECT_EQ(compute_index(1 / strtod("0x10000000000p0", nullptr)), -3);
EXPECT_EQ(compute_index(1 / strtod("0x100000000000p0", nullptr)), -3);
EXPECT_EQ(compute_index(1 / strtod("0x1000000000000p0", nullptr)), -4); // 2**-48
EXPECT_EQ(compute_index(1 / strtod("0x10000000000000p0", nullptr)), -4);
EXPECT_EQ(compute_index(1 / strtod("0x100000000000000p0", nullptr)), -4);
EXPECT_EQ(compute_index(1 / strtod("0x1000000000000000p0", nullptr)), -4);
EXPECT_EQ(compute_index(1 / strtod("0x10000000000000000p0", nullptr)), -5); // 2**-64
EXPECT_EQ(compute_index(1 / strtod("0x100000000000000000p0", nullptr)), -5);
// Max values.
EXPECT_EQ(compute_index(0x1.FFFFFFFFFFFFFp1023), 63);
EXPECT_EQ(compute_index(strtod("0x1p1023", nullptr)), 63);
EXPECT_EQ(compute_index(strtod("0x1p1019", nullptr)), 63);
EXPECT_EQ(compute_index(strtod("0x1p1009", nullptr)), 63);
EXPECT_EQ(compute_index(strtod("0x1p1008", nullptr)), 62);
EXPECT_EQ(compute_index(strtod("0x1p1007", nullptr)), 62);
EXPECT_EQ(compute_index(strtod("0x1p1000", nullptr)), 62);
EXPECT_EQ(compute_index(strtod("0x1p0993", nullptr)), 62);
EXPECT_EQ(compute_index(strtod("0x1p0992", nullptr)), 61);
EXPECT_EQ(compute_index(strtod("0x1p0991", nullptr)), 61);
// Min and subnormal values.
EXPECT_EQ(compute_index(strtod("0x1p-1074", nullptr)), -68);
EXPECT_EQ(compute_index(strtod("0x1p-1073", nullptr)), -68);
EXPECT_EQ(compute_index(strtod("0x1p-1072", nullptr)), -68);
EXPECT_EQ(compute_index(strtod("0x1p-1057", nullptr)), -67);
EXPECT_EQ(compute_index(strtod("0x1p-1056", nullptr)), -67);
EXPECT_EQ(compute_index(strtod("0x1p-1041", nullptr)), -66);
EXPECT_EQ(compute_index(strtod("0x1p-1040", nullptr)), -66);
EXPECT_EQ(compute_index(strtod("0x1p-1025", nullptr)), -65);
EXPECT_EQ(compute_index(strtod("0x1p-1024", nullptr)), -65);
EXPECT_EQ(compute_index(strtod("0x1p-1023", nullptr)), -64);
EXPECT_EQ(compute_index(strtod("0x1p-1022", nullptr)), -64);
EXPECT_EQ(compute_index(strtod("0x1p-1009", nullptr)), -64);
EXPECT_EQ(compute_index(strtod("0x1p-1008", nullptr)), -64);
EXPECT_EQ(compute_index(strtod("0x1p-1007", nullptr)), -63);
EXPECT_EQ(compute_index(strtod("0x1p-0993", nullptr)), -63);
EXPECT_EQ(compute_index(strtod("0x1p-0992", nullptr)), -63);
EXPECT_EQ(compute_index(strtod("0x1p-0991", nullptr)), -62);
EXPECT_EQ(compute_index(strtod("0x1p-0977", nullptr)), -62);
EXPECT_EQ(compute_index(strtod("0x1p-0976", nullptr)), -62);
EXPECT_EQ(compute_index(strtod("0x1p-0975", nullptr)), -61);
}

0 comments on commit 082bb93

Please sign in to comment.