Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SDK] Add base2 exponential histogram indexer #2173

Merged
merged 5 commits into from
Jun 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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);
}