Skip to content
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ Increment the:
`GetSpan(context)->GetContext()` incurs, and use it at existing call sites.
[#4254](https://github.com/open-telemetry/opentelemetry-cpp/pull/4254)

* [METRICS SDK] Validate Base2 Exponential Histogram Aggregation config
[#4253](https://github.com/open-telemetry/opentelemetry-cpp/pull/4253)

Breaking changes:

* [METRICS SDK] Rename Base2 Exponential Histogram Aggregation config field
[#4253](https://github.com/open-telemetry/opentelemetry-cpp/pull/4253)
* The public configuration member `max_buckets_` was renamed to `max_size_` to
match the configuration schema. Please adjust SDK configuration accordingly.

## [1.28.0] 2026-07-16

* [RELEASE] Bump main branch to 1.28.0-dev
Expand Down
2 changes: 1 addition & 1 deletion examples/metrics_simple/metrics_ostream.cc
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ void InitMetrics(const std::string &name)
new metrics_sdk::Base2ExponentialHistogramAggregationConfig);
histogram_base2_aggregation_config->max_scale_ = 3;
histogram_base2_aggregation_config->record_min_max_ = true;
histogram_base2_aggregation_config->max_buckets_ = 100;
histogram_base2_aggregation_config->max_size_ = 100;

std::shared_ptr<metrics_sdk::AggregationConfig> base2_aggregation_config(
std::move(histogram_base2_aggregation_config));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#pragma once

#include <cstddef>
#include <cstdint>

#include "opentelemetry/sdk/configuration/aggregation_configuration.h"
#include "opentelemetry/sdk/configuration/aggregation_configuration_visitor.h"
Expand All @@ -20,19 +21,16 @@ namespace configuration
class Base2ExponentialBucketHistogramAggregationConfiguration : public AggregationConfiguration
{
public:
// TODO: max_scale range is [-10, 20]. Negative values require GetSignedInteger in the parser and
// changing the type of max_scale to an int32_t to match the
// Base2ExponentialHistogramAggregationConfig.
static constexpr std::size_t kDefaultMaxScale = 20; // schema: minimum -10, maximum 20
static constexpr std::size_t kDefaultMaxSize = 160;
static constexpr bool kDefaultRecordMinMax = true;
static constexpr std::int32_t kDefaultMaxScale = 20; // schema: minimum -10, maximum 20
static constexpr std::size_t kDefaultMaxSize = 160;
static constexpr bool kDefaultRecordMinMax = true;

void Accept(AggregationConfigurationVisitor *visitor) const override
{
visitor->VisitBase2ExponentialBucketHistogram(this);
}

std::size_t max_scale{kDefaultMaxScale};
std::int32_t max_scale{kDefaultMaxScale};
std::size_t max_size{kDefaultMaxSize};
bool record_min_max{kDefaultRecordMinMax};
};
Expand Down
5 changes: 5 additions & 0 deletions sdk/include/opentelemetry/sdk/configuration/document_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#pragma once

#include <cstdint>
#include <memory>
#include <string>

Expand Down Expand Up @@ -49,6 +50,9 @@ class DocumentNode
virtual std::size_t GetRequiredInteger(const std::string &name) const = 0;
virtual std::size_t GetInteger(const std::string &name, std::size_t default_value) const = 0;

virtual std::int64_t GetSignedInteger(const std::string &name,
std::int64_t default_value) const = 0;

virtual double GetRequiredDouble(const std::string &name) const = 0;
virtual double GetDouble(const std::string &name, double default_value) const = 0;

Expand All @@ -71,6 +75,7 @@ class DocumentNode

bool BooleanFromString(const std::string &value) const;
std::size_t IntegerFromString(const std::string &value) const;
std::int64_t SignedIntegerFromString(const std::string &value) const;
double DoubleFromString(const std::string &value) const;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#pragma once

#include <stddef.h>
#include <cstdint>
#include <memory>
#include <ryml.hpp>
#include <string>
Expand Down Expand Up @@ -49,6 +50,8 @@ class RymlDocumentNode : public DocumentNode
std::size_t GetRequiredInteger(const std::string &name) const override;
std::size_t GetInteger(const std::string &name, std::size_t default_value) const override;

std::int64_t GetSignedInteger(const std::string &name, std::int64_t default_value) const override;

double GetRequiredDouble(const std::string &name) const override;
double GetDouble(const std::string &name, double default_value) const override;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ class HistogramAggregationConfig : public AggregationConfig
bool record_min_max_ = true;
};

// Valid ranges per the declarative configuration schema; the schema defines no maximum for
// max_size.
// https://github.com/open-telemetry/opentelemetry-configuration/blob/main/schema/meter_provider.yaml
constexpr std::int32_t kMaxScaleMin = -10;
constexpr std::int32_t kMaxScaleMax = 20;
constexpr std::size_t kMaxSizeMin = 2;

class Base2ExponentialHistogramAggregationConfig : public AggregationConfig
{
public:
Expand All @@ -69,7 +76,7 @@ class Base2ExponentialHistogramAggregationConfig : public AggregationConfig
return AggregationType::kBase2ExponentialHistogram;
}

size_t max_buckets_ = 160;
size_t max_size_ = 160;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a visible change in the SDK configuration interface.

Please add a "breaking change" section in the CHANGELOG, to say max_buckets_ was renamed to max_size_.

int32_t max_scale_ = 20;
bool record_min_max_ = true;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ static inline size_t GetSimpleFixedReservoirDefaultSize(const AggregationType ag
{
const auto *histogram_agg_config =
static_cast<const Base2ExponentialHistogramAggregationConfig *>(agg_config);
return (std::min)(kMaxBase2ExponentialHistogramReservoirSize,
histogram_agg_config->max_buckets_);
return (std::min)(kMaxBase2ExponentialHistogramReservoirSize, histogram_agg_config->max_size_);
}

return SimpleFixedSizeExemplarReservoir::kDefaultSimpleReservoirSize;
Expand Down
21 changes: 19 additions & 2 deletions sdk/src/configuration/configuration_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
#include "opentelemetry/sdk/configuration/view_configuration.h"
#include "opentelemetry/sdk/configuration/view_selector_configuration.h"
#include "opentelemetry/sdk/configuration/view_stream_configuration.h"
#include "opentelemetry/sdk/metrics/aggregation/aggregation_config.h"
#include "opentelemetry/version.h"

OPENTELEMETRY_BEGIN_NAMESPACE
Expand Down Expand Up @@ -1370,8 +1371,24 @@ ConfigurationParser::ParseBase2ExponentialBucketHistogramAggregationConfiguratio
using Config = Base2ExponentialBucketHistogramAggregationConfiguration;
auto model = std::make_unique<Base2ExponentialBucketHistogramAggregationConfiguration>();

model->max_scale = node->GetInteger("max_scale", Config::kDefaultMaxScale);
model->max_size = node->GetInteger("max_size", Config::kDefaultMaxSize);
std::int64_t max_scale = node->GetSignedInteger("max_scale", Config::kDefaultMaxScale);
if (max_scale < opentelemetry::sdk::metrics::kMaxScaleMin ||
max_scale > opentelemetry::sdk::metrics::kMaxScaleMax)
{
std::string message("Illegal max_scale: ");
message.append(std::to_string(max_scale));
throw InvalidSchemaException(node->Location(), message);
}
model->max_scale = static_cast<std::int32_t>(max_scale);

model->max_size = node->GetInteger("max_size", Config::kDefaultMaxSize);
if (model->max_size < opentelemetry::sdk::metrics::kMaxSizeMin)
{
std::string message("Illegal max_size: ");
message.append(std::to_string(model->max_size));
throw InvalidSchemaException(node->Location(), message);
}

model->record_min_max = node->GetBoolean("record_min_max", Config::kDefaultRecordMinMax);

return model;
Expand Down
30 changes: 30 additions & 0 deletions sdk/src/configuration/document_node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// SPDX-License-Identifier: Apache-2.0

#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <stdexcept>
#include <string>

#include "opentelemetry/sdk/common/env_variables.h"
Expand Down Expand Up @@ -250,6 +252,34 @@ size_t DocumentNode::IntegerFromString(const std::string &value) const
return val;
}

std::int64_t DocumentNode::SignedIntegerFromString(const std::string &value) const
{
try
{
std::size_t pos = 0;
std::int64_t val = std::stoll(value, &pos);
if (pos != value.length())
{
std::string message("Illegal integer value: ");
message.append(value);
throw InvalidSchemaException(Location(), message);
}
return val;
}
catch (const std::invalid_argument &)
{
std::string message("Illegal integer value: ");
message.append(value);
throw InvalidSchemaException(Location(), message);
}
catch (const std::out_of_range &)
{
std::string message("Illegal integer value: ");
message.append(value);
throw InvalidSchemaException(Location(), message);
}
}

double DocumentNode::DoubleFromString(const std::string &value) const
{
const char *ptr = value.c_str();
Expand Down
27 changes: 27 additions & 0 deletions sdk/src/configuration/ryml_document_node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

#include <stddef.h>
#include <cstdint>
#include <memory>
#include <ostream>
#include <ryml.hpp>
Expand Down Expand Up @@ -281,6 +282,32 @@ size_t RymlDocumentNode::GetInteger(const std::string &name, size_t default_valu
return IntegerFromString(value);
}

std::int64_t RymlDocumentNode::GetSignedInteger(const std::string &name,
std::int64_t default_value) const
{
OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::GetSignedInteger(" << name << ", " << default_value
<< ")");

auto ryml_child = GetRymlChildNode(name);

if (ryml_child.invalid())
{
return default_value;
}

ryml::csubstr view = ryml_child.val();
std::string value(view.str, view.len);

value = DoSubstitution(value);

if (value.empty())
{
return default_value;
}

return SignedIntegerFromString(value);
}

double RymlDocumentNode::GetRequiredDouble(const std::string &name) const
{
OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::GetRequiredDouble(" << name << ")");
Expand Down
4 changes: 2 additions & 2 deletions sdk/src/configuration/sdk_builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1619,8 +1619,8 @@ SdkBuilder::CreateBase2ExponentialBucketHistogramAggregation(
auto sdk =
std::make_unique<opentelemetry::sdk::metrics::Base2ExponentialHistogramAggregationConfig>();

sdk->max_buckets_ = model->max_size;
sdk->max_scale_ = static_cast<int32_t>(model->max_scale);
sdk->max_size_ = model->max_size;
sdk->max_scale_ = model->max_scale;
sdk->record_min_max_ = model->record_min_max;

return sdk;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,26 @@ Base2ExponentialHistogramAggregation::Base2ExponentialHistogramAggregation(
ac = &default_config;
}

point_data_.max_buckets_ = (std::max)(ac->max_buckets_, static_cast<size_t>(2));
point_data_.scale_ = ac->max_scale_;
size_t max_size = ac->max_size_;
if (max_size < kMaxSizeMin)
{
OTEL_INTERNAL_LOG_WARN("[Base2ExponentialHistogramAggregation] max_size "
<< max_size << " is less than " << kMaxSizeMin << "; using default "
<< default_config.max_size_);
max_size = default_config.max_size_;
}

int32_t max_scale = ac->max_scale_;
if (max_scale < kMaxScaleMin || max_scale > kMaxScaleMax)
{
OTEL_INTERNAL_LOG_WARN("[Base2ExponentialHistogramAggregation] max_scale "
<< max_scale << " is out of range [" << kMaxScaleMin << ", "
<< kMaxScaleMax << "]; using default " << default_config.max_scale_);
max_scale = default_config.max_scale_;
}

point_data_.max_buckets_ = max_size;
point_data_.scale_ = max_scale;
point_data_.record_min_max_ = ac->record_min_max_;
point_data_.min_ = (std::numeric_limits<double>::max)();
point_data_.max_ = (std::numeric_limits<double>::min)();
Expand Down
Loading
Loading