diff --git a/api/include/opentelemetry/context/runtime_context.h b/api/include/opentelemetry/context/runtime_context.h index 7ad3b73bdc..fabd353a84 100644 --- a/api/include/opentelemetry/context/runtime_context.h +++ b/api/include/opentelemetry/context/runtime_context.h @@ -47,6 +47,46 @@ class RuntimeContext static RuntimeContext *context_handler_; + // Sets the Key and Value into the passed in context or if a context is not + // passed in, the RuntimeContext. + // Should be used to SetValues to the current RuntimeContext, is essentially + // equivalent to RuntimeContext::GetCurrent().SetValue(key,value). Keep in + // mind that the current RuntimeContext will not be changed, and the new + // context will be returned. + static Context SetValue(nostd::string_view key, + ContextValue value, + Context *context = nullptr) noexcept + { + Context temp_context; + if (context == nullptr) + { + temp_context = GetCurrent(); + } + else + { + temp_context = *context; + } + return temp_context.SetValue(key, value); + } + + // Returns the value associated with the passed in key and either the + // passed in context* or the runtime context if a context is not passed in. + // Should be used to get values from the current RuntimeContext, is + // essentially equivalent to RuntimeContext::GetCurrent().GetValue(key). + static ContextValue GetValue(nostd::string_view key, Context *context = nullptr) noexcept + { + Context temp_context; + if (context == nullptr) + { + temp_context = GetCurrent(); + } + else + { + temp_context = *context; + } + return temp_context.GetValue(key); + } + protected: // Provides a token with the passed in context Token CreateToken(Context context) noexcept { return Token(context); } diff --git a/api/test/context/runtime_context_test.cc b/api/test/context/runtime_context_test.cc index b70d69dce8..5c0dc565d4 100644 --- a/api/test/context/runtime_context_test.cc +++ b/api/test/context/runtime_context_test.cc @@ -59,3 +59,44 @@ TEST(RuntimeContextTest, ThreeAttachDetach) EXPECT_TRUE(context::RuntimeContext::Detach(foo_context_token)); EXPECT_TRUE(context::RuntimeContext::Detach(test_context_token)); } + +// Tests that SetValue returns a context with the passed in data and the +// RuntimeContext data when a context is not passed into the +// RuntimeContext::SetValue method. +TEST(RuntimeContextTest, SetValueRuntimeContext) +{ + context::Context foo_context = context::Context("foo_key", (int64_t)596); + context::RuntimeContext::Token old_context_token = context::RuntimeContext::Attach(foo_context); + context::Context test_context = context::RuntimeContext::SetValue("test_key", (int64_t)123); + EXPECT_EQ(nostd::get(test_context.GetValue("test_key")), 123); + EXPECT_EQ(nostd::get(test_context.GetValue("foo_key")), 596); +} + +// Tests that SetValue returns a context with the passed in data and the +// passed in context data when a context* is passed into the +// RuntimeContext::SetValue method. +TEST(RuntimeContextTest, SetValueOtherContext) +{ + context::Context foo_context = context::Context("foo_key", (int64_t)596); + context::Context test_context = + context::RuntimeContext::SetValue("test_key", (int64_t)123, &foo_context); + EXPECT_EQ(nostd::get(test_context.GetValue("test_key")), 123); + EXPECT_EQ(nostd::get(test_context.GetValue("foo_key")), 596); +} + +// Tests that SetValue returns the ContextValue associated with the +// passed in string and the current Runtime Context +TEST(RuntimeContextTest, GetValueRuntimeContext) +{ + context::Context foo_context = context::Context("foo_key", (int64_t)596); + context::RuntimeContext::Token old_context_token = context::RuntimeContext::Attach(foo_context); + EXPECT_EQ(nostd::get(context::RuntimeContext::GetValue("foo_key")), 596); +} + +// Tests that SetValue returns the ContextValue associated with the +// passed in string and the passed in context +TEST(RuntimeContextTest, GetValueOtherContext) +{ + context::Context foo_context = context::Context("foo_key", (int64_t)596); + EXPECT_EQ(nostd::get(context::RuntimeContext::GetValue("foo_key", &foo_context)), 596); +} diff --git a/sdk/include/opentelemetry/sdk/metrics/aggregator/aggregator.h b/sdk/include/opentelemetry/sdk/metrics/aggregator/aggregator.h new file mode 100644 index 0000000000..b997514208 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/metrics/aggregator/aggregator.h @@ -0,0 +1,141 @@ +#pragma once + +#include +#include +#include "opentelemetry/core/timestamp.h" +#include "opentelemetry/metrics/instrument.h" +#include "opentelemetry/version.h" + +namespace metrics_api = opentelemetry::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +enum class AggregatorKind +{ + Counter = 0, + MinMaxSumCount = 1, + Gauge = 2, + Sketch = 3, + Histogram = 4, + Exact = 5, +}; + +/* + * Performs calculations necessary to combine updates from instruments into an + * insightful value. + * Also stores current instrument values and checkpoints collected at intervals + * governing the entire pipeline. + */ +template +class Aggregator +{ +public: + Aggregator() = default; + + virtual ~Aggregator() = default; + + /** + * Receives a captured value from the instrument and applies it to the current aggregator value. + * + * @param val, the raw value used in aggregation + * @return none + */ + virtual void update(T val) = 0; + + /** + * Checkpoints the current value. This function will overwrite the current checkpoint with the + * current value. + * + * @param none + * @return none + */ + virtual void checkpoint() = 0; + + /** + * Merges the values of two aggregators in a semantically accurate manner. + * Merging will occur differently for different aggregators depending on the + * way values are tracked. + * + * @param other, the aggregator with merge with + * @return none + */ + void merge(Aggregator *other); + + /** + * Returns the checkpointed value + * + * @param none + * @return the value of the checkpoint + */ + virtual std::vector get_checkpoint() = 0; + + /** + * Returns the current value + * + * @param none + * @return the present aggregator value + */ + virtual std::vector get_values() = 0; + + /** + * Returns the instrument kind which this aggregator is associated with + * + * @param none + * @return the InstrumentKind of the aggregator's owner + */ + virtual opentelemetry::metrics::InstrumentKind get_instrument_kind() final { return kind_; } + + /** + * Returns the type of this aggregator + * + * @param none + * @return the AggregatorKind of this instrument + */ + virtual AggregatorKind get_aggregator_kind() final { return agg_kind_; } + + // virtual function to be overriden for the Histogram Aggregator + virtual std::vector get_boundaries() { return std::vector(); } + + // virtual function to be overriden for the Histogram Aggregator + virtual std::vector get_counts() { return std::vector(); } + + // virtual function to be overriden for Exact and Sketch Aggregators + virtual bool get_quant_estimation() { return false; } + + // virtual function to be overriden for Exact and Sketch Aggregators + virtual T get_quantiles(double q) { return values_[0]; } + + // virtual function to be overriden for Sketch Aggregator + virtual double get_error_bound() { return 0; } + + // virtual function to be overriden for Sketch Aggregator + virtual size_t get_max_buckets() { return 0; } + + // virtual function to be overriden for Gauge Aggregator + virtual core::SystemTimestamp get_checkpoint_timestamp() { return core::SystemTimestamp(); } + + // Custom copy constructor to handle the mutex + Aggregator(const Aggregator &cp) + { + values_ = cp.values_; + checkpoint_ = cp.checkpoint_; + kind_ = cp.kind_; + agg_kind_ = cp.agg_kind_; + // use default initialized mutex as they cannot be copied + } + +protected: + std::vector values_; + std::vector checkpoint_; + opentelemetry::metrics::InstrumentKind kind_; + std::mutex mu_; + AggregatorKind agg_kind_; +}; + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/metrics/aggregator/counter_aggregator.h b/sdk/include/opentelemetry/sdk/metrics/aggregator/counter_aggregator.h new file mode 100644 index 0000000000..0bf44f6421 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/metrics/aggregator/counter_aggregator.h @@ -0,0 +1,101 @@ +#pragma once + +#include +#include +#include "opentelemetry/metrics/instrument.h" +#include "opentelemetry/sdk/metrics/aggregator/aggregator.h" +#include "opentelemetry/version.h" + +namespace metrics_api = opentelemetry::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +template +class CounterAggregator final : public Aggregator +{ + +public: + CounterAggregator(metrics_api::InstrumentKind kind) + { + this->kind_ = kind; + this->values_ = std::vector(1, 0); + this->checkpoint_ = std::vector(1, 0); + this->agg_kind_ = AggregatorKind::Counter; + } + + /** + * Recieves a captured value from the instrument and applies it to the current aggregator value. + * + * @param val, the raw value used in aggregation + * @return none + */ + void update(T val) override + { + this->mu_.lock(); + this->values_[0] += val; // atomic operation + this->mu_.unlock(); + } + + /** + * Checkpoints the current value. This function will overwrite the current checkpoint with the + * current value. + * + * @param none + * @return none + */ + void checkpoint() override + { + this->checkpoint_ = this->values_; + this->values_[0] = 0; + } + + /** + * Merges the values of two aggregators in a semantically accurate manner. + * In this case, merging only requires the the current values of the two aggregators be summed. + * + * @param other, the aggregator with merge with + * @return none + */ + void merge(CounterAggregator other) + { + if (this->agg_kind_ == other.agg_kind_) + { + this->mu_.lock(); + this->values_[0] += other.values_[0]; + this->checkpoint_[0] += other.checkpoint_[0]; + this->mu_.unlock(); + } + else + { +#if __EXCEPTIONS + throw std::invalid_argument("Aggregators of different types cannot be merged."); +#else + std::terminate(); +#endif + } + } + + /** + * Returns the checkpointed value + * + * @param none + * @return the value of the checkpoint + */ + virtual std::vector get_checkpoint() override { return this->checkpoint_; } + + /** + * Returns the current values + * + * @param none + * @return the present aggregator values + */ + virtual std::vector get_values() override { return this->values_; } +}; + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/metrics/aggregator/histogram_aggregator.h b/sdk/include/opentelemetry/sdk/metrics/aggregator/histogram_aggregator.h new file mode 100644 index 0000000000..78b024eaec --- /dev/null +++ b/sdk/include/opentelemetry/sdk/metrics/aggregator/histogram_aggregator.h @@ -0,0 +1,197 @@ +#pragma once + +#include +#include +#include +#include +#include "opentelemetry/metrics/instrument.h" +#include "opentelemetry/sdk/metrics/aggregator/aggregator.h" +#include "opentelemetry/version.h" + +namespace metrics_api = opentelemetry::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +template +class HistogramAggregator final : public Aggregator +{ + +public: + /** + * Constructor for the histogram aggregator. A sorted vector of boundaries is expected and + * boundaries are doubles regardless of the aggregator's templated data type. + * + * Sum is stored in values_[0] + * Count is stored in position_[1] + */ + HistogramAggregator(metrics_api::InstrumentKind kind, std::vector boundaries) + { + if (!std::is_sorted(boundaries.begin(), boundaries.end())) + { +#if __EXCEPTIONS + throw std::invalid_argument("Histogram boundaries must be monotonic."); +#else + std::terminate(); +#endif + } + this->kind_ = kind; + this->agg_kind_ = AggregatorKind::Histogram; + boundaries_ = boundaries; + this->values_ = std::vector(2, 0); + this->checkpoint_ = std::vector(2, 0); + bucketCounts_ = std::vector(boundaries_.size() + 1, 0); + bucketCounts_ckpt_ = std::vector(boundaries_.size() + 1, 0); + } + + /** + * Recieves a captured value from the instrument and inserts it into the current histogram counts. + * + * Depending on the use case, a linear search or binary search based implementation may be + * preferred. In uniformly distributed datasets, linear search outperforms binary search until 512 + * buckets. However, if the distribution is strongly skewed right (for example server latency + * where most values may be <10ms but the range is from 0 - 1000 ms), a linear search could be + * superior even with more than 500 buckets as almost all values inserted would be at the + * beginning of the boundaries array and thus found more quickly through linear search. + * + * @param val, the raw value used in aggregation + * @return none + */ + void update(T val) override + { + int bucketID = boundaries_.size(); + for (size_t i = 0; i < boundaries_.size(); i++) + { + if (val < boundaries_[i]) // concurrent read is thread-safe + { + bucketID = i; + break; + } + } + + // Alternate implementation with binary search + // auto pos = std::lower_bound (boundaries_.begin(), boundaries_.end(), val); + // bucketCounts_[pos-boundaries_.begin()] += 1; + + this->mu_.lock(); + this->values_[0] += val; + this->values_[1] += 1; + bucketCounts_[bucketID] += 1; + this->mu_.unlock(); + } + + /** + * Checkpoints the current value. This function will overwrite the current checkpoint with the + * current value. + * + * @param none + * @return none + */ + void checkpoint() override + { + this->checkpoint_ = this->values_; + this->values_[0] = 0; + this->values_[1] = 0; + bucketCounts_ckpt_ = bucketCounts_; + std::fill(bucketCounts_.begin(), bucketCounts_.end(), 0); + } + + /** + * Merges the values of two aggregators in a semantically accurate manner. + * A histogram aggregator can only be merged with another histogram aggregator that has the same + * boudnaries. A histogram merge first adds the sum and count values then iterates over the adds + * the bucket counts element by element. + * + * @param other, the aggregator with merge with + * @return none + */ + void merge(HistogramAggregator other) + { + this->mu_.lock(); + + // Ensure that incorrect types are not merged + if (this->agg_kind_ != other.agg_kind_) + { +#if __EXCEPTIONS + throw std::invalid_argument("Aggregators of different types cannot be merged."); +#else + std::terminate(); +#endif + // Reject histogram merges with differing boundary vectors + } + else if (other.boundaries_ != this->boundaries_) + { +#if __EXCEPTIONS + throw std::invalid_argument("Histogram boundaries do not match."); +#else + std::terminate(); +#endif + } + + this->values_[0] += other.values_[0]; + this->values_[1] += other.values_[1]; + + for (size_t i = 0; i < bucketCounts_.size(); i++) + { + bucketCounts_[i] += other.bucketCounts_[i]; + bucketCounts_ckpt_[i] += other.bucketCounts_ckpt_[i]; + } + this->mu_.unlock(); + } + + /** + * Returns the checkpointed value + * + * @param none + * @return the value of the checkpoint + */ + std::vector get_checkpoint() override { return this->checkpoint_; } + + /** + * Returns the current values + * + * @param none + * @return the present aggregator values + */ + std::vector get_values() override { return this->values_; } + + /** + * Returns the bucket boundaries specified at this aggregator's creation. + * + * @param none + * @return the aggregator boundaries + */ + virtual std::vector get_boundaries() override { return boundaries_; } + + /** + * Returns the current counts for each bucket . + * + * @param none + * @return the aggregator bucket counts + */ + virtual std::vector get_counts() override { return bucketCounts_ckpt_; } + + HistogramAggregator(const HistogramAggregator &cp) + { + this->values_ = cp.values_; + this->checkpoint_ = cp.checkpoint_; + this->kind_ = cp.kind_; + this->agg_kind_ = cp.agg_kind_; + boundaries_ = cp.boundaries_; + bucketCounts_ = cp.bucketCounts_; + bucketCounts_ckpt_ = cp.bucketCounts_ckpt_; + // use default initialized mutex as they cannot be copied + } + +private: + std::vector boundaries_; + std::vector bucketCounts_; + std::vector bucketCounts_ckpt_; +}; + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/test/metrics/BUILD b/sdk/test/metrics/BUILD index e2d5c5cb71..4ce50b36e2 100644 --- a/sdk/test/metrics/BUILD +++ b/sdk/test/metrics/BUILD @@ -8,3 +8,25 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "counter_aggregator_test", + srcs = [ + "counter_aggregator_test.cc", + ], + deps = [ + "//sdk/src/metrics", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "histogram_aggregator_test", + srcs = [ + "histogram_aggregator_test.cc", + ], + deps = [ + "//sdk/src/metrics", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/sdk/test/metrics/counter_aggregator_test.cc b/sdk/test/metrics/counter_aggregator_test.cc new file mode 100644 index 0000000000..1e2b1dce8c --- /dev/null +++ b/sdk/test/metrics/counter_aggregator_test.cc @@ -0,0 +1,115 @@ +#include "opentelemetry/sdk/metrics/aggregator/counter_aggregator.h" + +#include +#include +#include + +namespace metrics_api = opentelemetry::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +TEST(CounterAggregator, NoUpdates) +{ + CounterAggregator alpha(metrics_api::InstrumentKind::Counter); + + EXPECT_EQ(alpha.get_checkpoint().size(), 1); + EXPECT_EQ(alpha.get_checkpoint()[0], 0); + + alpha.checkpoint(); + EXPECT_EQ(alpha.get_checkpoint().size(), 1); + EXPECT_EQ(alpha.get_checkpoint()[0], 0); +} + +TEST(CounterAggregator, Update) +{ + CounterAggregator alpha(metrics_api::InstrumentKind::Counter); + CounterAggregator beta(metrics_api::InstrumentKind::Counter); + + for (int i = 0; i < 123456; i++) + { + alpha.update(1); + } + + int sum = 0; + for (int i = 0; i < 100; i++) + { + int tmp = std::rand(); + beta.update(tmp); + sum += tmp; + } + + EXPECT_EQ(alpha.get_checkpoint()[0], 0); // checkpoint shouldn't change even with updates + EXPECT_EQ(beta.get_checkpoint()[0], 0); + + alpha.checkpoint(); + beta.checkpoint(); + + EXPECT_EQ(alpha.get_checkpoint()[0], 123456); + EXPECT_EQ(beta.get_checkpoint()[0], sum); + + alpha.update(15); + alpha.checkpoint(); + EXPECT_EQ(alpha.get_checkpoint()[0], 15); // reset to 0 after first checkpoint call +} + +// callback update function used to test concurrent calls +void incrementingCallback(Aggregator &agg) +{ + for (int i = 0; i < 2000000; i++) + { + agg.update(1); + } +} + +TEST(CounterAggregator, Concurrency) +{ + CounterAggregator alpha(metrics_api::InstrumentKind::Counter); + + // spawn new threads that initiate the callback + std::thread first(incrementingCallback, std::ref(alpha)); + std::thread second(incrementingCallback, std::ref(alpha)); + + first.join(); + second.join(); + + alpha.checkpoint(); + + // race conditions result in values below 2*2000000 + EXPECT_EQ(alpha.get_checkpoint()[0], 2 * 2000000); +} + +TEST(CounterAggregator, Merge) +{ + CounterAggregator alpha(metrics_api::InstrumentKind::Counter); + CounterAggregator beta(metrics_api::InstrumentKind::Counter); + + alpha.merge(beta); + + alpha.checkpoint(); + EXPECT_EQ(alpha.get_checkpoint()[0], 0); // merge with no updates + + for (int i = 0; i < 500; i++) + { + alpha.update(1); + } + + for (int i = 0; i < 700; i++) + { + beta.update(1); + } + + alpha.merge(beta); + alpha.checkpoint(); + EXPECT_EQ(alpha.get_checkpoint()[0], 1200); + + // HistogramAggregator gamma(metrics_api::BoundInstrumentKind::BoundValueRecorder); + // ASSERT_THROW(alpha.merge(gamma), AggregatorMismatch); +} + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/test/metrics/histogram_aggregator_test.cc b/sdk/test/metrics/histogram_aggregator_test.cc new file mode 100644 index 0000000000..146c552bb0 --- /dev/null +++ b/sdk/test/metrics/histogram_aggregator_test.cc @@ -0,0 +1,167 @@ +#include "opentelemetry/sdk/metrics/aggregator/histogram_aggregator.h" + +#include +#include +#include +#include +// #include + +namespace metrics_api = opentelemetry::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +// Test updating with a uniform set of updates +TEST(Histogram, Uniform) +{ + std::vector boundaries{10, 20, 30, 40, 50}; + HistogramAggregator alpha(metrics_api::InstrumentKind::Counter, boundaries); + + EXPECT_EQ(alpha.get_aggregator_kind(), AggregatorKind::Histogram); + + alpha.checkpoint(); + EXPECT_EQ(alpha.get_checkpoint().size(), 2); + EXPECT_EQ(alpha.get_counts().size(), 6); + + for (int i = 0; i < 60; i++) + { + alpha.update(i); + } + + alpha.checkpoint(); + + EXPECT_EQ(alpha.get_checkpoint()[0], 1770); + EXPECT_EQ(alpha.get_checkpoint()[1], 60); + + std::vector correct = {10, 10, 10, 10, 10, 10}; + EXPECT_EQ(alpha.get_counts(), correct); +} + +// Test updating with a normal distribution +TEST(Histogram, Normal) +{ + std::vector boundaries{2, 4, 6, 8, 10, 12}; + HistogramAggregator alpha(metrics_api::InstrumentKind::Counter, boundaries); + + std::vector vals{1, 3, 3, 5, 5, 5, 7, 7, 7, 7, 9, 9, 9, 11, 11, 13}; + for (int i : vals) + { + alpha.update(i); + } + + alpha.checkpoint(); + + EXPECT_EQ(alpha.get_checkpoint()[0], std::accumulate(vals.begin(), vals.end(), 0)); + EXPECT_EQ(alpha.get_checkpoint()[1], vals.size()); + + std::vector correct = {1, 2, 3, 4, 3, 2, 1}; + EXPECT_EQ(alpha.get_counts(), correct); +} + +TEST(Histogram, Merge) +{ + std::vector boundaries{2, 4, 6, 8, 10, 12}; + HistogramAggregator alpha(metrics_api::InstrumentKind::Counter, boundaries); + HistogramAggregator beta(metrics_api::InstrumentKind::Counter, boundaries); + + std::vector vals{1, 3, 3, 5, 5, 5, 7, 7, 7, 7, 9, 9, 9, 11, 11, 13}; + for (int i : vals) + { + alpha.update(i); + } + + std::vector otherVals{1, 1, 1, 1, 11, 11, 13, 13, 13, 15}; + for (int i : otherVals) + { + beta.update(i); + } + + alpha.merge(beta); + alpha.checkpoint(); + + EXPECT_EQ(alpha.get_checkpoint()[0], std::accumulate(vals.begin(), vals.end(), 0) + + std::accumulate(otherVals.begin(), otherVals.end(), 0)); + EXPECT_EQ(alpha.get_checkpoint()[1], vals.size() + otherVals.size()); + + std::vector correct = {5, 2, 3, 4, 3, 4, 5}; + EXPECT_EQ(alpha.get_counts(), correct); +} + +// Update callback used to validate multi-threaded performance +void histogramUpdateCallback(Aggregator &agg, std::vector vals) +{ + for (int i : vals) + { + agg.update(i); + } +} + +int randVal() +{ + return rand() % 15; +} + +TEST(Histogram, Concurrency) +{ + std::vector boundaries{2, 4, 6, 8, 10, 12}; + HistogramAggregator alpha(metrics_api::InstrumentKind::Counter, boundaries); + + std::vector vals1(1000); + std::generate(vals1.begin(), vals1.end(), randVal); + + std::vector vals2(1000); + std::generate(vals2.begin(), vals2.end(), randVal); + + std::thread first(histogramUpdateCallback, std::ref(alpha), vals1); + std::thread second(histogramUpdateCallback, std::ref(alpha), vals2); + + first.join(); + second.join(); + + HistogramAggregator beta(metrics_api::InstrumentKind::Counter, boundaries); + + // Timing harness to compare linear and binary insertion + // auto start = std::chrono::system_clock::now(); + for (int i : vals1) + { + beta.update(i); + } + for (int i : vals2) + { + beta.update(i); + } + // auto end = std::chrono::system_clock::now(); + // auto elapsed = std::chrono::duration_cast(end - start); + // std::cout <<"Update time: " < boundaries{2, 4, 6, 8, 10, 12}; + std::vector boundaries2{1, 4, 6, 8, 10, 12}; + std::vector unsortedBoundaries{10, 12, 4, 6, 8}; + EXPECT_ANY_THROW( + HistogramAggregator alpha(metrics_api::InstrumentKind::Counter, unsortedBoundaries)); + + HistogramAggregator beta(metrics_api::InstrumentKind::Counter, boundaries); + HistogramAggregator gamma(metrics_api::InstrumentKind::Counter, boundaries2); + + EXPECT_ANY_THROW(beta.merge(gamma)); +} + +#endif + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE