From 3ac89eb85eefbea70a52491eb4ad4aa48611a677 Mon Sep 17 00:00:00 2001 From: Ankit Bhargava Date: Mon, 3 Aug 2020 15:56:50 -0400 Subject: [PATCH] Add Synchronous Metric Instruments SDK (#179) --- .../opentelemetry/common/attribute_value.h | 21 + .../opentelemetry/sdk/metrics/instrument.h | 242 ++++++++++ .../opentelemetry/sdk/metrics/record.h | 47 ++ .../sdk/metrics/sync_instruments.h | 421 ++++++++++++++++++ sdk/test/metrics/BUILD | 11 + sdk/test/metrics/metric_instrument_test.cc | 287 ++++++++++++ 6 files changed, 1029 insertions(+) create mode 100644 sdk/include/opentelemetry/sdk/metrics/instrument.h create mode 100644 sdk/include/opentelemetry/sdk/metrics/record.h create mode 100644 sdk/include/opentelemetry/sdk/metrics/sync_instruments.h create mode 100644 sdk/test/metrics/metric_instrument_test.cc diff --git a/api/include/opentelemetry/common/attribute_value.h b/api/include/opentelemetry/common/attribute_value.h index 6340a4f97c..e68b6d8bb1 100644 --- a/api/include/opentelemetry/common/attribute_value.h +++ b/api/include/opentelemetry/common/attribute_value.h @@ -24,5 +24,26 @@ using AttributeValue = nostd::variant, nostd::span, nostd::span>; + +enum AttributeType +{ + TYPE_BOOL, + TYPE_INT, + TYPE_INT64, + TYPE_UINT, + TYPE_UINT64, + TYPE_DOUBLE, + TYPE_STRING, + TYPE_CSTRING, + // TYPE_SPAN_BYTE, // TODO: not part of OT spec yet + TYPE_SPAN_BOOL, + TYPE_SPAN_INT, + TYPE_SPAN_INT64, + TYPE_SPAN_UINT, + TYPE_SPAN_UINT64, + TYPE_SPAN_DOUBLE, + TYPE_SPAN_STRING +}; + } // namespace common OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/metrics/instrument.h b/sdk/include/opentelemetry/sdk/metrics/instrument.h new file mode 100644 index 0000000000..3a3e525714 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/metrics/instrument.h @@ -0,0 +1,242 @@ +#pragma once + +#include "opentelemetry/metrics/instrument.h" +#include "opentelemetry/sdk/metrics/aggregator/aggregator.h" +#include "opentelemetry/sdk/metrics/record.h" +#include "opentelemetry/version.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace metrics_api = opentelemetry::metrics; +namespace trace_api = opentelemetry::trace; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +class Instrument : virtual public metrics_api::Instrument +{ + +public: + Instrument() = default; + + Instrument(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + bool enabled, + metrics_api::InstrumentKind kind) + : name_(name), description_(description), unit_(unit), enabled_(enabled), kind_(kind) + {} + + // Returns true if the instrument is enabled and collecting data + virtual bool IsEnabled() override { return enabled_; } + + // Return the instrument name + virtual nostd::string_view GetName() override { return name_; } + + // Return the instrument description + virtual nostd::string_view GetDescription() override { return description_; } + + // Return the insrument's units of measurement + virtual nostd::string_view GetUnits() override { return unit_; } + + virtual metrics_api::InstrumentKind GetKind() override { return this->kind_; } + +protected: + std::string name_; + std::string description_; + std::string unit_; + bool enabled_; + std::mutex mu_; + metrics_api::InstrumentKind kind_; +}; + +template +class BoundSynchronousInstrument : public Instrument, + virtual public metrics_api::BoundSynchronousInstrument +{ + +public: + BoundSynchronousInstrument() = default; + + BoundSynchronousInstrument(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + bool enabled, + metrics_api::InstrumentKind kind, + std::shared_ptr> agg) + : Instrument(name, description, unit, enabled, kind), agg_(agg) + { + this->inc_ref(); // increase reference count when instantiated + } + + /** + * Frees the resources associated with this Bound Instrument. + * The Metric from which this instrument was created is not impacted. + * + * @param none + * @return void + */ + virtual void unbind() override { ref_ -= 1; } + + /** + * Increments the reference count. This function is used when binding or instantiating. + * + * @param none + * @return void + */ + virtual void inc_ref() override { ref_ += 1; } + + /** + * Returns the current reference count of the instrument. This value is used to + * later in the pipeline remove stale instruments. + * + * @param none + * @return current ref count of the instrument + */ + virtual int get_ref() override { return ref_; } + + /** + * Records a single synchronous metric event via a call to the aggregator. + * Since this is a bound synchronous instrument, labels are not required in + * metric capture calls. + * + * @param value is the numerical representation of the metric being captured + * @return void + */ + virtual void update(T value) override + { + this->mu_.lock(); + agg_->update(value); + this->mu_.unlock(); + } + + /** + * Returns the aggregator responsible for meaningfully combining update values. + * + * @param none + * @return the aggregator assigned to this instrument + */ + virtual std::shared_ptr> GetAggregator() final { return agg_; } + +private: + std::shared_ptr> agg_; + int ref_ = 0; +}; + +template +class SynchronousInstrument : public Instrument, + virtual public metrics_api::SynchronousInstrument +{ + +public: + SynchronousInstrument() = default; + + SynchronousInstrument(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + bool enabled, + metrics_api::InstrumentKind kind) + : Instrument(name, description, unit, enabled, kind) + {} + + /** + * Returns a Bound Instrument associated with the specified labels. Multiples requests + * with the same set of labels may return the same Bound Instrument instance. + * + * It is recommended that callers keep a reference to the Bound Instrument + * instead of repeatedly calling this operation. + * + * @param labels the set of labels, as key-value pairs + * @return a Bound Instrument + */ + virtual nostd::shared_ptr> bind( + const trace::KeyValueIterable &labels) override + { + return nostd::shared_ptr>(); + } + + virtual void update(T value, const trace::KeyValueIterable &labels) override = 0; + + /** + * Checkpoints instruments and returns a set of records which are ready for processing. + * This method should ONLY be called by the Meter Class as part of the export pipeline + * as it also prunes bound instruments with no active references. + * + * @param none + * @return vector of Records which hold the data attached to this synchronous instrument + */ + virtual std::vector GetRecords() = 0; +}; + +// Helper functions for turning a trace::KeyValueIterable into a string +inline void print_value(std::stringstream &ss, + common::AttributeValue &value, + bool jsonTypes = false) +{ + switch (value.index()) + { + case common::AttributeType::TYPE_STRING: + if (jsonTypes) + ss << '"'; + ss << nostd::get(value); + if (jsonTypes) + ss << '"'; + break; + default: +#if __EXCEPTIONS + throw std::invalid_argument("Labels must be strings"); +#else + std::terminate(); +#endif + break; + } +}; + +// Utility function which converts maps to strings for better performance +inline std::string mapToString(const std::map &conv) +{ + std::stringstream ss; + ss << "{"; + for (auto i : conv) + { + ss << i.first << ':' << i.second << ','; + } + ss << "}"; + return ss.str(); +} + +inline std::string KvToString(const trace::KeyValueIterable &kv) noexcept +{ + std::stringstream ss; + ss << "{"; + size_t size = kv.size(); + if (size) + { + size_t i = 1; + kv.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { + ss << "\"" << key << "\":"; + print_value(ss, value, true); + if (size != i) + { + ss << ","; + } + i++; + return true; + }); + }; + ss << "}"; + return ss.str(); +} + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/metrics/record.h b/sdk/include/opentelemetry/sdk/metrics/record.h new file mode 100644 index 0000000000..1c646faf2e --- /dev/null +++ b/sdk/include/opentelemetry/sdk/metrics/record.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include "opentelemetry/metrics/instrument.h" +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/sdk/metrics/aggregator/aggregator.h" + +OPENTELEMETRY_BEGIN_NAMESPACE + +namespace metrics_api = opentelemetry::metrics; + +namespace sdk +{ +namespace metrics +{ +using AggregatorVariant = nostd::variant>, + std::shared_ptr>, + std::shared_ptr>, + std::shared_ptr>>; +class Record +{ +public: + explicit Record(nostd::string_view name, + nostd::string_view description, + std::string labels, + AggregatorVariant aggregator) + { + name_ = std::string(name); + description_ = std::string(description); + labels_ = labels; + aggregator_ = aggregator; + } + + std::string GetName() { return name_; } + std::string GetDescription() { return description_; } + std::string GetLabels() { return labels_; } + AggregatorVariant GetAggregator() { return aggregator_; } + +private: + std::string name_; + std::string description_; + std::string labels_; + AggregatorVariant aggregator_; +}; +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/metrics/sync_instruments.h b/sdk/include/opentelemetry/sdk/metrics/sync_instruments.h new file mode 100644 index 0000000000..d8b1f83d24 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/metrics/sync_instruments.h @@ -0,0 +1,421 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "opentelemetry/metrics/sync_instruments.h" +#include "opentelemetry/sdk/metrics/aggregator/counter_aggregator.h" +#include "opentelemetry/sdk/metrics/aggregator/min_max_sum_count_aggregator.h" +#include "opentelemetry/sdk/metrics/instrument.h" + +namespace metrics_api = opentelemetry::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +template +class BoundCounter final : public BoundSynchronousInstrument, public metrics_api::BoundCounter +{ + +public: + BoundCounter() = default; + + BoundCounter(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + bool enabled) + : BoundSynchronousInstrument( + name, + description, + unit, + enabled, + metrics_api::InstrumentKind::Counter, + std::shared_ptr>(new CounterAggregator( + metrics_api::InstrumentKind::Counter))) // Aggregator is chosen here + {} + + /* + * Add adds the value to the counter's sum. The labels are already linked to the instrument + * and are not specified. + * + * @param value the numerical representation of the metric being captured + * @param labels the set of labels, as key-value pairs + */ + virtual void add(T value) override + { + this->mu_.lock(); + if (value < 0) + { +#if __EXCEPTIONS + throw std::invalid_argument("Counter instrument updates must be non-negative."); +#else + std::terminate(); +#endif + } + else + { + this->update(value); + } + this->mu_.unlock(); + } +}; + +template +class Counter final : public SynchronousInstrument, public metrics_api::Counter +{ + +public: + Counter() = default; + + Counter(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + bool enabled) + : SynchronousInstrument(name, + description, + unit, + enabled, + metrics_api::InstrumentKind::Counter) + {} + + /* + * Bind creates a bound instrument for this counter. The labels are + * associated with values recorded via subsequent calls to Record. + * + * @param labels the set of labels, as key-value pairs. + * @return a BoundCounter tied to the specified labels + */ + + virtual nostd::shared_ptr> bindCounter( + const trace::KeyValueIterable &labels) override + { + std::string labelset = KvToString(labels); + if (boundInstruments_.find(labelset) == boundInstruments_.end()) + { + auto sp1 = nostd::shared_ptr>( + new BoundCounter(this->name_, this->description_, this->unit_, this->enabled_)); + boundInstruments_[labelset] = sp1; + return sp1; + } + else + { + boundInstruments_[labelset]->inc_ref(); + return boundInstruments_[labelset]; + } + } + + /* + * Add adds the value to the counter's sum. The labels should contain + * the keys and values to be associated with this value. Counters only + * accept positive valued updates. + * + * @param value the numerical representation of the metric being captured + * @param labels the set of labels, as key-value pairs + */ + virtual void add(T value, const trace::KeyValueIterable &labels) override + { + this->mu_.lock(); + if (value < 0) + { +#if __EXCEPTIONS + throw std::invalid_argument("Counter instrument updates must be non-negative."); +#else + std::terminate(); +#endif + } + else + { + auto sp = bindCounter(labels); + sp->update(value); + sp->unbind(); + } + this->mu_.unlock(); + } + + virtual std::vector GetRecords() override + { + std::vector ret; + std::vector toDelete; + for (const auto &x : boundInstruments_) + { + if (x.second->get_ref() == 0) + { + toDelete.push_back(x.first); + } + auto agg_ptr = dynamic_cast *>(x.second.get())->GetAggregator(); + agg_ptr->checkpoint(); + ret.push_back(Record(x.second->GetName(), x.second->GetDescription(), x.first, agg_ptr)); + } + for (const auto &x : toDelete) + { + boundInstruments_.erase(x); + } + return ret; + } + + virtual void update(T val, const trace::KeyValueIterable &labels) override { add(val, labels); } + + // A collection of the bound instruments created by this unbound instrument identified by their + // labels. + std::unordered_map>> + boundInstruments_; +}; + +template +class BoundUpDownCounter final : public BoundSynchronousInstrument, + virtual public metrics_api::BoundUpDownCounter +{ + +public: + BoundUpDownCounter() = default; + + BoundUpDownCounter(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + bool enabled) + : BoundSynchronousInstrument(name, + description, + unit, + enabled, + metrics_api::InstrumentKind::UpDownCounter, + std::shared_ptr>(new CounterAggregator( + metrics_api::InstrumentKind::UpDownCounter))) + {} + + /* + * Add adds the value to the counter's sum. The labels are already linked to the instrument + * and are not specified. + * + * @param value the numerical representation of the metric being captured + * @param labels the set of labels, as key-value pairs + */ + virtual void add(T value) override + { + this->mu_.lock(); + this->update(value); + this->mu_.unlock(); + } +}; + +template +class UpDownCounter final : public SynchronousInstrument, public metrics_api::UpDownCounter +{ + +public: + UpDownCounter() = default; + + UpDownCounter(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + bool enabled) + : SynchronousInstrument(name, + description, + unit, + enabled, + metrics_api::InstrumentKind::UpDownCounter) + {} + + /* + * Bind creates a bound instrument for this counter. The labels are + * associated with values recorded via subsequent calls to Record. + * + * @param labels the set of labels, as key-value pairs. + * @return a BoundIntCounter tied to the specified labels + */ + nostd::shared_ptr> bindUpDownCounter( + const trace::KeyValueIterable &labels) override + { + std::string labelset = KvToString(labels); + if (boundInstruments_.find(labelset) == boundInstruments_.end()) + { + auto sp1 = nostd::shared_ptr>( + new BoundUpDownCounter(this->name_, this->description_, this->unit_, this->enabled_)); + boundInstruments_[labelset] = sp1; + return sp1; + } + else + { + boundInstruments_[labelset]->inc_ref(); + return boundInstruments_[labelset]; + } + } + + /* + * Add adds the value to the counter's sum. The labels should contain + * the keys and values to be associated with this value. Counters only + * accept positive valued updates. + * + * @param value the numerical representation of the metric being captured + * @param labels the set of labels, as key-value pairs + */ + void add(T value, const trace::KeyValueIterable &labels) override + { + this->mu_.lock(); + auto sp = bindUpDownCounter(labels); + sp->update(value); + sp->unbind(); + this->mu_.unlock(); + } + + virtual std::vector GetRecords() override + { + std::vector ret; + std::vector toDelete; + for (const auto &x : boundInstruments_) + { + if (x.second->get_ref() == 0) + { + toDelete.push_back(x.first); + } + auto agg_ptr = dynamic_cast *>(x.second.get())->GetAggregator(); + agg_ptr->checkpoint(); + ret.push_back(Record(x.second->GetName(), x.second->GetDescription(), x.first, agg_ptr)); + } + for (const auto &x : toDelete) + { + boundInstruments_.erase(x); + } + return ret; + } + + virtual void update(T val, const trace::KeyValueIterable &labels) override { add(val, labels); } + + std::unordered_map>> + boundInstruments_; +}; + +template +class BoundValueRecorder final : public BoundSynchronousInstrument, + public metrics_api::BoundValueRecorder +{ + +public: + BoundValueRecorder() = default; + + BoundValueRecorder(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + bool enabled) + : BoundSynchronousInstrument( + name, + description, + unit, + enabled, + metrics_api::InstrumentKind::ValueRecorder, + std::shared_ptr>( + new MinMaxSumCountAggregator(metrics_api::InstrumentKind::ValueRecorder))) + {} + + /* + * Add adds the value to the counter's sum. The labels are already linked to the instrument + * and are not specified. + * + * @param value the numerical representation of the metric being captured + * @param labels the set of labels, as key-value pairs + */ + void record(T value) + { + this->mu_.lock(); + this->update(value); + this->mu_.unlock(); + } +}; + +template +class ValueRecorder final : public SynchronousInstrument, public metrics_api::ValueRecorder +{ + +public: + ValueRecorder() = default; + + ValueRecorder(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + bool enabled) + : SynchronousInstrument(name, + description, + unit, + enabled, + metrics_api::InstrumentKind::ValueRecorder) + {} + + /* + * Bind creates a bound instrument for this counter. The labels are + * associated with values recorded via subsequent calls to Record. + * + * @param labels the set of labels, as key-value pairs. + * @return a BoundIntCounter tied to the specified labels + */ + nostd::shared_ptr> bindValueRecorder( + const trace::KeyValueIterable &labels) override + { + std::string labelset = KvToString(labels); + if (boundInstruments_.find(labelset) == boundInstruments_.end()) + { + auto sp1 = nostd::shared_ptr>( + new BoundValueRecorder(this->name_, this->description_, this->unit_, this->enabled_)); + boundInstruments_[labelset] = sp1; + return sp1; + } + else + { + boundInstruments_[labelset]->inc_ref(); + return boundInstruments_[labelset]; + } + } + + /* + * Add adds the value to the counter's sum. The labels should contain + * the keys and values to be associated with this value. Counters only + * accept positive valued updates. + * + * @param value the numerical representation of the metric being captured + * @param labels the set of labels, as key-value pairs + */ + void record(T value, const trace::KeyValueIterable &labels) override + { + this->mu_.lock(); + auto sp = bindValueRecorder(labels); + sp->update(value); + sp->unbind(); + this->mu_.unlock(); + } + + virtual std::vector GetRecords() override + { + std::vector ret; + std::vector toDelete; + for (const auto &x : boundInstruments_) + { + if (x.second->get_ref() == 0) + { + toDelete.push_back(x.first); + } + auto agg_ptr = dynamic_cast *>(x.second.get())->GetAggregator(); + agg_ptr->checkpoint(); + ret.push_back(Record(x.second->GetName(), x.second->GetDescription(), x.first, agg_ptr)); + } + for (const auto &x : toDelete) + { + boundInstruments_.erase(x); + } + return ret; + } + + virtual void update(T value, const trace::KeyValueIterable &labels) override + { + record(value, labels); + } + + std::unordered_map>> + boundInstruments_; +}; + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/test/metrics/BUILD b/sdk/test/metrics/BUILD index d24acd3e58..85081d8e18 100644 --- a/sdk/test/metrics/BUILD +++ b/sdk/test/metrics/BUILD @@ -63,3 +63,14 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "metric_instrument_test", + srcs = [ + "metric_instrument_test.cc", + ], + deps = [ + "//sdk/src/metrics", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/sdk/test/metrics/metric_instrument_test.cc b/sdk/test/metrics/metric_instrument_test.cc new file mode 100644 index 0000000000..183b5b97e2 --- /dev/null +++ b/sdk/test/metrics/metric_instrument_test.cc @@ -0,0 +1,287 @@ +#include +#include "opentelemetry/sdk/metrics/sync_instruments.h" + +#include +#include +#include +#include +#include +#include + +namespace metrics_api = opentelemetry::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +TEST(Counter, InstrumentFunctions) +{ + Counter alpha("enabled", "no description", "unitless", true); + Counter beta("not enabled", "some description", "units", false); + + EXPECT_EQ(static_cast(alpha.GetName()), "enabled"); + EXPECT_EQ(static_cast(alpha.GetDescription()), "no description"); + EXPECT_EQ(static_cast(alpha.GetUnits()), "unitless"); + EXPECT_EQ(alpha.IsEnabled(), true); + + EXPECT_EQ(static_cast(beta.GetName()), "not enabled"); + EXPECT_EQ(static_cast(beta.GetDescription()), "some description"); + EXPECT_EQ(static_cast(beta.GetUnits()), "units"); + EXPECT_EQ(beta.IsEnabled(), false); +} + +TEST(Counter, Binding) +{ + Counter alpha("test", "none", "unitless", true); + + std::map labels = {{"key", "value"}}; + std::map labels1 = {{"key1", "value1"}}; + std::map labels2 = {{"key2", "value2"}, {"key3", "value3"}}; + std::map labels3 = {{"key3", "value3"}, {"key2", "value2"}}; + + auto labelkv = trace::KeyValueIterableView{labels}; + auto labelkv1 = trace::KeyValueIterableView{labels1}; + auto labelkv2 = trace::KeyValueIterableView{labels2}; + auto labelkv3 = trace::KeyValueIterableView{labels3}; + + auto beta = alpha.bindCounter(labelkv); + auto gamma = alpha.bindCounter(labelkv1); + auto delta = alpha.bindCounter(labelkv1); + auto epsilon = alpha.bindCounter(labelkv1); + auto zeta = alpha.bindCounter(labelkv2); + auto eta = alpha.bindCounter(labelkv3); + + EXPECT_EQ(beta->get_ref(), 1); + EXPECT_EQ(gamma->get_ref(), 3); + EXPECT_EQ(eta->get_ref(), 2); + + delta->unbind(); + gamma->unbind(); + epsilon->unbind(); + + EXPECT_EQ(alpha.boundInstruments_[KvToString(labelkv1)]->get_ref(), 0); + EXPECT_EQ(alpha.boundInstruments_.size(), 3); +} + +TEST(Counter, getAggsandnewupdate) +{ + Counter alpha("test", "none", "unitless", true); + + std::map labels = {{"key", "value"}}; + std::map labels1 = {{"key1", "value1"}}; + + // labels 2 and 3 are actually the same + std::map labels2 = {{"key2", "value2"}, {"key3", "value3"}}; + std::map labels3 = {{"key3", "value3"}, {"key2", "value2"}}; + + auto labelkv = trace::KeyValueIterableView{labels}; + auto labelkv1 = trace::KeyValueIterableView{labels1}; + auto labelkv2 = trace::KeyValueIterableView{labels2}; + auto labelkv3 = trace::KeyValueIterableView{labels3}; + + auto beta = alpha.bindCounter(labelkv); + auto gamma = alpha.bindCounter(labelkv1); + auto delta = alpha.bindCounter(labelkv1); + auto epsilon = alpha.bindCounter(labelkv1); + auto zeta = alpha.bindCounter(labelkv2); + auto eta = alpha.bindCounter(labelkv3); + + EXPECT_EQ(beta->get_ref(), 1); + EXPECT_EQ(gamma->get_ref(), 3); + EXPECT_EQ(eta->get_ref(), 2); + + delta->unbind(); + gamma->unbind(); + epsilon->unbind(); + + EXPECT_EQ(alpha.boundInstruments_[KvToString(labelkv1)]->get_ref(), 0); + EXPECT_EQ(alpha.boundInstruments_.size(), 3); + + auto theta = alpha.GetRecords(); + EXPECT_EQ(theta.size(), 3); + EXPECT_EQ(theta[0].GetName(), "test"); + EXPECT_EQ(theta[0].GetDescription(), "none"); + EXPECT_EQ(theta[0].GetLabels(), "{\"key2\":\"value2\",\"key3\":\"value3\"}"); + EXPECT_EQ(theta[1].GetLabels(), "{\"key1\":\"value1\"}"); +} + +void CounterCallback(std::shared_ptr> in, + int freq, + const trace::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->add(1, labels); + } +} + +TEST(Counter, StressAdd) +{ + std::shared_ptr> alpha(new Counter("test", "none", "unitless", true)); + + std::map labels = {{"key", "value"}}; + std::map labels1 = {{"key1", "value1"}}; + + auto labelkv = trace::KeyValueIterableView{labels}; + auto labelkv1 = trace::KeyValueIterableView{labels1}; + + std::thread first(CounterCallback, alpha, 100000, labelkv); + std::thread second(CounterCallback, alpha, 100000, labelkv); + std::thread third(CounterCallback, alpha, 300000, labelkv1); + + first.join(); + second.join(); + third.join(); + + EXPECT_EQ(dynamic_cast *>(alpha->boundInstruments_[KvToString(labelkv)].get()) + ->GetAggregator() + ->get_values()[0], + 200000); + EXPECT_EQ(dynamic_cast *>(alpha->boundInstruments_[KvToString(labelkv1)].get()) + ->GetAggregator() + ->get_values()[0], + 300000); +} + +void UpDownCounterCallback(std::shared_ptr> in, + int freq, + const trace::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->add(1, labels); + } +} + +void NegUpDownCounterCallback(std::shared_ptr> in, + int freq, + const trace::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->add(-1, labels); + } +} + +TEST(IntUpDownCounter, StressAdd) +{ + std::shared_ptr> alpha( + new UpDownCounter("test", "none", "unitless", true)); + + std::map labels = {{"key", "value"}}; + std::map labels1 = {{"key1", "value1"}}; + auto labelkv = trace::KeyValueIterableView{labels}; + auto labelkv1 = trace::KeyValueIterableView{labels1}; + + std::thread first(UpDownCounterCallback, alpha, 123400, + labelkv); // spawn new threads that call the callback + std::thread second(UpDownCounterCallback, alpha, 123400, labelkv); + std::thread third(UpDownCounterCallback, alpha, 567800, labelkv1); + std::thread fourth(NegUpDownCounterCallback, alpha, 123400, labelkv1); // negative values + + first.join(); + second.join(); + third.join(); + fourth.join(); + + EXPECT_EQ( + dynamic_cast *>(alpha->boundInstruments_[KvToString(labelkv)].get()) + ->GetAggregator() + ->get_values()[0], + 123400 * 2); + EXPECT_EQ( + dynamic_cast *>(alpha->boundInstruments_[KvToString(labelkv1)].get()) + ->GetAggregator() + ->get_values()[0], + 567800 - 123400); +} + +void RecorderCallback(std::shared_ptr> in, + int freq, + const trace::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->record(i, labels); + } +} + +void NegRecorderCallback(std::shared_ptr> in, + int freq, + const trace::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->record(-i, labels); + } +} + +TEST(IntValueRecorder, StressRecord) +{ + std::shared_ptr> alpha( + new ValueRecorder("test", "none", "unitless", true)); + + std::map labels = {{"key", "value"}}; + std::map labels1 = {{"key1", "value1"}}; + auto labelkv = trace::KeyValueIterableView{labels}; + auto labelkv1 = trace::KeyValueIterableView{labels1}; + + std::thread first(RecorderCallback, alpha, 25, + labelkv); // spawn new threads that call the callback + std::thread second(RecorderCallback, alpha, 50, labelkv); + std::thread third(RecorderCallback, alpha, 25, labelkv1); + std::thread fourth(NegRecorderCallback, alpha, 100, labelkv1); // negative values + + first.join(); + second.join(); + third.join(); + fourth.join(); + + EXPECT_EQ( + dynamic_cast *>(alpha->boundInstruments_[KvToString(labelkv)].get()) + ->GetAggregator() + ->get_values()[0], + 0); // min + EXPECT_EQ( + dynamic_cast *>(alpha->boundInstruments_[KvToString(labelkv)].get()) + ->GetAggregator() + ->get_values()[1], + 49); // max + EXPECT_EQ( + dynamic_cast *>(alpha->boundInstruments_[KvToString(labelkv)].get()) + ->GetAggregator() + ->get_values()[2], + 1525); // sum + EXPECT_EQ( + dynamic_cast *>(alpha->boundInstruments_[KvToString(labelkv)].get()) + ->GetAggregator() + ->get_values()[3], + 75); // count + + EXPECT_EQ( + dynamic_cast *>(alpha->boundInstruments_[KvToString(labelkv1)].get()) + ->GetAggregator() + ->get_values()[0], + -99); // min + EXPECT_EQ( + dynamic_cast *>(alpha->boundInstruments_[KvToString(labelkv1)].get()) + ->GetAggregator() + ->get_values()[1], + 24); // max + EXPECT_EQ( + dynamic_cast *>(alpha->boundInstruments_[KvToString(labelkv1)].get()) + ->GetAggregator() + ->get_values()[2], + -4650); // sum + EXPECT_EQ( + dynamic_cast *>(alpha->boundInstruments_[KvToString(labelkv1)].get()) + ->GetAggregator() + ->get_values()[3], + 125); // count +} + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE