From c344737fb8720152cd5652c2a2fefa49e6a5f016 Mon Sep 17 00:00:00 2001 From: Ankit Bhargava Date: Wed, 5 Aug 2020 14:58:00 -0400 Subject: [PATCH 01/12] Add Asynchronous Metric Instruments SDK (#191) --- .../sdk/metrics/async_instruments.h | 272 ++++++++++++++++++ .../opentelemetry/sdk/metrics/instrument.h | 67 ++++- .../sdk/metrics/sync_instruments.h | 46 +-- sdk/test/metrics/metric_instrument_test.cc | 180 +++++++++++- 4 files changed, 533 insertions(+), 32 deletions(-) create mode 100644 sdk/include/opentelemetry/sdk/metrics/async_instruments.h diff --git a/sdk/include/opentelemetry/sdk/metrics/async_instruments.h b/sdk/include/opentelemetry/sdk/metrics/async_instruments.h new file mode 100644 index 0000000000..9eccb86466 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/metrics/async_instruments.h @@ -0,0 +1,272 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "opentelemetry/metrics/async_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" +#include "opentelemetry/version.h" + +namespace metrics_api = opentelemetry::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +template +class ValueObserver : public AsynchronousInstrument, virtual public metrics_api::ValueObserver +{ + +public: + ValueObserver() = default; + + ValueObserver(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + bool enabled, + void (*callback)(metrics_api::ObserverResult)) + : AsynchronousInstrument(name, + description, + unit, + enabled, + callback, + metrics_api::InstrumentKind::ValueObserver) + {} + + /* + * Updates the instruments aggregator with the new value. The labels should + * contain the keys and values to be associated with this value. + * + * @param value is the numerical representation of the metric being captured + * @param labels the set of labels, as key-value pairs + */ + virtual void observe(T value, const trace::KeyValueIterable &labels) override + { + this->mu_.lock(); + std::string labelset = KvToString(labels); + if (boundAggregators_.find(labelset) == boundAggregators_.end()) + { + auto sp1 = std::shared_ptr>(new MinMaxSumCountAggregator(this->kind_)); + boundAggregators_.insert(std::make_pair(labelset, sp1)); + sp1->update(value); + } + else + { + boundAggregators_[labelset]->update(value); + } + this->mu_.unlock(); + } + + /* + * Activate the instrument's callback function to record a measurement. This + * function will be called by the specified controller at a regular interval. + * + * @param none + * @return none + */ + virtual void run() override + { + metrics_api::ObserverResult res(this); + this->callback_(res); + } + + virtual std::vector GetRecords() override + { + this->mu_.lock(); + std::vector ret; + for (auto x : boundAggregators_) + { + x.second->checkpoint(); + ret.push_back(Record(this->GetName(), this->GetDescription(), x.first, x.second)); + } + boundAggregators_.clear(); + this->mu_.unlock(); + return ret; + } + + // Public mapping from labels (stored as strings) to their respective aggregators + std::unordered_map>> boundAggregators_; +}; + +template +class SumObserver : public AsynchronousInstrument, virtual public metrics_api::SumObserver +{ + +public: + SumObserver() = default; + + SumObserver(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + bool enabled, + void (*callback)(metrics_api::ObserverResult)) + : AsynchronousInstrument(name, + description, + unit, + enabled, + callback, + metrics_api::InstrumentKind::SumObserver) + {} + + /* + * Updates the instruments aggregator with the new value. The labels should + * contain the keys and values to be associated with this value. + * + * @param value is the numerical representation of the metric being captured + * @param labels the set of labels, as key-value pairs + */ + virtual void observe(T value, const trace::KeyValueIterable &labels) override + { + this->mu_.lock(); + std::string labelset = KvToString(labels); + if (boundAggregators_.find(labelset) == boundAggregators_.end()) + { + auto sp1 = std::shared_ptr>(new CounterAggregator(this->kind_)); + boundAggregators_.insert(std::make_pair(labelset, sp1)); + if (value < 0) + { +#if __EXCEPTIONS + throw std::invalid_argument("Counter instrument updates must be non-negative."); +#else + std::terminate(); +#endif + } + else + { + sp1->update(value); + } + } + else + { + if (value < 0) + { +#if __EXCEPTIONS + throw std::invalid_argument("Counter instrument updates must be non-negative."); +#else + std::terminate(); +#endif + } + else + { + boundAggregators_[labelset]->update(value); + } + } + this->mu_.unlock(); + } + + /* + * Activate the intsrument's callback function to record a measurement. This + * function will be called by the specified controller at a regular interval. + * + * @param none + * @return none + */ + virtual void run() override + { + metrics_api::ObserverResult res(this); + this->callback_(res); + } + + virtual std::vector GetRecords() override + { + this->mu_.lock(); + std::vector ret; + for (auto x : boundAggregators_) + { + x.second->checkpoint(); + ret.push_back(Record(this->GetName(), this->GetDescription(), x.first, x.second)); + } + boundAggregators_.clear(); + this->mu_.unlock(); + return ret; + } + + // Public mapping from labels (stored as strings) to their respective aggregators + std::unordered_map>> boundAggregators_; +}; + +template +class UpDownSumObserver : public AsynchronousInstrument, + virtual public metrics_api::UpDownSumObserver +{ + +public: + UpDownSumObserver() = default; + + UpDownSumObserver(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + bool enabled, + void (*callback)(metrics_api::ObserverResult)) + : AsynchronousInstrument(name, + description, + unit, + enabled, + callback, + metrics_api::InstrumentKind::UpDownSumObserver) + {} + + /* + * Updates the instruments aggregator with the new value. The labels should + * contain the keys and values to be associated with this value. + * + * @param value is the numerical representation of the metric being captured + * @param labels the set of labels, as key-value pairs + */ + virtual void observe(T value, const trace::KeyValueIterable &labels) override + { + this->mu_.lock(); + std::string labelset = KvToString(labels); + if (boundAggregators_.find(labelset) == boundAggregators_.end()) + { + auto sp1 = std::shared_ptr>(new CounterAggregator(this->kind_)); + boundAggregators_.insert(std::make_pair(labelset, sp1)); + sp1->update(value); + } + else + { + boundAggregators_[labelset]->update(value); + } + this->mu_.unlock(); + } + + /* + * Activate the intsrument's callback function to record a measurement. This + * function will be called by the specified controller at a regular interval. + * + * @param none + * @return none + */ + virtual void run() override + { + metrics_api::ObserverResult res(this); + this->callback_(res); + } + + virtual std::vector GetRecords() override + { + this->mu_.lock(); + std::vector ret; + for (auto x : boundAggregators_) + { + x.second->checkpoint(); + ret.push_back(Record(this->GetName(), this->GetDescription(), x.first, x.second)); + } + boundAggregators_.clear(); + this->mu_.unlock(); + return ret; + } + + // Public mapping from labels (stored as strings) to their respective aggregators + std::unordered_map>> boundAggregators_; +}; + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/metrics/instrument.h b/sdk/include/opentelemetry/sdk/metrics/instrument.h index 3a3e525714..1d06cbec4f 100644 --- a/sdk/include/opentelemetry/sdk/metrics/instrument.h +++ b/sdk/include/opentelemetry/sdk/metrics/instrument.h @@ -1,10 +1,5 @@ #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 @@ -12,6 +7,10 @@ #include #include #include +#include "opentelemetry/metrics/instrument.h" +#include "opentelemetry/sdk/metrics/aggregator/aggregator.h" +#include "opentelemetry/sdk/metrics/record.h" +#include "opentelemetry/version.h" namespace metrics_api = opentelemetry::metrics; namespace trace_api = opentelemetry::trace; @@ -85,7 +84,12 @@ class BoundSynchronousInstrument : public Instrument, * @param none * @return void */ - virtual void unbind() override { ref_ -= 1; } + virtual void unbind() override + { + this->mu_.lock(); + ref_ -= 1; + this->mu_.unlock(); + } /** * Increments the reference count. This function is used when binding or instantiating. @@ -93,7 +97,12 @@ class BoundSynchronousInstrument : public Instrument, * @param none * @return void */ - virtual void inc_ref() override { ref_ += 1; } + virtual void inc_ref() override + { + this->mu_.lock(); + ref_ += 1; + this->mu_.unlock(); + } /** * Returns the current reference count of the instrument. This value is used to @@ -164,6 +173,7 @@ class SynchronousInstrument : public Instrument, return nostd::shared_ptr>(); } + // This function is necessary for batch recording and should NOT be called by the user virtual void update(T value, const trace::KeyValueIterable &labels) override = 0; /** @@ -177,6 +187,49 @@ class SynchronousInstrument : public Instrument, virtual std::vector GetRecords() = 0; }; +template +class AsynchronousInstrument : public Instrument, + virtual public metrics_api::AsynchronousInstrument +{ + +public: + AsynchronousInstrument() = default; + + AsynchronousInstrument(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + bool enabled, + void (*callback)(metrics_api::ObserverResult), + metrics_api::InstrumentKind kind) + : Instrument(name, description, unit, enabled, kind) + { + this->callback_ = callback; + } + + /** + * Captures data through a manual call rather than the automatic collection process instituted + * in the run function. Asynchronous instruments are generally expected to obtain data from + * their callbacks rather than direct calls. This function is used by the callback to store data. + * + * @param value is the numerical representation of the metric being captured + * @param labels is the numerical representation of the metric being captured + * @return none + */ + virtual void observe(T value, const trace::KeyValueIterable &labels) override = 0; + + virtual std::vector GetRecords() = 0; + + /** + * Captures data by activating the callback function associated with the + * instrument and storing its return value. Callbacks for asynchronous + * instruments are defined during construction. + * + * @param none + * @return none + */ + virtual void run() override = 0; +}; + // Helper functions for turning a trace::KeyValueIterable into a string inline void print_value(std::stringstream &ss, common::AttributeValue &value, diff --git a/sdk/include/opentelemetry/sdk/metrics/sync_instruments.h b/sdk/include/opentelemetry/sdk/metrics/sync_instruments.h index d8b1f83d24..2c0931eeb2 100644 --- a/sdk/include/opentelemetry/sdk/metrics/sync_instruments.h +++ b/sdk/include/opentelemetry/sdk/metrics/sync_instruments.h @@ -48,7 +48,6 @@ class BoundCounter final : public BoundSynchronousInstrument, public metrics_ */ virtual void add(T value) override { - this->mu_.lock(); if (value < 0) { #if __EXCEPTIONS @@ -61,7 +60,6 @@ class BoundCounter final : public BoundSynchronousInstrument, public metrics_ { this->update(value); } - this->mu_.unlock(); } }; @@ -94,18 +92,22 @@ class Counter final : public SynchronousInstrument, public metrics_api::Count virtual nostd::shared_ptr> bindCounter( const trace::KeyValueIterable &labels) override { + this->mu_.lock(); 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; + this->mu_.unlock(); return sp1; } else { boundInstruments_[labelset]->inc_ref(); - return boundInstruments_[labelset]; + auto ret = boundInstruments_[labelset]; + this->mu_.unlock(); + return ret; } } @@ -119,7 +121,6 @@ class Counter final : public SynchronousInstrument, public metrics_api::Count */ virtual void add(T value, const trace::KeyValueIterable &labels) override { - this->mu_.lock(); if (value < 0) { #if __EXCEPTIONS @@ -134,11 +135,11 @@ class Counter final : public SynchronousInstrument, public metrics_api::Count sp->update(value); sp->unbind(); } - this->mu_.unlock(); } virtual std::vector GetRecords() override { + this->mu_.lock(); std::vector ret; std::vector toDelete; for (const auto &x : boundInstruments_) @@ -155,6 +156,7 @@ class Counter final : public SynchronousInstrument, public metrics_api::Count { boundInstruments_.erase(x); } + this->mu_.unlock(); return ret; } @@ -194,12 +196,7 @@ class BoundUpDownCounter final : public BoundSynchronousInstrument, * @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(); - } + virtual void add(T value) override { this->update(value); } }; template @@ -230,18 +227,22 @@ class UpDownCounter final : public SynchronousInstrument, public metrics_api: nostd::shared_ptr> bindUpDownCounter( const trace::KeyValueIterable &labels) override { + this->mu_.lock(); 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; + this->mu_.unlock(); return sp1; } else { boundInstruments_[labelset]->inc_ref(); - return boundInstruments_[labelset]; + auto ret = boundInstruments_[labelset]; + this->mu_.unlock(); + return ret; } } @@ -255,15 +256,14 @@ class UpDownCounter final : public SynchronousInstrument, public metrics_api: */ 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 { + this->mu_.lock(); std::vector ret; std::vector toDelete; for (const auto &x : boundInstruments_) @@ -280,6 +280,7 @@ class UpDownCounter final : public SynchronousInstrument, public metrics_api: { boundInstruments_.erase(x); } + this->mu_.unlock(); return ret; } @@ -318,12 +319,7 @@ class BoundValueRecorder final : public BoundSynchronousInstrument, * @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(); - } + void record(T value) { this->update(value); } }; template @@ -354,18 +350,22 @@ class ValueRecorder final : public SynchronousInstrument, public metrics_api: nostd::shared_ptr> bindValueRecorder( const trace::KeyValueIterable &labels) override { + this->mu_.lock(); 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; + this->mu_.unlock(); return sp1; } else { boundInstruments_[labelset]->inc_ref(); - return boundInstruments_[labelset]; + auto ret = boundInstruments_[labelset]; + this->mu_.unlock(); + return ret; } } @@ -379,15 +379,14 @@ class ValueRecorder final : public SynchronousInstrument, public metrics_api: */ 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 { + this->mu_.lock(); std::vector ret; std::vector toDelete; for (const auto &x : boundInstruments_) @@ -404,6 +403,7 @@ class ValueRecorder final : public SynchronousInstrument, public metrics_api: { boundInstruments_.erase(x); } + this->mu_.unlock(); return ret; } diff --git a/sdk/test/metrics/metric_instrument_test.cc b/sdk/test/metrics/metric_instrument_test.cc index f1bb23b73f..97fe53278c 100644 --- a/sdk/test/metrics/metric_instrument_test.cc +++ b/sdk/test/metrics/metric_instrument_test.cc @@ -1,12 +1,13 @@ -#include -#include "opentelemetry/sdk/metrics/sync_instruments.h" +#include #include #include #include #include #include #include +#include "opentelemetry/sdk/metrics/async_instruments.h" +#include "opentelemetry/sdk/metrics/sync_instruments.h" namespace metrics_api = opentelemetry::metrics; @@ -16,6 +17,180 @@ namespace sdk namespace metrics { +void ObserverConstructorCallback(metrics_api::ObserverResult result) +{ + std::map labels = {{"key", "value"}}; + auto labelkv = trace::KeyValueIterableView{labels}; + result.observe(1, labelkv); +} + +TEST(ApiSdkConversion, async) +{ + nostd::shared_ptr> alpha = + nostd::shared_ptr>( + new ValueObserver("ankit", "none", "unitles", true, &ObserverConstructorCallback)); + + std::map labels = {{"key587", "value264"}}; + auto labelkv = trace::KeyValueIterableView{labels}; + + alpha->observe(123456, labelkv); + EXPECT_EQ(dynamic_cast *>(alpha.get())->GetRecords()[0].GetLabels(), + "{\"key587\":\"value264\"}"); + + alpha->observe(123456, labelkv); + AggregatorVariant canCollect = + dynamic_cast *>(alpha.get())->GetRecords()[0].GetAggregator(); + EXPECT_EQ(nostd::holds_alternative>>(canCollect), false); + EXPECT_EQ(nostd::holds_alternative>>(canCollect), true); + EXPECT_EQ(nostd::get>>(canCollect)->get_checkpoint()[0], 123456); +} + +TEST(IntValueObserver, InstrumentFunctions) +{ + ValueObserver alpha("enabled", "no description", "unitless", true, + &ObserverConstructorCallback); + std::map labels = {{"key", "value"}}; + auto labelkv = trace::KeyValueIterableView{labels}; + + EXPECT_EQ(alpha.GetName(), "enabled"); + EXPECT_EQ(alpha.GetDescription(), "no description"); + EXPECT_EQ(alpha.GetUnits(), "unitless"); + EXPECT_EQ(alpha.IsEnabled(), true); + EXPECT_EQ(alpha.GetKind(), metrics_api::InstrumentKind::ValueObserver); + + alpha.run(); + EXPECT_EQ(alpha.boundAggregators_[KvToString(labelkv)]->get_values()[0], 1); // min +} + +void ObserverCallback(std::shared_ptr> in, + int freq, + const trace::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->observe(i, labels); + } +} + +void NegObserverCallback(std::shared_ptr> in, + int freq, + const trace::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->observe(-i, labels); + } +} + +TEST(IntValueObserver, StressObserve) +{ + std::shared_ptr> alpha(new ValueObserver( + "enabled", "no description", "unitless", true, &ObserverConstructorCallback)); + + std::map labels = {{"key", "value"}}; + std::map labels1 = {{"key1", "value1"}}; + auto labelkv = trace::KeyValueIterableView{labels}; + auto labelkv1 = trace::KeyValueIterableView{labels1}; + + std::thread first(ObserverCallback, alpha, 25, + labelkv); // spawn new threads that call the callback + std::thread second(ObserverCallback, alpha, 50, labelkv); + std::thread third(ObserverCallback, alpha, 25, labelkv1); + std::thread fourth(NegObserverCallback, alpha, 100, labelkv1); // negative values + + first.join(); + second.join(); + third.join(); + fourth.join(); + + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv)]->get_values()[0], 0); // min + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv)]->get_values()[1], 49); // max + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv)]->get_values()[2], 1525); // sum + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv)]->get_values()[3], 75); // count + + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv1)]->get_values()[0], -99); // min + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv1)]->get_values()[1], 24); // max + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv1)]->get_values()[2], -4650); // sum + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv1)]->get_values()[3], 125); // count +} + +void SumObserverCallback(std::shared_ptr> in, + int freq, + const trace::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->observe(1, labels); + } +} + +TEST(IntSumObserver, StressObserve) +{ + std::shared_ptr> alpha( + new SumObserver("test", "none", "unitless", true, &ObserverConstructorCallback)); + + std::map labels = {{"key", "value"}}; + std::map labels1 = {{"key1", "value1"}}; + auto labelkv = trace::KeyValueIterableView{labels}; + auto labelkv1 = trace::KeyValueIterableView{labels1}; + + std::thread first(SumObserverCallback, alpha, 100000, labelkv); + std::thread second(SumObserverCallback, alpha, 100000, labelkv); + std::thread third(SumObserverCallback, alpha, 300000, labelkv1); + + first.join(); + second.join(); + third.join(); + + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv)]->get_values()[0], 200000); + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv1)]->get_values()[0], 300000); +} + +void UpDownSumObserverCallback(std::shared_ptr> in, + int freq, + const trace::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->observe(1, labels); + } +} + +void NegUpDownSumObserverCallback(std::shared_ptr> in, + int freq, + const trace::KeyValueIterable &labels) +{ + for (int i = 0; i < freq; i++) + { + in->observe(-1, labels); + } +} + +TEST(IntUpDownObserver, StressAdd) +{ + std::shared_ptr> alpha( + new UpDownSumObserver("test", "none", "unitless", true, &ObserverConstructorCallback)); + + std::map labels = {{"key", "value"}}; + std::map labels1 = {{"key1", "value1"}}; + auto labelkv = trace::KeyValueIterableView{labels}; + auto labelkv1 = trace::KeyValueIterableView{labels1}; + + std::thread first(UpDownSumObserverCallback, alpha, 123400, + labelkv); // spawn new threads that call the callback + std::thread second(UpDownSumObserverCallback, alpha, 123400, labelkv); + std::thread third(UpDownSumObserverCallback, alpha, 567800, labelkv1); + std::thread fourth(NegUpDownSumObserverCallback, alpha, 123400, labelkv1); // negative values + + first.join(); + second.join(); + third.join(); + fourth.join(); + + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv)]->get_values()[0], 123400 * 2); + EXPECT_EQ(alpha->boundAggregators_[KvToString(labelkv1)]->get_values()[0], 567800 - 123400); +} + TEST(Counter, InstrumentFunctions) { Counter alpha("enabled", "no description", "unitless", true); @@ -262,4 +437,5 @@ TEST(IntValueRecorder, StressRecord) } // namespace metrics } // namespace sdk + OPENTELEMETRY_END_NAMESPACE From cb017bff9d7c2a59cba5fecb21f9887ba67ab9d7 Mon Sep 17 00:00:00 2001 From: Nadia Ciobanu Date: Wed, 5 Aug 2020 13:48:57 -0700 Subject: [PATCH 02/12] Implement links and event attributes in SpanData (#242) --- .../ext/zpages/threadsafe_span_data.h | 3 +- .../opentelemetry/sdk/trace/attribute_utils.h | 113 +++++++++++++++ .../opentelemetry/sdk/trace/span_data.h | 134 +++++++----------- sdk/test/trace/BUILD | 11 ++ sdk/test/trace/attribute_utils_test.cc | 26 ++++ sdk/test/trace/span_data_test.cc | 39 +++++ 6 files changed, 245 insertions(+), 81 deletions(-) create mode 100644 sdk/include/opentelemetry/sdk/trace/attribute_utils.h create mode 100644 sdk/test/trace/attribute_utils_test.cc diff --git a/ext/include/opentelemetry/ext/zpages/threadsafe_span_data.h b/ext/include/opentelemetry/ext/zpages/threadsafe_span_data.h index b6a455dff2..1c7595aae6 100644 --- a/ext/include/opentelemetry/ext/zpages/threadsafe_span_data.h +++ b/ext/include/opentelemetry/ext/zpages/threadsafe_span_data.h @@ -179,8 +179,7 @@ class ThreadsafeSpanData final : public opentelemetry::sdk::trace::Recordable trace_api::KeyValueIterableView>({})) noexcept override { std::lock_guard lock(mutex_); - events_.push_back(SpanDataEvent(std::string(name), timestamp)); - // TODO: handle attributes + events_.push_back(SpanDataEvent(std::string(name), timestamp, attributes)); } ThreadsafeSpanData() {} diff --git a/sdk/include/opentelemetry/sdk/trace/attribute_utils.h b/sdk/include/opentelemetry/sdk/trace/attribute_utils.h new file mode 100644 index 0000000000..bd86c3776a --- /dev/null +++ b/sdk/include/opentelemetry/sdk/trace/attribute_utils.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include "opentelemetry/common/attribute_value.h" +#include "opentelemetry/trace/key_value_iterable_view.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ +/** + * A counterpart to AttributeValue that makes sure a value is owned. This + * replaces all non-owning references with owned copies. + */ +using SpanDataAttributeValue = nostd::variant, + std::vector, + std::vector, + std::vector, + std::vector>; + +/** + * Creates an owned copy (SpanDataAttributeValue) of a non-owning AttributeValue. + */ +struct AttributeConverter +{ + SpanDataAttributeValue operator()(bool v) { return SpanDataAttributeValue(v); } + SpanDataAttributeValue operator()(int v) + { + return SpanDataAttributeValue(static_cast(v)); + } + SpanDataAttributeValue operator()(int64_t v) { return SpanDataAttributeValue(v); } + SpanDataAttributeValue operator()(unsigned int v) + { + return SpanDataAttributeValue(static_cast(v)); + } + SpanDataAttributeValue operator()(uint64_t v) { return SpanDataAttributeValue(v); } + SpanDataAttributeValue operator()(double v) { return SpanDataAttributeValue(v); } + SpanDataAttributeValue operator()(nostd::string_view v) + { + return SpanDataAttributeValue(std::string(v)); + } + SpanDataAttributeValue operator()(nostd::span v) { return convertSpan(v); } + SpanDataAttributeValue operator()(nostd::span v) + { + return convertSpan(v); + } + SpanDataAttributeValue operator()(nostd::span v) + { + return convertSpan(v); + } + SpanDataAttributeValue operator()(nostd::span v) + { + return convertSpan(v); + } + SpanDataAttributeValue operator()(nostd::span v) { return convertSpan(v); } + SpanDataAttributeValue operator()(nostd::span v) { return convertSpan(v); } + SpanDataAttributeValue operator()(nostd::span v) + { + return convertSpan(v); + } + + template + SpanDataAttributeValue convertSpan(nostd::span vals) + { + const std::vector copy(vals.begin(), vals.end()); + return SpanDataAttributeValue(std::move(copy)); + } +}; + +/** + * Class for storing attributes. + */ +class AttributeMap +{ +public: + // Contruct empty attribute map + AttributeMap(){}; + + // Contruct attribute map and populate with attributes + AttributeMap(const opentelemetry::trace::KeyValueIterable &attributes) + { + attributes.ForEachKeyValue([&](nostd::string_view key, + opentelemetry::common::AttributeValue value) noexcept { + SetAttribute(key, value); + return true; + }); + } + + const std::unordered_map &GetAttributes() const noexcept + { + return attributes_; + } + + void SetAttribute(nostd::string_view key, + const opentelemetry::common::AttributeValue &value) noexcept + { + attributes_[std::string(key)] = nostd::visit(converter_, value); + } + +private: + std::unordered_map attributes_; + AttributeConverter converter_; +}; +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/trace/span_data.h b/sdk/include/opentelemetry/sdk/trace/span_data.h index ee87bab3c2..b745a3f33f 100644 --- a/sdk/include/opentelemetry/sdk/trace/span_data.h +++ b/sdk/include/opentelemetry/sdk/trace/span_data.h @@ -6,6 +6,7 @@ #include "opentelemetry/common/attribute_value.h" #include "opentelemetry/core/timestamp.h" #include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/trace/attribute_utils.h" #include "opentelemetry/sdk/trace/recordable.h" #include "opentelemetry/trace/canonical_code.h" #include "opentelemetry/trace/span_id.h" @@ -16,83 +17,16 @@ namespace sdk { namespace trace { -/** - * A counterpart to AttributeValue that makes sure a value is owned. This - * replaces all non-owning references with owned copies. - */ -using SpanDataAttributeValue = nostd::variant, - std::vector, - std::vector, - std::vector, - std::vector>; - -/** - * Creates an owned copy (SpanDataAttributeValue) of a non-owning AttributeValue. - */ -struct AttributeConverter -{ - SpanDataAttributeValue operator()(bool v) { return SpanDataAttributeValue(v); } - SpanDataAttributeValue operator()(int v) - { - return SpanDataAttributeValue(static_cast(v)); - } - SpanDataAttributeValue operator()(int64_t v) { return SpanDataAttributeValue(v); } - SpanDataAttributeValue operator()(unsigned int v) - { - return SpanDataAttributeValue(static_cast(v)); - } - SpanDataAttributeValue operator()(uint64_t v) { return SpanDataAttributeValue(v); } - SpanDataAttributeValue operator()(double v) { return SpanDataAttributeValue(v); } - SpanDataAttributeValue operator()(nostd::string_view v) - { - return SpanDataAttributeValue(std::string(v)); - } - SpanDataAttributeValue operator()(nostd::span v) { return convertSpan(v); } - SpanDataAttributeValue operator()(nostd::span v) - { - return convertSpan(v); - } - SpanDataAttributeValue operator()(nostd::span v) - { - return convertSpan(v); - } - SpanDataAttributeValue operator()(nostd::span v) - { - return convertSpan(v); - } - SpanDataAttributeValue operator()(nostd::span v) { return convertSpan(v); } - SpanDataAttributeValue operator()(nostd::span v) { return convertSpan(v); } - SpanDataAttributeValue operator()(nostd::span v) - { - return convertSpan(v); - } - - template - SpanDataAttributeValue convertSpan(nostd::span vals) - { - std::vector copy; - for (auto &val : vals) - { - copy.push_back(T(val)); - } - - return SpanDataAttributeValue(std::move(copy)); - } -}; - /** * Class for storing events in SpanData. */ class SpanDataEvent { public: - SpanDataEvent(std::string name, core::SystemTimestamp timestamp) - : name_(name), timestamp_(timestamp) + SpanDataEvent(std::string name, + core::SystemTimestamp timestamp, + const trace_api::KeyValueIterable &attributes) + : name_(name), timestamp_(timestamp), attribute_map_(attributes) {} /** @@ -107,9 +41,45 @@ class SpanDataEvent */ core::SystemTimestamp GetTimestamp() const noexcept { return timestamp_; } + /** + * Get the attributes for this event + * @return the attributes for this event + */ + const std::unordered_map &GetAttributes() const noexcept + { + return attribute_map_.GetAttributes(); + } + private: std::string name_; core::SystemTimestamp timestamp_; + AttributeMap attribute_map_; +}; + +/** + * Class for storing links in SpanData. + * TODO: Add getters for trace_id, span_id and trace_state when these are supported by SpanContext + */ +class SpanDataLink +{ +public: + SpanDataLink(opentelemetry::trace::SpanContext span_context, + const trace_api::KeyValueIterable &attributes) + : span_context_(span_context), attribute_map_(attributes) + {} + + /** + * Get the attributes for this link + * @return the attributes for this link + */ + const std::unordered_map &GetAttributes() const noexcept + { + return attribute_map_.GetAttributes(); + } + +private: + opentelemetry::trace::SpanContext span_context_; + AttributeMap attribute_map_; }; /** @@ -172,7 +142,7 @@ class SpanData final : public Recordable */ const std::unordered_map &GetAttributes() const noexcept { - return attributes_; + return attribute_map_.GetAttributes(); } /** @@ -181,6 +151,12 @@ class SpanData final : public Recordable */ const std::vector &GetEvents() const noexcept { return events_; } + /** + * Get the links associated with this span + * @return the links associated with this span + */ + const std::vector &GetLinks() const noexcept { return links_; } + void SetIds(opentelemetry::trace::TraceId trace_id, opentelemetry::trace::SpanId span_id, opentelemetry::trace::SpanId parent_span_id) noexcept override @@ -193,22 +169,22 @@ class SpanData final : public Recordable void SetAttribute(nostd::string_view key, const opentelemetry::common::AttributeValue &value) noexcept override { - attributes_[std::string(key)] = nostd::visit(converter_, value); + attribute_map_.SetAttribute(key, value); } void AddEvent(nostd::string_view name, core::SystemTimestamp timestamp, const trace_api::KeyValueIterable &attributes) noexcept override { - events_.push_back(SpanDataEvent(std::string(name), timestamp)); - // TODO: handle attributes + SpanDataEvent event(std::string(name), timestamp, attributes); + events_.push_back(event); } void AddLink(opentelemetry::trace::SpanContext span_context, const trace_api::KeyValueIterable &attributes) noexcept override { - (void)span_context; - (void)attributes; + SpanDataLink link(span_context, attributes); + links_.push_back(link); } void SetStatus(trace_api::CanonicalCode code, nostd::string_view description) noexcept override @@ -235,9 +211,9 @@ class SpanData final : public Recordable std::string name_; opentelemetry::trace::CanonicalCode status_code_{opentelemetry::trace::CanonicalCode::OK}; std::string status_desc_; - std::unordered_map attributes_; + AttributeMap attribute_map_; std::vector events_; - AttributeConverter converter_; + std::vector links_; }; } // namespace trace } // namespace sdk diff --git a/sdk/test/trace/BUILD b/sdk/test/trace/BUILD index 6089c39aae..f257ff1f1f 100644 --- a/sdk/test/trace/BUILD +++ b/sdk/test/trace/BUILD @@ -100,6 +100,17 @@ cc_test( ], ) +cc_test( + name = "attribute_utils_test", + srcs = [ + "attribute_utils_test.cc", + ], + deps = [ + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + otel_cc_benchmark( name = "sampler_benchmark", srcs = ["sampler_benchmark.cc"], diff --git a/sdk/test/trace/attribute_utils_test.cc b/sdk/test/trace/attribute_utils_test.cc new file mode 100644 index 0000000000..ee6d846fbb --- /dev/null +++ b/sdk/test/trace/attribute_utils_test.cc @@ -0,0 +1,26 @@ +#include "opentelemetry/sdk/trace/attribute_utils.h" + +#include + +TEST(AttributeMapTest, DefaultConstruction) +{ + opentelemetry::sdk::trace::AttributeMap map; + EXPECT_EQ(map.GetAttributes().size(), 0); +} + +TEST(AttributeMapTest, AttributesConstruction) +{ + const int kNumAttributes = 3; + std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3"}; + int values[kNumAttributes] = {15, 24, 37}; + std::map attributes = { + {keys[0], values[0]}, {keys[1], values[1]}, {keys[2], values[2]}}; + + opentelemetry::trace::KeyValueIterableView> iterable(attributes); + opentelemetry::sdk::trace::AttributeMap map(iterable); + + for (int i = 0; i < kNumAttributes; i++) + { + EXPECT_EQ(opentelemetry::nostd::get(map.GetAttributes().at(keys[i])), values[i]); + } +} diff --git a/sdk/test/trace/span_data_test.cc b/sdk/test/trace/span_data_test.cc index 81189991a0..3b35490df9 100644 --- a/sdk/test/trace/span_data_test.cc +++ b/sdk/test/trace/span_data_test.cc @@ -53,3 +53,42 @@ TEST(SpanData, Set) ASSERT_EQ(data.GetEvents().at(0).GetName(), "event1"); ASSERT_EQ(data.GetEvents().at(0).GetTimestamp(), now); } + +TEST(SpanData, EventAttributes) +{ + SpanData data; + const int kNumAttributes = 3; + std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3"}; + int values[kNumAttributes] = {3, 5, 20}; + std::map attributes = { + {keys[0], values[0]}, {keys[1], values[1]}, {keys[2], values[2]}}; + + data.AddEvent("Test Event", std::chrono::system_clock::now(), + opentelemetry::trace::KeyValueIterableView>(attributes)); + + for (int i = 0; i < kNumAttributes; i++) + { + EXPECT_EQ( + opentelemetry::nostd::get(data.GetEvents().at(0).GetAttributes().at(keys[i])), + values[i]); + } +} + +TEST(SpanData, Links) +{ + SpanData data; + const int kNumAttributes = 3; + std::string keys[kNumAttributes] = {"attr1", "attr2", "attr3"}; + int values[kNumAttributes] = {4, 12, 33}; + std::map attributes = { + {keys[0], values[0]}, {keys[1], values[1]}, {keys[2], values[2]}}; + + data.AddLink(opentelemetry::trace::SpanContext(false, false), + opentelemetry::trace::KeyValueIterableView>(attributes)); + + for (int i = 0; i < kNumAttributes; i++) + { + EXPECT_EQ(opentelemetry::nostd::get(data.GetLinks().at(0).GetAttributes().at(keys[i])), + values[i]); + } +} \ No newline at end of file From 6f6978d840cfc5edaa6f8e0d0b81b2bedd67ed0e Mon Sep 17 00:00:00 2001 From: Brandon Kimberly Date: Wed, 5 Aug 2020 21:58:50 -0400 Subject: [PATCH 03/12] Add Meter SDK Class (#212) --- sdk/include/opentelemetry/sdk/metrics/meter.h | 328 +++++--- sdk/src/metrics/CMakeLists.txt | 3 +- sdk/src/metrics/meter.cc | 764 ++++++++++++++++++ sdk/test/metrics/BUILD | 11 + sdk/test/metrics/CMakeLists.txt | 3 +- sdk/test/metrics/meter_test.cc | 289 +++++++ 6 files changed, 1283 insertions(+), 115 deletions(-) create mode 100644 sdk/src/metrics/meter.cc create mode 100644 sdk/test/metrics/meter_test.cc diff --git a/sdk/include/opentelemetry/sdk/metrics/meter.h b/sdk/include/opentelemetry/sdk/metrics/meter.h index ccb96ea804..001866dd99 100644 --- a/sdk/include/opentelemetry/sdk/metrics/meter.h +++ b/sdk/include/opentelemetry/sdk/metrics/meter.h @@ -1,9 +1,14 @@ #pragma once #include "opentelemetry/metrics/meter.h" -#include "opentelemetry/version.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/sdk/metrics/async_instruments.h" +#include "opentelemetry/sdk/metrics/instrument.h" +#include "opentelemetry/sdk/metrics/record.h" +#include "opentelemetry/sdk/metrics/sync_instruments.h" -#include +#include +#include OPENTELEMETRY_BEGIN_NAMESPACE namespace sdk @@ -11,7 +16,7 @@ namespace sdk namespace metrics { namespace metrics_api = opentelemetry::metrics; -class Meter final : public metrics_api::Meter, public std::enable_shared_from_this +class Meter : public metrics_api::Meter { public: explicit Meter(std::string library_name, std::string library_version = "") @@ -20,254 +25,351 @@ class Meter final : public metrics_api::Meter, public std::enable_shared_from_th library_version_ = library_version; } + /** + * Creates a Counter with the passed characteristics and returns a shared_ptr to that Counter. + * + * @param name the name of the new Counter. + * @param description a brief description of what the Counter is used for. + * @param unit the unit of metric values following https://unitsofmeasure.org/ucum.html. + * @param enabled a boolean value that turns on or off the metric instrument. + * @return a shared pointer to the created Counter. + * @throws invalid_argument exception if name is null or does not conform to OTel syntax. + */ nostd::shared_ptr> NewShortCounter(nostd::string_view name, nostd::string_view description, nostd::string_view unit, - const bool enabled) override - { - return nostd::shared_ptr>(nullptr); - } + const bool enabled) override; nostd::shared_ptr> NewIntCounter(nostd::string_view name, nostd::string_view description, nostd::string_view unit, - const bool enabled) override - { - return nostd::shared_ptr>(nullptr); - } + const bool enabled) override; nostd::shared_ptr> NewFloatCounter(nostd::string_view name, nostd::string_view description, nostd::string_view unit, - const bool enabled) override - { - return nostd::shared_ptr>(nullptr); - } + const bool enabled) override; nostd::shared_ptr> NewDoubleCounter(nostd::string_view name, nostd::string_view description, nostd::string_view unit, - const bool enabled) override - { - return nostd::shared_ptr>(nullptr); - } - + const bool enabled) override; + + /** + * Creates an UpDownCounter with the passed characteristics and returns a shared_ptr to that + * UpDownCounter. + * + * @param name the name of the new UpDownCounter. + * @param description a brief description of what the UpDownCounter is used for. + * @param unit the unit of metric values following https://unitsofmeasure.org/ucum.html. + * @param enabled a boolean value that turns on or off the metric instrument. + * @return a shared pointer to the created UpDownCounter. + * @throws invalid_argument exception if name is null or does not conform to OTel syntax. + */ nostd::shared_ptr> NewShortUpDownCounter( nostd::string_view name, nostd::string_view description, nostd::string_view unit, - const bool enabled) override - { - return nostd::shared_ptr>(nullptr); - } + const bool enabled) override; nostd::shared_ptr> NewIntUpDownCounter( nostd::string_view name, nostd::string_view description, nostd::string_view unit, - const bool enabled) override - { - return nostd::shared_ptr>(nullptr); - } + const bool enabled) override; nostd::shared_ptr> NewFloatUpDownCounter( nostd::string_view name, nostd::string_view description, nostd::string_view unit, - const bool enabled) override - { - return nostd::shared_ptr>(nullptr); - } + const bool enabled) override; nostd::shared_ptr> NewDoubleUpDownCounter( nostd::string_view name, nostd::string_view description, nostd::string_view unit, - const bool enabled) override - { - return nostd::shared_ptr>(nullptr); - } - + const bool enabled) override; + + /** + * Creates a ValueRecorder with the passed characteristics and returns a shared_ptr to that + * ValueRecorder. + * + * @param name the name of the new ValueRecorder. + * @param description a brief description of what the ValueRecorder is used for. + * @param unit the unit of metric values following https://unitsofmeasure.org/ucum.html. + * @param enabled a boolean value that turns on or off the metric instrument. + * @return a shared pointer to the created DoubleValueRecorder. + * @throws invalid_argument exception if name is null or does not conform to OTel syntax. + */ nostd::shared_ptr> NewShortValueRecorder( nostd::string_view name, nostd::string_view description, nostd::string_view unit, - const bool enabled) override - { - return nostd::shared_ptr>(nullptr); - } + const bool enabled) override; nostd::shared_ptr> NewIntValueRecorder( nostd::string_view name, nostd::string_view description, nostd::string_view unit, - const bool enabled) override - { - return nostd::shared_ptr>(nullptr); - } + const bool enabled) override; nostd::shared_ptr> NewFloatValueRecorder( nostd::string_view name, nostd::string_view description, nostd::string_view unit, - const bool enabled) override - { - return nostd::shared_ptr>(nullptr); - } + const bool enabled) override; nostd::shared_ptr> NewDoubleValueRecorder( nostd::string_view name, nostd::string_view description, nostd::string_view unit, - const bool enabled) override - { - return nostd::shared_ptr>(nullptr); - } - + const bool enabled) override; + + /** + * Creates a SumObserver with the passed characteristics and returns a shared_ptr to that + * SumObserver. + * + * @param name the name of the new SumObserver. + * @param description a brief description of what the SumObserver is used for. + * @param unit the unit of metric values following https://unitsofmeasure.org/ucum.html. + * @param enabled a boolean value that turns on or off the metric instrument. + * @param callback the function to be observed by the instrument. + * @return a shared pointer to the created SumObserver. + * @throws invalid_argument exception if name is null or does not conform to OTel syntax. + */ nostd::shared_ptr> NewShortSumObserver( nostd::string_view name, nostd::string_view description, nostd::string_view unit, const bool enabled, - void (*callback)(metrics_api::ObserverResult)) override - { - return nostd::shared_ptr>(nullptr); - } + void (*callback)(metrics_api::ObserverResult)) override; nostd::shared_ptr> NewIntSumObserver( nostd::string_view name, nostd::string_view description, nostd::string_view unit, const bool enabled, - void (*callback)(metrics_api::ObserverResult)) override - { - return nostd::shared_ptr>(nullptr); - } + void (*callback)(metrics_api::ObserverResult)) override; nostd::shared_ptr> NewFloatSumObserver( nostd::string_view name, nostd::string_view description, nostd::string_view unit, const bool enabled, - void (*callback)(metrics_api::ObserverResult)) override - { - return nostd::shared_ptr>(nullptr); - } + void (*callback)(metrics_api::ObserverResult)) override; nostd::shared_ptr> NewDoubleSumObserver( nostd::string_view name, nostd::string_view description, nostd::string_view unit, const bool enabled, - void (*callback)(metrics_api::ObserverResult)) override - { - return nostd::shared_ptr>(nullptr); - } - + void (*callback)(metrics_api::ObserverResult)) override; + + /** + * Creates an UpDownSumObserver with the passed characteristics and returns a shared_ptr to + * that UpDowNSumObserver. + * + * @param name the name of the new UpDownSumObserver. + * @param description a brief description of what the UpDownSumObserver is used for. + * @param unit the unit of metric values following https://unitsofmeasure.org/ucum.html. + * @param enabled a boolean value that turns on or off the metric instrument. + * @param callback the function to be observed by the instrument. + * @return a shared pointer to the created UpDownSumObserver. + * @throws invalid_argument exception if name is null or does not conform to OTel syntax. + */ nostd::shared_ptr> NewShortUpDownSumObserver( nostd::string_view name, nostd::string_view description, nostd::string_view unit, const bool enabled, - void (*callback)(metrics_api::ObserverResult)) override - { - return nostd::shared_ptr>(nullptr); - } + void (*callback)(metrics_api::ObserverResult)) override; nostd::shared_ptr> NewIntUpDownSumObserver( nostd::string_view name, nostd::string_view description, nostd::string_view unit, const bool enabled, - void (*callback)(metrics_api::ObserverResult)) override - { - return nostd::shared_ptr>(nullptr); - } + void (*callback)(metrics_api::ObserverResult)) override; nostd::shared_ptr> NewFloatUpDownSumObserver( nostd::string_view name, nostd::string_view description, nostd::string_view unit, const bool enabled, - void (*callback)(metrics_api::ObserverResult)) override - { - return nostd::shared_ptr>(nullptr); - } + void (*callback)(metrics_api::ObserverResult)) override; nostd::shared_ptr> NewDoubleUpDownSumObserver( nostd::string_view name, nostd::string_view description, nostd::string_view unit, const bool enabled, - void (*callback)(metrics_api::ObserverResult)) override - { - return nostd::shared_ptr>(nullptr); - } - + void (*callback)(metrics_api::ObserverResult)) override; + + /** + * Creates a ValueObserver with the passed characteristics and returns a shared_ptr to that + * ValueObserver. + * + * @param name the name of the new ValueObserver. + * @param description a brief description of what the ValueObserver is used for. + * @param unit the unit of metric values following https://unitsofmeasure.org/ucum.html. + * @param enabled a boolean value that turns on or off the metric instrument. + * @param callback the function to be observed by the instrument. + * @return a shared pointer to the created ValueObserver. + * @throws invalid_argument exception if name is null or does not conform to OTel syntax. + */ nostd::shared_ptr> NewShortValueObserver( nostd::string_view name, nostd::string_view description, nostd::string_view unit, const bool enabled, - void (*callback)(metrics_api::ObserverResult)) override - { - return nostd::shared_ptr>(nullptr); - } + void (*callback)(metrics_api::ObserverResult)) override; nostd::shared_ptr> NewIntValueObserver( nostd::string_view name, nostd::string_view description, nostd::string_view unit, const bool enabled, - void (*callback)(metrics_api::ObserverResult)) override - { - return nostd::shared_ptr>(nullptr); - } + void (*callback)(metrics_api::ObserverResult)) override; nostd::shared_ptr> NewFloatValueObserver( nostd::string_view name, nostd::string_view description, nostd::string_view unit, const bool enabled, - void (*callback)(metrics_api::ObserverResult)) override - { - return nostd::shared_ptr>(nullptr); - } + void (*callback)(metrics_api::ObserverResult)) override; nostd::shared_ptr> NewDoubleValueObserver( nostd::string_view name, nostd::string_view description, nostd::string_view unit, const bool enabled, - void (*callback)(metrics_api::ObserverResult)) override - { - return nostd::shared_ptr>(nullptr); - } - + void (*callback)(metrics_api::ObserverResult)) override; + + /** + * Utility method that allows users to atomically record measurements to a set of + * synchronous metric instruments with a common set of labels. + * + * @param labels the set of labels to associate with this recorder. + * @param values a span of pairs where the first element of the pair is a metric instrument + * to record to, and the second element is the value to update that instrument with. + */ void RecordShortBatch(const trace::KeyValueIterable &labels, nostd::span *> instruments, - nostd::span values) noexcept override - {} + nostd::span values) noexcept override; void RecordIntBatch(const trace::KeyValueIterable &labels, nostd::span *> instruments, - nostd::span values) noexcept override - {} + nostd::span values) noexcept override; void RecordFloatBatch(const trace::KeyValueIterable &labels, nostd::span *> instruments, - nostd::span values) noexcept override - {} + nostd::span values) noexcept override; void RecordDoubleBatch(const trace::KeyValueIterable &labels, nostd::span *> instruments, - nostd::span values) noexcept override - {} + nostd::span values) noexcept override; + + /** + * An SDK-only function that checkpoints the aggregators of all instruments created from + * this meter, creates a {@code Record} out of them, and sends them for export. + * + * @return A vector of {@code Records} to be sent to the processor. + */ + std::vector Collect() noexcept; private: + /** + * A private function that creates records from all synchronous instruments created from + * this meter. + * + * @param records A reference to the vector to push the new records to. + */ + void CollectMetrics(std::vector &records); + + /** + * Helper function to collect Records from a single synchronous instrument + * + * @tparam T The integral type of the instrument to collect from. + * @param i A map iterator pointing to the instrument to collect from + * @param records The vector to add the new records to. + */ + template + void CollectSingleSyncInstrument( + typename std::map>>::iterator i, + std::vector &records); + + /** + * A private function that creates records from all asynchronous instruments created from + * this meter. + * + * @param records A reference to the vector to push the new records to. + */ + void CollectObservers(std::vector &records); + + /** + * Helper function to collect Records from a single asynchronous instrument + * + * @tparam T The integral type of the instrument to collect from. + * @param i A map iterator pointing to the instrument to collect from + * @param records The vector to add the new records to. + */ + template + void CollectSingleAsyncInstrument( + typename std::map>>::iterator i, + std::vector &records); + + /** + * Utility function used by the meter that checks if a user-passed name abides by OpenTelemetry + * naming rules. The rules are as follows: + * 1. The name must not be empty. + * 2. The name must not start with a digit, a space, or any punctuation. + * 3. The name must only have the following chaacters: + * All alphanumeric characters, '.', '_' and '-'. + * + * @param name The name to be examined for legality. + * @return A bool representing whether the name is valid by the OpenTelemetry syntax rules. + */ + bool IsValidName(nostd::string_view name); + + /** + * A utility function used by the meter to determine whether an instrument of a specified + * name already exists in this meter. + * + * @param name The name to examine. + * @return A boolean representing whether the name has already been used by this meter. + */ + bool NameAlreadyUsed(nostd::string_view name); + + /* + * All instruments must be stored in a map so the meter can collect on these instruments. + * Additionally, when creating a new instrument, the meter must check if an instrument of the same + * name already exists. + */ + std::map>> short_metrics_; + std::map>> int_metrics_; + std::map>> float_metrics_; + std::map>> + double_metrics_; + + std::map>> + short_observers_; + std::map>> int_observers_; + std::map>> + float_observers_; + std::map>> + double_observers_; + + std::unordered_set names_; + std::string library_name_; std::string library_version_; + + std::mutex metrics_lock_; + std::mutex observers_lock_; }; + } // namespace metrics } // namespace sdk OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/metrics/CMakeLists.txt b/sdk/src/metrics/CMakeLists.txt index 0a5faed745..c147a3e3c2 100644 --- a/sdk/src/metrics/CMakeLists.txt +++ b/sdk/src/metrics/CMakeLists.txt @@ -1 +1,2 @@ -add_library(opentelemetry_metrics meter_provider.cc ungrouped_processor.cc) +add_library(opentelemetry_metrics meter_provider.cc meter.cc + ungrouped_processor.cc) diff --git a/sdk/src/metrics/meter.cc b/sdk/src/metrics/meter.cc new file mode 100644 index 0000000000..f856481a5e --- /dev/null +++ b/sdk/src/metrics/meter.cc @@ -0,0 +1,764 @@ +#include "opentelemetry/sdk/metrics/meter.h" +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ +nostd::shared_ptr> Meter::NewShortCounter( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto counter = new Counter(name, description, unit, enabled); + auto ptr = std::shared_ptr>(counter); + metrics_lock_.lock(); + short_metrics_.insert(std::make_pair(std::string(name), ptr)); + metrics_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewIntCounter(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto counter = new Counter(name, description, unit, enabled); + auto ptr = std::shared_ptr>(counter); + metrics_lock_.lock(); + int_metrics_.insert(std::make_pair(std::string(name), ptr)); + metrics_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewFloatCounter( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto counter = new Counter(name, description, unit, enabled); + auto ptr = std::shared_ptr>(counter); + metrics_lock_.lock(); + float_metrics_.insert(std::make_pair(std::string(name), ptr)); + metrics_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewDoubleCounter( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto counter = new Counter(name, description, unit, enabled); + auto ptr = std::shared_ptr>(counter); + metrics_lock_.lock(); + double_metrics_.insert(std::make_pair(std::string(name), ptr)); + metrics_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewShortUpDownCounter( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto udcounter = new UpDownCounter(name, description, unit, enabled); + auto ptr = std::shared_ptr>(udcounter); + metrics_lock_.lock(); + short_metrics_.insert(std::make_pair(std::string(name), ptr)); + metrics_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewIntUpDownCounter( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto udcounter = new UpDownCounter(name, description, unit, enabled); + auto ptr = std::shared_ptr>(udcounter); + metrics_lock_.lock(); + int_metrics_.insert(std::make_pair(std::string(name), ptr)); + metrics_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewFloatUpDownCounter( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto udcounter = new UpDownCounter(name, description, unit, enabled); + auto ptr = std::shared_ptr>(udcounter); + metrics_lock_.lock(); + float_metrics_.insert(std::make_pair(std::string(name), ptr)); + metrics_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewDoubleUpDownCounter( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto udcounter = new UpDownCounter(name, description, unit, enabled); + auto ptr = std::shared_ptr>(udcounter); + metrics_lock_.lock(); + double_metrics_.insert(std::make_pair(std::string(name), ptr)); + metrics_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewShortValueRecorder( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto recorder = new ValueRecorder(name, description, unit, enabled); + auto ptr = std::shared_ptr>(recorder); + metrics_lock_.lock(); + short_metrics_.insert(std::make_pair(std::string(name), ptr)); + metrics_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewIntValueRecorder( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto recorder = new ValueRecorder(name, description, unit, enabled); + auto ptr = std::shared_ptr>(recorder); + metrics_lock_.lock(); + int_metrics_.insert(std::make_pair(std::string(name), ptr)); + metrics_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewFloatValueRecorder( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto recorder = new ValueRecorder(name, description, unit, enabled); + auto ptr = std::shared_ptr>(recorder); + metrics_lock_.lock(); + float_metrics_.insert(std::make_pair(std::string(name), ptr)); + metrics_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewDoubleValueRecorder( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto recorder = new ValueRecorder(name, description, unit, enabled); + auto ptr = std::shared_ptr>(recorder); + metrics_lock_.lock(); + double_metrics_.insert(std::make_pair(std::string(name), ptr)); + metrics_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewShortSumObserver( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled, + void (*callback)(metrics_api::ObserverResult)) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto sumobs = new SumObserver(name, description, unit, enabled, callback); + auto ptr = std::shared_ptr>(sumobs); + observers_lock_.lock(); + short_observers_.insert(std::make_pair(std::string(name), ptr)); + observers_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewIntSumObserver( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled, + void (*callback)(metrics_api::ObserverResult)) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto sumobs = new SumObserver(name, description, unit, enabled, callback); + auto ptr = std::shared_ptr>(sumobs); + observers_lock_.lock(); + int_observers_.insert(std::make_pair(std::string(name), ptr)); + observers_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewFloatSumObserver( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled, + void (*callback)(metrics_api::ObserverResult)) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto sumobs = new SumObserver(name, description, unit, enabled, callback); + auto ptr = std::shared_ptr>(sumobs); + observers_lock_.lock(); + float_observers_.insert(std::make_pair(std::string(name), ptr)); + observers_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewDoubleSumObserver( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled, + void (*callback)(metrics_api::ObserverResult)) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto sumobs = new SumObserver(name, description, unit, enabled, callback); + auto ptr = std::shared_ptr>(sumobs); + observers_lock_.lock(); + double_observers_.insert(std::make_pair(std::string(name), ptr)); + observers_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewShortUpDownSumObserver( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled, + void (*callback)(metrics_api::ObserverResult)) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto sumobs = new UpDownSumObserver(name, description, unit, enabled, callback); + auto ptr = std::shared_ptr>(sumobs); + observers_lock_.lock(); + short_observers_.insert(std::make_pair(std::string(name), ptr)); + observers_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewIntUpDownSumObserver( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled, + void (*callback)(metrics_api::ObserverResult)) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto sumobs = new UpDownSumObserver(name, description, unit, enabled, callback); + auto ptr = std::shared_ptr>(sumobs); + observers_lock_.lock(); + int_observers_.insert(std::make_pair(std::string(name), ptr)); + observers_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewFloatUpDownSumObserver( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled, + void (*callback)(metrics_api::ObserverResult)) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto sumobs = new UpDownSumObserver(name, description, unit, enabled, callback); + auto ptr = std::shared_ptr>(sumobs); + observers_lock_.lock(); + float_observers_.insert(std::make_pair(std::string(name), ptr)); + observers_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewDoubleUpDownSumObserver( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled, + void (*callback)(metrics_api::ObserverResult)) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto sumobs = new UpDownSumObserver(name, description, unit, enabled, callback); + auto ptr = std::shared_ptr>(sumobs); + observers_lock_.lock(); + double_observers_.insert(std::make_pair(std::string(name), ptr)); + observers_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewShortValueObserver( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled, + void (*callback)(metrics_api::ObserverResult)) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto sumobs = new ValueObserver(name, description, unit, enabled, callback); + auto ptr = std::shared_ptr>(sumobs); + observers_lock_.lock(); + short_observers_.insert(std::make_pair(std::string(name), ptr)); + observers_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewIntValueObserver( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled, + void (*callback)(metrics_api::ObserverResult)) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto sumobs = new ValueObserver(name, description, unit, enabled, callback); + auto ptr = std::shared_ptr>(sumobs); + observers_lock_.lock(); + int_observers_.insert(std::make_pair(std::string(name), ptr)); + observers_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewFloatValueObserver( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled, + void (*callback)(metrics_api::ObserverResult)) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto sumobs = new ValueObserver(name, description, unit, enabled, callback); + auto ptr = std::shared_ptr>(sumobs); + observers_lock_.lock(); + float_observers_.insert(std::make_pair(std::string(name), ptr)); + observers_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +nostd::shared_ptr> Meter::NewDoubleValueObserver( + nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + const bool enabled, + void (*callback)(metrics_api::ObserverResult)) +{ + if (!IsValidName(name) || NameAlreadyUsed(name)) + { +#if __EXCEPTIONS + throw std::invalid_argument("Invalid Name"); +#else + std::terminate(); +#endif + } + auto sumobs = new ValueObserver(name, description, unit, enabled, callback); + auto ptr = std::shared_ptr>(sumobs); + observers_lock_.lock(); + double_observers_.insert(std::make_pair(std::string(name), ptr)); + observers_lock_.unlock(); + return nostd::shared_ptr>(ptr); +} + +void Meter::RecordShortBatch(const trace::KeyValueIterable &labels, + nostd::span *> instruments, + nostd::span values) noexcept +{ + for (int i = 0; i < instruments.size(); ++i) + { + instruments[i]->update(values[i], labels); + } +} + +void Meter::RecordIntBatch(const trace::KeyValueIterable &labels, + nostd::span *> instruments, + nostd::span values) noexcept +{ + for (int i = 0; i < instruments.size(); ++i) + { + instruments[i]->update(values[i], labels); + } +} + +void Meter::RecordFloatBatch(const trace::KeyValueIterable &labels, + nostd::span *> instruments, + nostd::span values) noexcept +{ + for (int i = 0; i < instruments.size(); ++i) + { + instruments[i]->update(values[i], labels); + } +} + +void Meter::RecordDoubleBatch(const trace::KeyValueIterable &labels, + nostd::span *> instruments, + nostd::span values) noexcept +{ + for (int i = 0; i < instruments.size(); ++i) + { + instruments[i]->update(values[i], labels); + } +} + +std::vector Meter::Collect() noexcept +{ + std::vector records; + CollectMetrics(records); + CollectObservers(records); + return records; +} + +// Must cast to sdk::SynchronousInstrument to have access to GetRecords() function +void Meter::CollectMetrics(std::vector &records) +{ + metrics_lock_.lock(); + for (auto i = short_metrics_.begin(); i != short_metrics_.end();) + { + CollectSingleSyncInstrument(i, records); + if (i->second.use_count() == 1) // Evaluates to true if user's shared_ptr has been deleted + { + i = short_metrics_.erase(i); // Remove instrument that is no longer accessible + } + else + { + i++; + } + } + for (auto i = int_metrics_.begin(); i != int_metrics_.end();) + { + CollectSingleSyncInstrument(i, records); + if (i->second.use_count() == 1) // Evaluates to true if user's shared_ptr has been deleted + { + i = int_metrics_.erase(i); // Remove instrument that is no longer accessible + } + else + { + i++; + } + } + for (auto i = float_metrics_.begin(); i != float_metrics_.end();) + { + CollectSingleSyncInstrument(i, records); + if (i->second.use_count() == 1) // Evaluates to true if user's shared_ptr has been deleted + { + i = float_metrics_.erase(i); // Remove instrument that is no longer accessible + } + else + { + i++; + } + } + for (auto i = double_metrics_.begin(); i != double_metrics_.end();) + { + CollectSingleSyncInstrument(i, records); + if (i->second.use_count() == 1) // Evaluates to true if user's shared_ptr has been deleted + { + i = double_metrics_.erase(i); // Remove instrument that is no longer accessible + } + else + { + i++; + } + } + metrics_lock_.unlock(); +} + +template +void Meter::CollectSingleSyncInstrument( + typename std::map>>::iterator + i, + std::vector &records) +{ + if (!i->second->IsEnabled()) + { + i++; + return; + } + auto cast_ptr = std::dynamic_pointer_cast>(i->second); + std::vector new_records = cast_ptr->GetRecords(); + records.insert(records.begin(), new_records.begin(), new_records.end()); +} + +void Meter::CollectObservers(std::vector &records) +{ + observers_lock_.lock(); + for (auto i = short_observers_.begin(); i != short_observers_.end();) + { + CollectSingleAsyncInstrument(i, records); + if (i->second.use_count() == 1) + { + i = short_observers_.erase(i); + } + else + { + i++; + } + } + for (auto i = int_observers_.begin(); i != int_observers_.end();) + { + CollectSingleAsyncInstrument(i, records); + if (i->second.use_count() == 1) + { + i = int_observers_.erase(i); + } + else + { + i++; + } + } + for (auto i = float_observers_.begin(); i != float_observers_.end();) + { + CollectSingleAsyncInstrument(i, records); + if (i->second.use_count() == 1) + { + i = float_observers_.erase(i); + } + else + { + i++; + } + } + for (auto i = double_observers_.begin(); i != double_observers_.end();) + { + CollectSingleAsyncInstrument(i, records); + if (i->second.use_count() == 1) + { + i = double_observers_.erase(i); + } + else + { + i++; + } + } + observers_lock_.unlock(); +} + +template +void Meter::CollectSingleAsyncInstrument( + typename std::map>>::iterator i, + std::vector &records) +{ + if (!i->second->IsEnabled()) + { + i++; + return; + } + auto cast_ptr = std::dynamic_pointer_cast>(i->second); + std::vector new_records = cast_ptr->GetRecords(); + records.insert(records.begin(), new_records.begin(), new_records.end()); +} + +bool Meter::IsValidName(nostd::string_view name) +{ + if (name.empty() || isdigit(name[0]) || isspace(name[0]) || ispunct(name[0])) + return false; + else + { + for (int i = 0; i < name.size(); ++i) + { + if (!isalnum(name[i]) && name[i] != '_' && name[i] != '.' && name[i] != '-') + return false; + } + } + return true; +} + +bool Meter::NameAlreadyUsed(nostd::string_view name) +{ + std::lock_guard lg_metrics(metrics_lock_); + std::lock_guard lg_obsevers(observers_lock_); + if (names_.find(std::string(name)) != names_.end()) + return true; + else + { + names_.insert(std::string(name)); + return false; + } +} +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE \ No newline at end of file diff --git a/sdk/test/metrics/BUILD b/sdk/test/metrics/BUILD index dd49336d59..ee6638a030 100644 --- a/sdk/test/metrics/BUILD +++ b/sdk/test/metrics/BUILD @@ -31,6 +31,17 @@ cc_test( ], ) +cc_test( + name = "meter_test", + srcs = [ + "meter_test.cc", + ], + deps = [ + "//sdk/src/metrics", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "counter_aggregator_test", srcs = [ diff --git a/sdk/test/metrics/CMakeLists.txt b/sdk/test/metrics/CMakeLists.txt index 0acf9af35c..5b6faf60ed 100644 --- a/sdk/test/metrics/CMakeLists.txt +++ b/sdk/test/metrics/CMakeLists.txt @@ -6,7 +6,8 @@ foreach( exact_aggregator_test counter_aggregator_test histogram_aggregator_test - ungrouped_processor_test) + ungrouped_processor_test + meter_test) add_executable(${testname} "${testname}.cc") target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} opentelemetry_metrics) diff --git a/sdk/test/metrics/meter_test.cc b/sdk/test/metrics/meter_test.cc new file mode 100644 index 0000000000..b3cdc0ce39 --- /dev/null +++ b/sdk/test/metrics/meter_test.cc @@ -0,0 +1,289 @@ +#include "opentelemetry/sdk/metrics/meter.h" +#include +#include + +using namespace opentelemetry::sdk::metrics; +namespace metrics_api = opentelemetry::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE + +TEST(Meter, CreateSyncInstruments) +{ + // Test that there are no errors creating synchronous instruments. + Meter m("Test"); + + m.NewShortCounter("Test-short-counter", "For testing", "Unitless", true); + m.NewIntCounter("Test-int-counter", "For testing", "Unitless", true); + m.NewFloatCounter("Test-float-counter", "For testing", "Unitless", true); + m.NewDoubleCounter("Test-double-counter", "For testing", "Unitless", true); + + m.NewShortUpDownCounter("Test-short-ud-counter", "For testing", "Unitless", true); + m.NewIntUpDownCounter("Test-int-ud-counter", "For testing", "Unitless", true); + m.NewFloatUpDownCounter("Test-float-ud-counter", "For testing", "Unitless", true); + m.NewDoubleUpDownCounter("Test-double-ud-counter", "For testing", "Unitless", true); + + m.NewShortValueRecorder("Test-short-recorder", "For testing", "Unitless", true); + m.NewIntValueRecorder("Test-int-recorder", "For testing", "Unitless", true); + m.NewFloatValueRecorder("Test-float-recorder", "For testing", "Unitless", true); + m.NewDoubleValueRecorder("Test-double-recorder", "For testing", "Unitless", true); +} + +// Dummy functions for asynchronous instrument constructors +void ShortCallback(metrics_api::ObserverResult) {} +void IntCallback(metrics_api::ObserverResult) {} +void FloatCallback(metrics_api::ObserverResult) {} +void DoubleCallback(metrics_api::ObserverResult) {} + +TEST(Meter, CreateAsyncInstruments) +{ + // Test that there are no errors when creating asynchronous instruments. + Meter m("Test"); + + m.NewShortSumObserver("Test-short-sum-obs", "For testing", "Unitless", true, &ShortCallback); + m.NewIntSumObserver("Test-int-sum-obs", "For testing", "Unitless", true, &IntCallback); + m.NewFloatSumObserver("Test-float-sum-obs", "For testing", "Unitless", true, &FloatCallback); + m.NewDoubleSumObserver("Test-double-sum-obs", "For testing", "Unitless", true, &DoubleCallback); + + m.NewShortUpDownSumObserver("Test-short-ud-sum-obs", "For testing", "Unitless", true, + &ShortCallback); + m.NewIntUpDownSumObserver("Test-int-ud-sum-obs", "For testing", "Unitless", true, &IntCallback); + m.NewFloatUpDownSumObserver("Test-float-ud-sum-obs", "For testing", "Unitless", true, + &FloatCallback); + m.NewDoubleUpDownSumObserver("Test-double-ud-sum-obs", "For testing", "Unitless", true, + &DoubleCallback); + + m.NewShortValueObserver("Test-short-val-obs", "For testing", "Unitless", true, &ShortCallback); + m.NewIntValueObserver("Test-int-val-obs", "For testing", "Unitless", true, &IntCallback); + m.NewFloatValueObserver("Test-float-val-obs", "For testing", "Unitless", true, &FloatCallback); + m.NewDoubleValueObserver("Test-double-val-obs", "For testing", "Unitless", true, &DoubleCallback); +} + +TEST(Meter, CollectSyncInstruments) +{ + // Verify that the records returned on a call to Collect() are correct for synchronous + // instruments. + Meter m("Test"); + + ASSERT_EQ(m.Collect().size(), 0); + + auto counter = m.NewShortCounter("Test-counter", "For testing", "Unitless", true); + + std::map labels = {{"Key", "Value"}}; + auto labelkv = opentelemetry::trace::KeyValueIterableView{labels}; + + counter->add(1, labelkv); + + std::vector res = m.Collect(); + auto agg_var = res[0].GetAggregator(); + auto agg = opentelemetry::nostd::get<0>(agg_var); + + ASSERT_EQ(agg->get_checkpoint()[0], 1); + + // Now call add() and Collect() again to ensure that the value in the underlying + // aggregator was reset to the default. + + counter->add(10, labelkv); + + res = m.Collect(); + agg_var = res[0].GetAggregator(); + agg = opentelemetry::nostd::get<0>(agg_var); + + ASSERT_EQ(agg->get_checkpoint()[0], 10); +} + +TEST(Meter, CollectDeletedSync) +{ + // Verify that calling Collect() after creating a synchronous instrument and destroying + // the return pointer does not result in a segfault. + + Meter m("Test"); + + ASSERT_EQ(m.Collect().size(), 0); + + std::map labels = {{"Key", "Value"}}; + auto labelkv = opentelemetry::trace::KeyValueIterableView{labels}; + { + auto counter = m.NewShortCounter("Test-counter", "For testing", "Unitless", true); + counter->add(1, labelkv); + } // counter shared_ptr deleted here + + std::vector res = m.Collect(); + auto agg_var = res[0].GetAggregator(); + auto agg = opentelemetry::nostd::get<0>(agg_var); + + ASSERT_EQ(agg->get_checkpoint()[0], 1); +} + +// Dummy function for asynchronous instrument constructors. +void Callback(opentelemetry::metrics::ObserverResult result) +{ + std::map labels = {{"key", "value"}}; + auto labelkv = trace::KeyValueIterableView{labels}; + result.observe(1, labelkv); +} + +TEST(Meter, CollectAsyncInstruments) +{ + // Verify that the records returned on a call to Collect() are correct for asynchronous + // instruments. + Meter m("Test"); + + ASSERT_EQ(m.Collect().size(), 0); + + auto sumobs = + m.NewShortSumObserver("Test-counter", "For testing", "Unitless", true, &ShortCallback); + + std::map labels = {{"Key", "Value"}}; + auto labelkv = opentelemetry::trace::KeyValueIterableView{labels}; + + sumobs->observe(1, labelkv); + + std::vector res = m.Collect(); + auto agg_var = res[0].GetAggregator(); + auto agg = opentelemetry::nostd::get<0>(agg_var); + + ASSERT_EQ(agg->get_checkpoint()[0], 1); + + // Now call observe() and Collect() again to ensure that the value in the underlying + // aggregator was reset to the default. + + sumobs->observe(10, labelkv); + + res = m.Collect(); + agg_var = res[0].GetAggregator(); + agg = opentelemetry::nostd::get<0>(agg_var); + + ASSERT_EQ(agg->get_checkpoint()[0], 10); +} + +TEST(Meter, CollectDeletedAsync) +{ + // Verify that calling Collect() after creating an asynchronous instrument and destroying + // the return pointer does not result in a segfault. + + Meter m("Test"); + + ASSERT_EQ(m.Collect().size(), 0); + + std::map labels = {{"Key", "Value"}}; + auto labelkv = opentelemetry::trace::KeyValueIterableView{labels}; + { + auto sumobs = m.NewShortSumObserver("Test-counter", "For testing", "Unitless", true, &Callback); + sumobs->observe(1, labelkv); + } // sumobs shared_ptr deleted here + + std::vector res = m.Collect(); + auto agg_var = res[0].GetAggregator(); + auto agg = opentelemetry::nostd::get<0>(agg_var); + + ASSERT_EQ(agg->get_checkpoint()[0], 1); +} + +TEST(Meter, RecordBatch) +{ + // This tests that RecordBatch appropriately updates the aggregators of the instruments + // passed to the function. Short, int, float, and double data types are tested. + Meter m("Test"); + + auto scounter = m.NewShortCounter("Test-scounter", "For testing", "Unitless", true); + auto icounter = m.NewIntCounter("Test-icounter", "For testing", "Unitless", true); + auto fcounter = m.NewFloatCounter("Test-fcounter", "For testing", "Unitless", true); + auto dcounter = m.NewDoubleCounter("Test-dcounter", "For testing", "Unitless", true); + + std::map labels = {{"Key", "Value"}}; + auto labelkv = opentelemetry::trace::KeyValueIterableView{labels}; + + metrics_api::SynchronousInstrument *sinstr_arr[] = {scounter.get()}; + short svalues_arr[] = {1}; + + nostd::span *> sinstrs{sinstr_arr}; + nostd::span svalues{svalues_arr}; + + m.RecordShortBatch(labelkv, sinstrs, svalues); + std::vector res = m.Collect(); + auto short_agg_var = res[0].GetAggregator(); + auto short_agg = opentelemetry::nostd::get<0>(short_agg_var); + ASSERT_EQ(short_agg->get_checkpoint()[0], 1); + + metrics_api::SynchronousInstrument *iinstr_arr[] = {icounter.get()}; + int ivalues_arr[] = {1}; + + nostd::span *> iinstrs{iinstr_arr}; + nostd::span ivalues{ivalues_arr}; + + m.RecordIntBatch(labelkv, iinstrs, ivalues); + res = m.Collect(); + auto int_agg_var = res[0].GetAggregator(); + auto int_agg = opentelemetry::nostd::get<1>(int_agg_var); + ASSERT_EQ(int_agg->get_checkpoint()[0], 1); + + metrics_api::SynchronousInstrument *finstr_arr[] = {fcounter.get()}; + float fvalues_arr[] = {1.0}; + + nostd::span *> finstrs{finstr_arr}; + nostd::span fvalues{fvalues_arr}; + + m.RecordFloatBatch(labelkv, finstrs, fvalues); + res = m.Collect(); + auto float_agg_var = res[0].GetAggregator(); + auto float_agg = opentelemetry::nostd::get<2>(float_agg_var); + ASSERT_EQ(float_agg->get_checkpoint()[0], 1.0); + + metrics_api::SynchronousInstrument *dinstr_arr[] = {dcounter.get()}; + double dvalues_arr[] = {1.0}; + + nostd::span *> dinstrs{dinstr_arr}; + nostd::span dvalues{dvalues_arr}; + + m.RecordDoubleBatch(labelkv, dinstrs, dvalues); + res = m.Collect(); + auto double_agg_var = res[0].GetAggregator(); + auto double_agg = opentelemetry::nostd::get<3>(double_agg_var); + ASSERT_EQ(double_agg->get_checkpoint()[0], 1.0); +} + +TEST(Meter, DisableCollectSync) +{ + Meter m("Test"); + std::map labels = {{"Key", "Value"}}; + auto labelkv = opentelemetry::trace::KeyValueIterableView{labels}; + auto c = m.NewShortCounter("c", "", "", false); + c->add(1, labelkv); + ASSERT_EQ(m.Collect().size(), 0); +} + +TEST(Meter, DisableCollectAsync) +{ + Meter m("Test"); + std::map labels = {{"Key", "Value"}}; + auto labelkv = opentelemetry::trace::KeyValueIterableView{labels}; + auto c = m.NewShortValueObserver("c", "", "", false, &ShortCallback); + c->observe(1, labelkv); + ASSERT_EQ(m.Collect().size(), 0); +} + +TEST(MeterStringUtil, IsValid) +{ +#if __EXCEPTIONS + Meter m("Test"); + ASSERT_ANY_THROW(m.NewShortCounter("", "Empty name is invalid", " ", true)); + ASSERT_ANY_THROW(m.NewShortCounter("1a", "Can't begin with a number", " ", true)); + ASSERT_ANY_THROW(m.NewShortCounter(".a", "Can't begin with punctuation", " ", true)); + ASSERT_ANY_THROW(m.NewShortCounter(" a", "Can't begin with space", " ", true)); + ASSERT_ANY_THROW(m.NewShortCounter( + "te^ s=%t", "Only alphanumeric ., -, and _ characters are allowed", " ", true)); +#endif +} + +TEST(MeterStringUtil, AlreadyExists) +{ +#if __EXCEPTIONS + Meter m("Test"); + + m.NewShortCounter("a", "First instance of instrument named 'a'", "", true); + ASSERT_ANY_THROW(m.NewShortCounter("a", "Second (illegal) instrument named 'a'", "", true)); + ASSERT_ANY_THROW(m.NewShortSumObserver("a", "Still illegal even though it is not a short counter", + "", true, &ShortCallback)); +#endif +} +OPENTELEMETRY_END_NAMESPACE From b4d52349c4eeb146fe9fa469617c9c45489b6c2d Mon Sep 17 00:00:00 2001 From: Ankit Bhargava Date: Thu, 6 Aug 2020 00:56:29 -0400 Subject: [PATCH 04/12] Add Metrics Controller (#231) --- .../metrics/aggregator/counter_aggregator.h | 2 + .../metrics/aggregator/histogram_aggregator.h | 4 +- .../metrics/aggregator/sketch_aggregator.h | 2 +- .../opentelemetry/sdk/metrics/controller.h | 150 ++++++++++++++++++ .../opentelemetry/sdk/metrics/instrument.h | 8 +- sdk/test/metrics/BUILD | 12 ++ sdk/test/metrics/controller_test.cc | 45 ++++++ 7 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 sdk/include/opentelemetry/sdk/metrics/controller.h create mode 100644 sdk/test/metrics/controller_test.cc diff --git a/sdk/include/opentelemetry/sdk/metrics/aggregator/counter_aggregator.h b/sdk/include/opentelemetry/sdk/metrics/aggregator/counter_aggregator.h index 0bf44f6421..ebd0201ef0 100644 --- a/sdk/include/opentelemetry/sdk/metrics/aggregator/counter_aggregator.h +++ b/sdk/include/opentelemetry/sdk/metrics/aggregator/counter_aggregator.h @@ -49,8 +49,10 @@ class CounterAggregator final : public Aggregator */ void checkpoint() override { + this->mu_.lock(); this->checkpoint_ = this->values_; this->values_[0] = 0; + this->mu_.unlock(); } /** diff --git a/sdk/include/opentelemetry/sdk/metrics/aggregator/histogram_aggregator.h b/sdk/include/opentelemetry/sdk/metrics/aggregator/histogram_aggregator.h index b2ab83bf22..9d5aafa127 100644 --- a/sdk/include/opentelemetry/sdk/metrics/aggregator/histogram_aggregator.h +++ b/sdk/include/opentelemetry/sdk/metrics/aggregator/histogram_aggregator.h @@ -62,6 +62,7 @@ class HistogramAggregator final : public Aggregator */ void update(T val) override { + this->mu_.lock(); int bucketID = boundaries_.size(); for (size_t i = 0; i < boundaries_.size(); i++) { @@ -76,7 +77,6 @@ class HistogramAggregator final : public Aggregator // 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; @@ -92,11 +92,13 @@ class HistogramAggregator final : public Aggregator */ void checkpoint() override { + this->mu_.lock(); this->checkpoint_ = this->values_; this->values_[0] = 0; this->values_[1] = 0; bucketCounts_ckpt_ = bucketCounts_; std::fill(bucketCounts_.begin(), bucketCounts_.end(), 0); + this->mu_.unlock(); } /** diff --git a/sdk/include/opentelemetry/sdk/metrics/aggregator/sketch_aggregator.h b/sdk/include/opentelemetry/sdk/metrics/aggregator/sketch_aggregator.h index 25f178c9c0..fe0b4737ec 100644 --- a/sdk/include/opentelemetry/sdk/metrics/aggregator/sketch_aggregator.h +++ b/sdk/include/opentelemetry/sdk/metrics/aggregator/sketch_aggregator.h @@ -148,7 +148,6 @@ class SketchAggregator final : public Aggregator */ void merge(SketchAggregator other) { - this->mu_.lock(); if (gamma != other.gamma) { #if __EXCEPTIONS @@ -166,6 +165,7 @@ class SketchAggregator final : public Aggregator #endif } + this->mu_.lock(); this->values_[0] += other.values_[0]; this->values_[1] += other.values_[1]; this->checkpoint_[0] += other.checkpoint_[0]; diff --git a/sdk/include/opentelemetry/sdk/metrics/controller.h b/sdk/include/opentelemetry/sdk/metrics/controller.h new file mode 100644 index 0000000000..0bb789e91c --- /dev/null +++ b/sdk/include/opentelemetry/sdk/metrics/controller.h @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "opentelemetry/exporters/ostream/metrics_exporter.h" +#include "opentelemetry/metrics/instrument.h" +#include "opentelemetry/nostd/unique_ptr.h" +#include "opentelemetry/sdk/metrics/exporter.h" +#include "opentelemetry/sdk/metrics/meter.h" +#include "opentelemetry/sdk/metrics/processor.h" +#include "opentelemetry/sdk/metrics/record.h" +#include "opentelemetry/version.h" + +namespace metrics_api = opentelemetry::metrics; +namespace trace_api = opentelemetry::trace; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +class PushController +{ + +public: + PushController(nostd::shared_ptr meter, + nostd::unique_ptr exporter, + nostd::shared_ptr processor, + double period, + int timeout = 30) + { + meter_ = meter; + exporter_ = std::move(exporter); + processor_ = processor; + timeout_ = (unsigned int)(timeout * 1000000); // convert seconds to microseconds + period_ = (unsigned int)(period * 1000000); + } + + /* + * Used to check if the metrics pipeline is currecntly active + * + * @param none + * @return true when active, false when on standby + */ + bool isActive() { return active_.load(); } + + /* + * Begins the data processing and export pipeline. The function first ensures that the pipeline + * is not already running. If not, it begins and detaches a new thread for the Controller's run + * function which periodically polls the instruments for their data. + * + * @param none + * @return a boolean which is true when the pipeline is successfully started and false when + * already active + */ + bool start() + { + if (!active_.load()) + { + active_ = true; + std::thread runner(&PushController::run, this); + runner.detach(); + return true; + } + return false; + } + + /* + * Ends the processing and export pipeline then exports metrics one last time + * before returning. + * + * @param none + * @return none + */ + void stop() + { + if (active_.load()) + { + active_ = false; + while (running_.load()) + { + std::this_thread::sleep_for( + std::chrono::microseconds(period_ / 100)); // wait until the runner thread concludes + } + tick(); // flush metrics sitting in the processor + } + } + +private: + /* + * Run the tick function at a regular interval. This function + * should be run in its own thread. + * + * Used to wait between collection intervals. + */ + void run() + { + if (!running_.load()) + { + running_ = true; + while (active_.load()) + { + tick(); + std::this_thread::sleep_for(std::chrono::microseconds(period_)); + } + running_ = false; + ; + } + } + + /* + * Tick + * + * Called at regular intervals, this function collects all values from the + * member variable meter_, then sends them to the processor_ for + * processing. After the records have been processed they are sent to the + * exporter_ to be exported. + * + */ + void tick() + { + this->mu_.lock(); + std::vector collected = dynamic_cast(meter_.get())->Collect(); + for (const auto &rec : collected) + { + processor_->process(rec); + } + collected = processor_->CheckpointSelf(); + processor_->FinishedCollection(); + exporter_->Export(collected); + this->mu_.unlock(); + } + + nostd::shared_ptr meter_; + nostd::unique_ptr exporter_; + nostd::shared_ptr processor_; + std::mutex mu_; + std::atomic active_ = ATOMIC_VAR_INIT(false); + std::atomic running_ = ATOMIC_VAR_INIT(false); + unsigned int period_; + unsigned int timeout_; +}; + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/metrics/instrument.h b/sdk/include/opentelemetry/sdk/metrics/instrument.h index 1d06cbec4f..85de5ee701 100644 --- a/sdk/include/opentelemetry/sdk/metrics/instrument.h +++ b/sdk/include/opentelemetry/sdk/metrics/instrument.h @@ -111,7 +111,13 @@ class BoundSynchronousInstrument : public Instrument, * @param none * @return current ref count of the instrument */ - virtual int get_ref() override { return ref_; } + virtual int get_ref() override + { + this->mu_.lock(); + auto ret = ref_; + this->mu_.unlock(); + return ret; + } /** * Records a single synchronous metric event via a call to the aggregator. diff --git a/sdk/test/metrics/BUILD b/sdk/test/metrics/BUILD index ee6638a030..cebe041aff 100644 --- a/sdk/test/metrics/BUILD +++ b/sdk/test/metrics/BUILD @@ -1,3 +1,15 @@ +cc_test( + name = "controller_test", + srcs = [ + "controller_test.cc", + ], + deps = [ + "//exporters/ostream:ostream_metrics_exporter", + "//sdk/src/metrics", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "gauge_aggregator_test", srcs = [ diff --git a/sdk/test/metrics/controller_test.cc b/sdk/test/metrics/controller_test.cc new file mode 100644 index 0000000000..ab1878c9a2 --- /dev/null +++ b/sdk/test/metrics/controller_test.cc @@ -0,0 +1,45 @@ +#include "opentelemetry/sdk/metrics/controller.h" +#include "opentelemetry/sdk/metrics/meter.h" +#include "opentelemetry/sdk/metrics/ungrouped_processor.h" + +#include +#include +#include +// #include + +namespace metrics_api = opentelemetry::metrics; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +TEST(Controller, Constructor) +{ + + std::shared_ptr meter = + std::shared_ptr(new Meter("Test")); + PushController alpha(meter, + std::unique_ptr( + new opentelemetry::exporter::metrics::OStreamMetricsExporter), + std::shared_ptr( + new opentelemetry::sdk::metrics::UngroupedMetricsProcessor(false)), + .05); + + auto instr = meter->NewIntCounter("test", "none", "none", true); + std::map labels = {{"key", "value"}}; + auto labelkv = trace::KeyValueIterableView{labels}; + + alpha.start(); + + for (int i = 0; i < 20; i++) + { + instr->add(i, labelkv); + } + alpha.stop(); +} + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE From e9ccbc25a55debc904ac94ce2fe52c3957092fa5 Mon Sep 17 00:00:00 2001 From: Hudson Humphries Date: Fri, 7 Aug 2020 12:24:39 -0500 Subject: [PATCH 05/12] Add Simple Metrics example (#258) --- examples/metrics_simple/BUILD | 12 ++++ examples/metrics_simple/README.md | 67 +++++++++++++++++++ examples/metrics_simple/main.cc | 107 ++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 examples/metrics_simple/BUILD create mode 100644 examples/metrics_simple/README.md create mode 100644 examples/metrics_simple/main.cc diff --git a/examples/metrics_simple/BUILD b/examples/metrics_simple/BUILD new file mode 100644 index 0000000000..196614a96c --- /dev/null +++ b/examples/metrics_simple/BUILD @@ -0,0 +1,12 @@ +cc_binary( + name = "metrics_simple_example", + srcs = [ + "main.cc", + ], + linkopts = ["-pthread"], + deps = [ + "//api", + "//exporters/ostream:ostream_metrics_exporter", + "//sdk/src/metrics", + ], +) diff --git a/examples/metrics_simple/README.md b/examples/metrics_simple/README.md new file mode 100644 index 0000000000..598b920726 --- /dev/null +++ b/examples/metrics_simple/README.md @@ -0,0 +1,67 @@ +# Simple Metrics Example + +In this example, the application in `main.cc` initializes the metrics pipeline and shows 3 different ways of updating instrument values. Here are more detailed explanations of each part. + +1. Initialize a MeterProvider. We will use this to obtain Meter objects in the future. + +`auto provider = shared_ptr(new MeterProvider);` + +2. Set the MeterProvider as the default instance for the library. This ensures that we will have access to the same MeterProvider across our application. + +`Provider::SetMeterProvider(provider);` + +3. Obtain a meter from this meter provider. Every Meter pointer returned by the MeterProvider points to the same Meter. This means that the Meter will be able to combine metrics captured from different functions without having to constantly pass the Meter around the library. + +`shared_ptr meter = provider→GetMeter("Test");` + +4. Initialize an exporter and processor. In this case, we initialize an OStream Exporter which will print to stdout by default. The Processor is an UngroupedProcessor which doesn’t filter or group captured metrics in any way. The false parameter indicates that this processor will send metric deltas rather than metric cumulatives. + +``` +unique_ptr exporter = unique_ptr(new OStreamMetricsExporter); +shared_ptr processor = shared_ptr(new UngroupedMetricsProcessor(false)); +``` + +5. Pass the meter, exporter, and processor into the controller. Since this is a push controller, a collection interval parameter (in seconds) is also taken. At each collection interval, the controller will request data from all of the instruments in the code and export them. Start the controller to begin the metrics pipeline. + +`metrics_sdk::PushController controller(meter, std::move(exporter), processor, 5);` +`controller.start();` + +6. Instrument code with synchronous and asynchronous instrument. These instruments can be placed in areas of interest to collect metrics and are created by the meter. Synchronous instruments are updated whenever the user desires with a value and label set. Calling add on a counter instrument for example will increase its value. Asynchronous instruments can be updated the same way, but are intended to recieve updates from a callback function. The callback below observes a value of 1. The user never has to call this function as it is automatically called by the controller. + +``` + +// Observer callback function +void SumObserverCallback(metrics_api::ObserverResult result){ + std::map labels = {{"key", "value"}}; + auto labelkv = trace::KeyValueIterableView{labels}; + result.observe(1,labelkv); +} + +// Create new instruments +auto ctr= meter->NewIntCounter("Counter","none", "none", true); +auto obs= meter->NewIntSumObserver("Counter","none", "none", true, &SumObserverCallback); + +// Create a label set which annotates metric values +std::map labels = {{"key", "value"}}; +auto labelkv = trace::KeyValueIterableView{labels}; + +// Capture data from instruments. Note that the asynchronous instrument is updates +// automatically though its callback at the collection interval. Additional measurments +// can be made through calls to its observe function. +ctr->add(5, labelkv); + +``` + +7. Stop the controller once the program finished. This ensures that any metrics inside the pipeline are properly exported. Otherwise, some metrics may be destroyed in cleanup. + +`controller.stop();` + +See [CONTRIBUTING.md](../../CONTRIBUTING.md) for instructions on building and running the example. + +## Additional Documentation + +[API Design](https://github.com/open-o11y/otel-docs/blob/master/cpp-metrics/api-design.md) + +[SDK Design](https://github.com/open-o11y/otel-docs/blob/master/cpp-metrics/sdk-design.md) + +[OStreamExporters Design](https://github.com/open-o11y/otel-docs/blob/master/exporter/ostream/ostream-exporter-design.md) diff --git a/examples/metrics_simple/main.cc b/examples/metrics_simple/main.cc new file mode 100644 index 0000000000..7661daf3c6 --- /dev/null +++ b/examples/metrics_simple/main.cc @@ -0,0 +1,107 @@ +#include "opentelemetry/metrics/provider.h" +#include "opentelemetry/sdk/metrics/controller.h" +#include "opentelemetry/sdk/metrics/meter.h" +#include "opentelemetry/sdk/metrics/meter_provider.h" +#include "opentelemetry/sdk/metrics/ungrouped_processor.h" + +namespace sdkmetrics = opentelemetry::sdk::metrics; +namespace nostd = opentelemetry::nostd; +namespace trace = opentelemetry::trace; + +int main() +{ + // Initialize and set the global MeterProvider + auto provider = nostd::shared_ptr(new sdkmetrics::MeterProvider); + opentelemetry::metrics::Provider::SetMeterProvider(provider); + + // Get the Meter from the MeterProvider + nostd::shared_ptr meter = provider->GetMeter("Test", "0.1.0"); + + // Create the controller with Stateless Metrics Processor + sdkmetrics::PushController ControllerStateless( + meter, + std::unique_ptr( + new opentelemetry::exporter::metrics::OStreamMetricsExporter), + std::shared_ptr( + new opentelemetry::sdk::metrics::UngroupedMetricsProcessor(false)), + .05); + + // Create and instrument + auto intupdowncounter = meter->NewIntUpDownCounter("UpDownCounter", "None", "none", true); + auto intcounter = meter->NewIntCounter("Counter", "none", "none", true); + + // Create a labelset + std::map labels = {{"key", "value"}}; + auto labelkv = trace::KeyValueIterableView{labels}; + + // Create arrays of instrument and values to add to them + metrics_api::SynchronousInstrument *iinstr_arr[] = {intupdowncounter.get(), + intcounter.get()}; + int ivalues_arr[] = {10, 5}; + + // Change the arrays to be nostd::spans + nostd::span *> instrument_span{iinstr_arr}; + nostd::span instrument_values{ivalues_arr}; + + /** + * First way of updating an instrument, RecordBatch. We can update multiple instruments at once by + * using a span of instruments and a span of values. This RecordBatch will update the ith + * instrument with the ith value. + **/ + std::cout << "Example 1" << std::endl; + ControllerStateless.start(); + + // Updating multiple instruments with the same labelset + meter->RecordIntBatch(labelkv, instrument_span, instrument_values); + + ControllerStateless.stop(); + /** + * Second way of updating an instrument, bind then add. In this method the user binds an + *instrument to a labelset Then add to the bounded instrument, then unbind. + **/ + std::cout << "Example 2" << std::endl; + ControllerStateless.start(); + + auto boundintupdowncounter = intupdowncounter->bindUpDownCounter(labelkv); + boundintupdowncounter->add(50); + boundintupdowncounter->unbind(); + + ControllerStateless.stop(); + /** + * The Third and final way is to add a value with a labelset at the same time. This also shows + * The difference between using a Stateless and Stateful Processor + */ + + // Start exporting from the Controller with Stateless Processor + std::cout << "-----" + << " Stateless Processor " + << "-----" << std::endl; + ControllerStateless.start(); + for (int i = 0; i < 20; i++) + { + intupdowncounter->add(i, labelkv); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + ControllerStateless.stop(); + + // Do the same thing for stateful to see the difference + sdkmetrics::PushController ControllerStateful( + meter, + std::unique_ptr( + new opentelemetry::exporter::metrics::OStreamMetricsExporter), + std::shared_ptr( + new opentelemetry::sdk::metrics::UngroupedMetricsProcessor(true)), + .05); + + // Start exporting from the Controller with Stateful Processor + std::cout << "-----" + << " Stateful Processor " + << "-----" << std::endl; + ControllerStateful.start(); + for (int i = 0; i < 20; i++) + { + intupdowncounter->add(i, labelkv); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + ControllerStateful.stop(); +} From b35d589bd7b12bbe89daadfed39b2d39e0e3b89f Mon Sep 17 00:00:00 2001 From: Hudson Humphries Date: Fri, 7 Aug 2020 16:02:32 -0500 Subject: [PATCH 06/12] Metrics example (#266) --- examples/metrics_simple/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/metrics_simple/README.md b/examples/metrics_simple/README.md index 598b920726..7581dfb2f8 100644 --- a/examples/metrics_simple/README.md +++ b/examples/metrics_simple/README.md @@ -65,3 +65,5 @@ See [CONTRIBUTING.md](../../CONTRIBUTING.md) for instructions on building and ru [SDK Design](https://github.com/open-o11y/otel-docs/blob/master/cpp-metrics/sdk-design.md) [OStreamExporters Design](https://github.com/open-o11y/otel-docs/blob/master/exporter/ostream/ostream-exporter-design.md) + +[OpenTelemetry C++ Metrics Overview](https://github.com/open-o11y/docs/blob/master/cpp-metrics/README.md) \ No newline at end of file From a675ec4b4c8a574afc23aa11a8b4bce068dcc4e6 Mon Sep 17 00:00:00 2001 From: Ankit Bhargava Date: Fri, 7 Aug 2020 17:02:55 -0400 Subject: [PATCH 07/12] Add Metrics Design Documents (#264) --- docs/cpp-metrics-api-design.md | 520 +++++++++++++++++++++ docs/cpp-metrics-sdk-design.md | 681 ++++++++++++++++++++++++++++ docs/cpp-ostream-exporter-design.md | 195 ++++++++ 3 files changed, 1396 insertions(+) create mode 100644 docs/cpp-metrics-api-design.md create mode 100644 docs/cpp-metrics-sdk-design.md create mode 100644 docs/cpp-ostream-exporter-design.md diff --git a/docs/cpp-metrics-api-design.md b/docs/cpp-metrics-api-design.md new file mode 100644 index 0000000000..fc58ed7ea7 --- /dev/null +++ b/docs/cpp-metrics-api-design.md @@ -0,0 +1,520 @@ +# Metrics API Design + +This document outlines a proposed implementation of the OpenTelemetry Metrics API in C++. The design conforms to the current versions of the [Metrics API Specification](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/metrics/api.md) though it is currently under development and subject to change. + +The design supports a minimal implementation for the library to be used by an application. However, without the reference SDK or another implementation, no metric data will be collected. + + +## Use Cases + +A *metric* is some raw measurement about a service, captured at run-time. Logically, the moment of capturing one of these measurements is known as a *metric event* which consists not only of the measurement itself, but the time that it was captured as well as contextual annotations which tie it to the event being measured. Users can inject instruments which facilitate the collection of these measurements into their services or systems which may be running locally, in containers, or on distributed platforms. The data collected are then used by monitoring and alerting systems to provide statistical performance data. + +Monitoring and alerting systems commonly use the data provided through metric events, after applying various aggregations and converting into various exposition formats. However, we find that there are many other uses for metric events, such as to record aggregated or raw measurements in tracing and logging systems. For this reason, OpenTelemetry requires a separation of the API from the SDK, so that different SDKs can be configured at run time. + +Various instruments also allow for more optimized capture of certain types of measurements. `Counter` instruments, for example, are monotonic and can therefore be used to capture rate information. Other potential uses for the `Counter` include tracking the number of bytes received, requests completed, accounts created, etc. + +A `ValueRecorder` is commonly used to capture latency measurements. Latency measurements are not additive in the sense that there is little need to know the latency-sum of all processed requests. We use a `ValueRecorder` instrument to capture latency measurements typically because we are interested in knowing mean, median, and other summary statistics about individual events. + +`Observers` are a good choice in situations where a measurement is expensive to compute, such that it would be wasteful to compute on every request. For example, a system call is needed to capture process CPU usage, therefore it should be done periodically, not on each request. + + + +## Design Tenets + +* Reliability + * The Metrics API and SDK should be “reliable,” meaning that metrics data will always be accounted for. It will get back to the user or an error will be logged. Reliability also entails that the end-user application will never be blocked. Error handling will therefore not interfere with the execution of the instrumented program. + * Thread Safety + * As with the Tracer API and SDK, thread safety is not guaranteed on all functions and will be explicitly mentioned in documentation for functions that support concurrent calling. Generally, the goal is to lock functions which change the state of library objects (incrementing the value of a Counter or adding a new Observer for example) or access global memory. As a performance consideration, the library strives to hold locks for as short a duration as possible to avoid lock contention concerns. Calls to create instrumentation may not be thread-safe as this is expected to occur during initialization of the program. +* Scalability + * As OpenTelemetry is a distributed tracing system, it must be able to operate on sizeable systems with predictable overhead growth. A key requirement of this is that the library does not consume unbounded memory resource. +* Security + * Currently security is not a key consideration but may be addressed at a later date. + +## **Meter Interface (`MeterProvider` Class)** + +The singleton global `MeterProvider` can be used to obtain a global Meter by calling `global.GetMeter(name,version)` which calls `GetMeter() `on the initialized global `MeterProvider` + +**Global Meter Provider** + +The API should support a global `MeterProvider`. When a global instance is supported, the API must ensure that `Meter` instances derived from the global `MeterProvider` are initialized after the global SDK implementation is first initialized. + +A `MeterProvider` interface must support a `global.SetMeterProvider(MeterProvider)` function which installs the SDK implementation of the `MeterProvider` into the API + +**Obtaining a Meter from MeterProvider** + +**`GetMeter(name, version)` method must be supported** + + +* Expects 2 string arguments: + * name (required): identifies the instrumentation library. + * version (optional): specifies the version of the instrumenting library (the library injecting OpenTelemetry calls into the code) + +``` +# meter_provider.h +class Provider +{ +public: + /* + * Get Meter Provider + * + * Returns the singleton MeterProvider. By default, a no-op MeterProvider + * is returned. This will never return a nullptr MeterProvider. + * + */ + static nostd::shared_ptr GetMeterProvider(); + { + GetProvider(); + } + + /* + * Set Meter Provider + * + * Changes the singleton MeterProvider. + * + * Arguments: + * newMeterProvider, the MeterProvider instance to be set as the new global + * provider. + */ + static void SetMeterProvider(nostd::shared_ptr newMeterProvider); + +private: + /* + * Get Provider + * + * Returns a no-op MeterProvider. + * + */ + static nostd::shared_ptr &GetProvider() noexcept + { + return DefaultMeterProvider(); + } + +}; +``` + + + +``` +# meter_provider.h +class MeterProvider +{ +public: + /* + * Get Meter + * + * Gets or creates a named meter instance. + * + * Arguments: + * library_name, the name of the instrumenting library. + * library_version, the version of the instrumenting library (OPTIONAL). + */ + nostd::shared_ptr GetMeter(nostd::string_view library_name, + nostd::string_view library_version = "") + +}; +``` + + +Using this MeterProvider, users can obtain new Meters through the GetMeter function. + + +## **Metric Instruments (`Meter` Class)** + +**Metric Events** + +This interface consists of a set of **instrument constructors**, and a **facility for capturing batches of measurements.** + + + +``` +# meter.h +Class Meter { +public: + +/////////////////////////Metric Instrument Constructors//////////////////////////// + + /* + * New Counter + * + * Function that creates and returns a Counter metric instruent + * + * Arguments: + * name, the name of the metric instrument (must conform to the above syntax). + * description, a brief, readable description of the metric instrument. + * unit, the unit of metric values following the UCUM convention + * (https://unitsofmeasure.org/ucum.html). + * enabled, a boolean that turns on or off collection. + * + */ + virtual nostd::shared_ptr> + NewShortCounter(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + nostd::string_view enabled) = 0; + + virtual nostd::shared_ptr> + NewIntCounter(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + nostd::string_view enabled) = 0; + + virtual nostd::shared_ptr> + NewFloatCounter(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + nostd::string_view enabled) = 0; + + virtual nostd::shared_ptr> + NewDoubleCounter(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + nostd::string_view enabled) = 0; + + +//////////////////////////////////////////////////////////////////////////////////// +// // +// Repeat above functions for short, int, float, and // +// double type versions of all 6 metric instruments. // +// // +//////////////////////////////////////////////////////////////////////////////////// + /* + * RecordBatch + * + * Allows the functionality of acting upon multiple metrics with the same set + * of labels with a single API call. Implementations should find bound metric + * instruments that match the key-value pairs in the labels. + * + * Arugments: + * labels, labels associated with all measurements in the batch. + * instruments, a span of pointers to instruments to record to. + * values, a synchronized span of values to record to those instruments. + * + */ + virtual void RecordIntBatch(nostd::KeyValueIterable labels, + nostd::span>> instruments, + nostd::span values) noexcept; + + + /* + * Overloaded RecordBatch function which takes initializer lists of pairs. + * Provided to improve ease-of-use of the BatchRecord function. + * + */ + template::value, int> = 0> + void RecordBatch(std::initializer_list> labels, + std::initializer_list>, + int>> values) + { + // Translate parameters + // return RecordIntBatch(@ translated_params ); + } + +private: + MeterProvider meterProvider_; + InstrumentationInfo instrumentationInfo_; +} +``` + + + +### **Meter API Class Design Considerations** + +According to the specification, both signed integer and floating point value types must be supported. This implementation will use short, int, float, and double types. Different constructors are used for the different metric instruments and even for different value types due to C++ being a strongly typed language. This is similar to Java’s implementation of the meter class. Python gets around this by passing the value type and metric type to a single function called `create_metric`. + + +## **Instrument Types (`Metric` Class)** + +Metric instruments capture raw measurements of designated quantities in instrumented applications. All measurements captured by the Metrics API are associated with the instrument which collected that measurement. These instruments are also templated allowing users to decide which data type to capture. This enhances user control over the memory used by their instrument set and provides greater precision when necessary. + + +### Metric Instrument Data Model + +Each instrument must have enough information to meaningfully attach its measured values with a process in the instrumented application. As such, metric instruments contain the following information: + + +* name (string) — Identifier for this metric instrument. +* description (string) — Short description of what this instrument is capturing. +* value_type (string or enum) — Determines whether the value tracked is an int64 or double. +* meter (Meter) — The Meter instance from which this instrument was derived. +* label_keys (KeyValueIterable) — A nostd class acting as a map from nostd::string_view to nostd::string_view +* enabled (boolean) — Determines whether the instrument is currently collecting data. +* bound_instruments (key value container) — Contains the bound instruments derived from this instrument. + +Metric instruments are created through instances of the `Meter` class and each type of instrument can be described with the following properties: + + +* Synchronicity: A synchronous instrument is called by the user in a distributed [Context](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/context/context.md) (i.e., Span context, Correlation context) and is updated once per request. An asynchronous instrument is called by the SDK once per collection interval and only one value from the interval is kept. +* Additivity: An additive instrument is one that records additive measurements, meaning the final sum of updates is the only useful value. Non-additive instruments should be used when the intent is to capture information about the distribution of values. +* Monotonicity: A monotonic instrument is an additive instrument, where the progression of each sum is non-decreasing. Monotonic instruments are useful for monitoring rate information. + +The following instrument types will be supported: +![Metric Instrument Table](../images/MetricInstrumentsTable.png) + +### Metric Event Data Model + +Each measurement taken by a Metric instrument is a Metric event which must contain the following information: + + +* timestamp (implicit) — System time when measurement was captured. +* instrument definition(strings) — Name of instrument, kind, description, and unit of measure +* label set (key value pairs) — Labels associated with the capture, described further below. +* resources associated with the SDK at startup + +**Label Set** + +A key:value mapping of some kind MUST be supported as annotation each metric event. Labels must be represented the same way throughout the API (i.e. using the same idiomatic data structure) and duplicates are dealt with by taking the last value mapping. + +To maintain ABI stability, we have chosen to implement this as a KeyValueIterable type. However, due to performance concerns, we may convert to a std::string internally. + +**Calling Conventions** + +Metric instruments must support bound instrument calling where the labels for each capture remain the same. After a call to `instrument.Bind(labels)` , all subsequent calls to `instrument.add()` will include the labels implicitly in their capture. + +Direct calling must also be supported. The user can specify labels with the capture rather than binding beforehand by including the labels in the update call: `instrument.Add(x, labels)`. + +MUST support `RecordBatch` calling (where a single set of labels is applied to several metric instruments). + + +``` +# metric.h + +/* + * Enum classes to hold the various types of Metric Instruments and their + * bound complements. + */ +enum class MetricKind +{ + Counter, + UpDownCounter, + ValueRecorder, + SumObserver, + UpDownSumObserver, + ValueObserver, +}; + + /* + * Instrument + * + * Base class for all metric types. + * + * Also known as metric instrument. This is the class that is used to + * represent a metric that is to be continuously recorded and tracked. Each + * metric has a set of bound metrics that are created from the metric. See + * `BoundSychnronousInstrument` for information on bound metric instruments. + */ +class Instrument { +public: + // Note that Instruments should be created using the Meter class. + // Please refer to meter.h for documentation. + Instrument() = default; + + /** + * Base class constructor for all other instrument types. Whether or not + * an instrument is synchronous or bound, it requires a name, description, + * unit, and enabled flag. + * + * @param name is the identifier of the instrumenting library + * @param description explains what the metric captures + * @param unit specified the data type held in the instrument + * @param enabled determins if the metric is currently capturing data + * @return Instrument type with the specified attirbutes + */ + Instrument(nostd::string_view name, nostd::string_view description, nostd::string_view unit, bool enabled); + + // Returns true if the instrument is enabled and collecting data + bool IsEnabled(); + + // Return the instrument name + nostd::string_view GetName(); + + // Return the instrument description + nostd::string_view GetDescription(); + + // Return the insrument's units of measurement + nostd::string_view GetUnits(); + + // Return the kind of the instrument e.g. Counter + InstrumentKind GetKind(); +}; +``` + + + +``` +template +class SynchronousInstrument: public Instrument { +public: + SynchronousInstrument() = default; + + SynchronousInstrument(nostd::string_view name, nostd::string_view description, nostd::string_view unit, bool enabled); + + /** + * 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 always + * calling this method for every operation. + * + * @param labels the set of labels, as key-value pairs. + * @return a Bound Instrument + */ + BoundSynchronousInstrument bind(nostd::KeyValueIterable labels); + + /** + * Records a single synchronous metric event. + * Since this is an unbound synchronous instrument, labels are required in * metric capture calls. + * + * + * @param labels the set of labels, as key-value pairs. + * @param value the numerical representation of the metric being captured + * @return void + */ + void update(T value, nostd::KeyValueIterable labels); //add or record + +}; +template +class BoundSynchronousInstrument: public Instrument { +public: + BoundSynchronousInstrument() = default; + + // Will also call the processor to acquire the correct aggregator for this instrument + BoundSynchronousInstrument(nostd::string_view name, nostd::string_view description, nostd::string_view unit, bool enabled); + + /** + * Frees the resources associated with this Bound Instrument. + * The Metric from which this instrument was created is not impacted. + * + * @param none + * @return void + */ + void unbind(); + + /** + * Records a single synchronous metric event. //Call to aggregator + * Since this is a bound synchronous instrument, labels are notrequired in * metric capture calls. + * + * @param value the numerical representation of the metric being captured + * @return void + */ + void update(T value); //add or record + +}; + +template +class AsynchronousInstrument: public Instrument{ +public: + AsynchronousInstrument(nostd::string_view name, nostd::string_view description, nostd::string_view unit, bool enabled, void (*callback)(ObserverResult)); + + /** + * Captures data by activating the callback function associated with the + * instrument and storing its return value. Callbacks for asychronous + * instruments are defined during construction. + * + * @param none + * @return none + */ + void observe(); + + /** + * Captures data from the stored callback function. The callback itself + * makes use of the instrument's observe function to take capture + * responsibilities out of the user's hands. + * + * @param none + * @return none + */ + void run(); + +private: + + // Callback function which takes a pointer to an Asynchronous instrument (this) + // type which is stored in an observer result type and returns nothing. + // The function calls the instrument's observe method. + void (*callback_)(ObserverResult); +}; +``` + + +The Counter below is an example of one Metric instrument. It is important to note that in the Counter’s add function, it binds the labels to the instrument before calling add, then unbinds. Therefore all interactions with the aggregator take place through bound instruments and by extension, the BaseBoundInstrument Class. + + +``` +template +class BoundCounter: public BoundSynchronousInstrument{ //override bind? +public: + BoundCounter() = default; + BoundCounter(nostd::string_view name, nostd::string_view description, nostd::string_view unit, bool enabled); + /* + * 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 add(T value, nostd::KeyValueIterable labels); + + void unbind(); +}; +template +class Counter: public SynchronousInstrument{ +public: + Counter() = default; + Counter(nostd::string_view name, nostd::string_view description, nostd::string_view unit, bool enabled); + /* + * 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 + */ + + BoundCounter bind(nostd::KeyValueIterable labels); + /* + * Add adds the value to the counter's sum by sending to aggregator. 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, nostd::KeyValueIterable labels); +}; + +template +class ValueObserver: public AsynchronousInstrument{ +public: + /* + * 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 observe(T value, KeyValueIterable &labels) override; +} +``` + + + +``` +// The above Counter and BoundCounter are examples of 1 metric instrument. +// The remaining 5 will also be implemented in a similar fashion. +class UpDownCounter: public SynchronousInstrument; +class BoundUpDownCounter: public BoundSynchronousInstrument; +class ValueRecorder: public SynchronousInstrument; +class BoundValueRecorder: public BoundSynchronousInstrument; +class SumObserver: public AsynchronousInstrument; +class BoundSumObserver: public AsynchronousInstrument; +class UpDownSumObserver: public AsynchronousInstrument; +class BoundUpDownSumObserver: public AsynchronousInstrument; +class ValueObserver: public AsynchronousInstrument; +class BoundValueObserver: public AsynchronousInstrument; +``` + + + +### **Metric Class Design Considerations**: + +OpenTelemetry requires several types of metric instruments with very similar core usage, but slightly different tracking schemes. As such, a base Metric class defines the necessary functions for each instrument leaving the implementation for the specific instrument type. Each instrument then inherits from this base class making the necessary modifications. In order to facilitate efficient aggregation of labeled data, a complementary BoundInstrument class is included which attaches the same set of labels to each capture. Knowing that all data in an instrument has the same labels enhances the efficiency of any post-collection calculations as there is no need for filtering or separation. In the above code examples, a Counter instrument is shown but all 6 mandated by the specification will be supported. + +A base BoundInstrument class also serves as the foundation for more specific bound instruments. It also facilitates the practice of reference counting which can determine when an instrument is unused and can improve memory optimization as inactive bound instruments can be removed for performance. + diff --git a/docs/cpp-metrics-sdk-design.md b/docs/cpp-metrics-sdk-design.md new file mode 100644 index 0000000000..583771bdac --- /dev/null +++ b/docs/cpp-metrics-sdk-design.md @@ -0,0 +1,681 @@ +# Metrics SDK Design + +## Design Tenets + +* Reliability + * The Metrics API and SDK should be “reliable,” meaning that metrics data will always be accounted for. It will get back to the user or an error will be logged. Reliability also entails that the end-user application will never be blocked. Error handling will therefore not interfere with the execution of the instrumented program. The library may “fail fast” during the initialization or configuration path however. + * Thread Safety + * As with the Tracer API and SDK, thread safety is not guaranteed on all functions and will be explicitly mentioned in documentation for functions that support concurrent calling. Generally, the goal is to lock functions which change the state of library objects (incrementing the value of a Counter or adding a new Observer for example) or access global memory. As a performance consideration, the library strives to hold locks for as short a duration as possible to avoid lock contention concerns. Calls to create instrumentation may not be thread-safe as this is expected to occur during initialization of the program. +* Scalability + * As OpenTelemetry is a distributed tracing system, it must be able to operate on sizeable systems with predictable overhead growth. A key requirement of this is that the library does not consume unbounded memory resource. +* Security + * Currently security is not a key consideration but may be addressed at a later date. + +## SDK Data Path Diagram + +![Data Path Diagram](../images/DataPath.png) + +This is the control path our implementation of the metrics SDK will follow. There are five main components: The controller, accumulator, aggregators, processor, and exporter. Each of these components will be further elaborated on. + + +# API Class Implementations + +## **MeterProvider Class** + +The singleton global `MeterProvider` can be used to obtain a global Meter by calling `global.GetMeter(name,version)` which calls `GetMeter() `on the initialized global `MeterProvider`. + +**Global Meter Provider** + +The API should support a global `MeterProvider`. When a global instance is supported, the API must ensure that `Meter` instances derived from the global `MeterProvider` are initialized after the global SDK implementation is first initialized. + +A `MeterProvider` interface must support a `global.SetMeterProvider(MeterProvider)` function which installs the SDK implementation of the `MeterProvider` into the API. + +**Obtaining a Meter from MeterProvider** + +**`GetMeter(name, version)` method must be supported** + + +* Expects 2 string arguments: + * name (required): identifies the instrumentation library. + * version (optional): specifies the version of the instrumenting library (the library injecting OpenTelemetry calls into the code). + +### Implementation + +The Provider class offers static functions to both get and set the global MeterProvider. Once a user sets the MeterProvider, it will replace the default No-op implementation stored as a private variable and persist for the remainder of the program’s execution. This pattern imitates the TracerProvider used in the Tracing portion of this SDK. + + +``` +# meter_provider.cc +class MeterProvider +{ +public: + /* + * Get Meter + * + * Gets or creates a named meter instance. + * + * Arguments: + * library_name, the name of the instrumenting library. + * library_version, the version of the instrumenting library (OPTIONAL). + */ + nostd::shared_ptr GetMeter(nostd::string_view library_name, + nostd::string_view library_version = "") { + + // Create an InstrumentationInfo object which holds the library name and version. + // Call the Meter constructor with InstrumentationInfo. + InstrumentationInfo instrumentationInfo; + instrumentationInfo.SetName(library_name); + if library_version: + instrumentationInfo.SetVersion(library_version); + return nostd::shared_ptr(Meter(this, instrumentationInfo)); + } + +}; +``` + + + +## **Meter Class** + + +**Metric Events** + +Metric instruments are primarily defined by their name. Names MUST conform to the following syntax: + + +* Non-empty string +* case-insensitive +* first character non-numeric, non-space, non-punctuation +* subsequent characters alphanumeric, ‘_’, ‘.’ , and ‘-’ + +`Meter` instances MUST return an error when multiple instruments with the same name are registered + +**The meter implementation will throw an illegal argument exception if the user-passed `name` for a metric instrument either conflicts with the name of another metric instrument created from the same `meter` or violates the `name` syntax outlined above.** + +Each distinctly named Meter (i.e. Meters derived from different instrumentation libraries) MUST create a new namespace for metric instruments descending from them. Thus, the same instrument name can be used in an application provided they come from different Meter instances. + +**In order to achieve this, each instance of the `Meter` class will have a container storing all metric instruments that were created using that meter. This way, metric instruments created from different instantiations of the `Meter` class will never be compared to one another and will never result in an error.** + + +### Implementation + +``` +# meter.h / meter.cc +Class Meter : public API::Meter { +public: + /* + * Constructor for Meter class + * + * Arguments: + * MeterProvider, the MeterProvider object that spawned this Meter. + * InstrumentationInfo, the name of the instrumentation library and, optionally, + * the version. + * + */ + explicit Meter(MeterProvider meterProvider, + InstrumentationInfo instrumentationInfo) { + meterProvider_(meterProvider); + instrumentationInfo_(instrumentationInfo); + } + +/////////////////////////Metric Instrument Constructors//////////////////////////// + + /* + * New Int Counter + * + * Function that creates and returns a Counter metric instrument with value + * type int. + * + * Arguments: + * name, the name of the metric instrument (must conform to the above syntax). + * description, a brief, readable description of the metric instrument. + * unit, the unit of metric values following the UCUM convention + * (https://unitsofmeasure.org/ucum.html). + * + */ + nostd::shared_ptr> NewIntCounter(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + nostd::string_view enabled) { + auto intCounter = Counter(name, description, unit, enabled); + ptr = shared_ptr>(intCounter) + int_metrics_.insert(name, ptr); + return ptr; + } + + /* + * New float Counter + * + * Function that creates and returns a Counter metric instrument with value + * type float. + * + * Arguments: + * name, the name of the metric instrument (must conform to the above syntax). + * description, a brief, readable description of the metric instrument. + * unit, the unit of metric values following the UCUM convention + * (https://unitsofmeasure.org/ucum.html). + * + */ + nostd::unique_ptr> NewFloatCounter(nostd::string_view name, + nostd::string_view description, + nostd::string_view unit, + nostd::string_view enabled) { + auto floatCounter = Counter(name, description, unit, enabled); + ptr = unique_ptr>(floatCounter) + float_metrics_.insert(name, ptr); + return ptr; + } + +//////////////////////////////////////////////////////////////////////////////////// +// // +// Repeat above two functions for all // +// six (five other) metric instruments // +// of types short, int, float, and double. // +// // +//////////////////////////////////////////////////////////////////////////////////// + +private: + /* + * Collect (THREADSAFE) + * + * Checkpoints all metric instruments created from this meter and returns a + * vector of records containing the name, labels, and values of each instrument. + * This function also removes instruments that have not received updates in the + * last collection period. + * + */ + std::vector Collect() { + std::vector records; + metrics_lock_.lock(); + for instr in ALL_metrics_: + if instr is not enabled: + continue + else: + for bound_instr in instr.BoundInstruments: + records.push_back(Record(instr->name, instr->description, + bound_instr->labels, + bound_instr->GetAggregator()->Checkpoint()); + metrics_lock_.unlock(); + return records; + } + + /* + * Record Batch + * + * Allows the functionality of acting upon multiple metrics with the same set + * of labels with a single API call. Implementations should find bound metric + * instruments that match the key-value pairs in the labels. + * + * Arugments: + * labels, labels associated with all measurements in the batch. + * records, a KeyValueIterable containing metric instrument names such as + * "IntCounter" or "DoubleSumObserver" and the corresponding value + * to record for that metric. + * + */ + void RecordBatch(nostd::string_view labels, + nostd::KeyValueIterable values) { + for instr in metrics: + instr.bind(labels) // Bind the instrument to the label set + instr.record(values.GetValue(instr.type)) // Record the corresponding value + // to the instrument. + } + + std::map>> short_metrics_; + std::map>> int_metrics_; + std::map>> float_metrics_; + std::map>> double_metrics_; + + std::map>> short_observers_; + std::map>> int_observers_; + std::map>> float_observers_; + std::map>> double_observers_; + + std::mutex metrics_lock_; + unique_ptr meterProvider_; + InstrumentationInfo instrumentationInfo_; +}; +``` + + + +``` +# record.h +/* + * This class is used to pass checkpointed values from the Meter + * class, to the processor, to the exporter. This class is not + * templated but instead uses variants in order to avoid needing + * to template the exporters. + * + */ +class Record +{ +public: + explicit Record(std::string name, std::string description, + metrics_api::BoundInstrumentKind instrumentKind, + std::string labels, + nostd::variant, Aggregator, Aggregator, Aggregator> agg) + { + name_ = name; + description_ = description; + instrumentKind_ = instrumentKind; + labels_ = labels; + aggregator_ = aggregator; + } + + string GetName() {return name_;} + + string GetDescription() {return description_;} + + BoundInstrumentKind GetInstrumentKind() {return instrumentKind_;} + + string GetLabels() {return labels_;} + + nostd::variant, Aggregator, Aggregator, Aggregator> GetAggregator() {return aggregator_;} + +private: + string name_; + string description_; + BoundInstrumentKind instrumentKind_; + string labels_; + nostd::variant, Aggregator, Aggregator, Aggregator> aggregator_; +}; +``` + + +Metric instruments created from this Meter class will be stored in a map (or another, similar container [needs to be nostd]) called “metrics.” This is identical to the Python implementation and makes sense because the SDK implementation of the `Meter` class should have a function titled `collect_all()` that collects metrics for every instrument created from this meter. In contrast, Java’s implementation has a `MeterSharedState` class that contains a registry (hash map) of all metric instruments spawned from this meter. However, since each `Meter` has its own unique instruments it is easier to store the instruments in the meter itself. + +The SDK implementation of the `Meter` class will contain a function called `collect_all()` that will collect the measurements from each metric stored in the `metrics` container. The implementation of this class acts as the accumulator in the SDK specification. + +**Pros of this implementation:** + + +* Different constructors and overloaded template calls to those constructors for the various metric instruments allows us to forego much of the code duplication involved in supporting various types. +* Storing the metric instruments created from this meter directly in the meter object itself allows us to implement the collect_all method without creating a new class that contains the meter state and instrument registry. + +**Cons of this implementation:** + + +* Different constructors for the different metric instruments means less duplicated code but still a lot. +* Storing the metric instruments in the Meter class means that if we have multiple meters, metric instruments are stored in various objects. Using an instrument registry that maps meters to metric instruments resolves this. However, we have designed our SDK to only support one Meter instance. +* Storing 8 maps in the meter class is costly. However, we believe that this is ok because these maps will only need to be created once, at the instantiation of the meter class. **We believe that these maps will not slow down the pipeline in any meaningful way** + +**The SDK implementation of the `Meter` class will act as the Accumulator mentioned in the SDK specification.** + + +## **Metric Instrument Class** + +Metric instruments capture raw measurements of designated quantities in instrumented applications. All measurements captured by the Metrics API are associated with the instrument which collected that measurement. + + +### Metric Instrument Data Model + +Each instrument must have enough information to meaningfully attach its measured values with a process in the instrumented application. As such, metric instruments contain the following fields + + +* name (string) — Identifier for this metric instrument. +* description (string) — Short description of what this instrument is capturing. +* value_type (string or enum) — Determines whether the value tracked is an int64 or double. +* meter (Meter) — The Meter instance from which this instrument was derived. +* label_keys (KeyValueIterable) — A nostd class acting as map from nostd::string_view to nostd::string_view. +* enabled (boolean) — Determines whether the instrument is currently collecting data. +* bound_instruments (key value container) — Contains the bound instruments derived from this instrument. + +### Metric Event Data Model + +Each measurement taken by a Metric instrument is a Metric event which must contain the following information: + + +* timestamp (implicit) — System time when measurement was captured. +* instrument definition(strings) — Name of instrument, kind, description, and unit of measure +* label set (key value pairs) — Labels associated with the capture, described further below. +* resources associated with the SDK at startup + +**Label Set** + +A key:value mapping of some kind MUST be supported as annotation each metric event. Labels must be represented the same way throughout the API (i.e. using the same idiomatic data structure) and duplicates are dealt with by taking the last value mapping. + +Due to the requirement to maintain ABI stability we have chosen to implement labels as type KeyValueIterable. Though, due to performance reasons, we may convert to std::string internally. + + +### Implementation + +A base Metric class defines the constructor and binding functions which each metric instrument will need. Once an instrument is bound, it becomes a BoundInstrument which extends the BaseBoundInstrument class. The BaseBoundInstrument is what communicates with the aggregator and performs the actual updating of values. An enum helps to organize the numerous types of metric instruments that will be supported. + +The only addition to the SDK metric instrument classes from their API counterparts is the function GetRecords() and the private variables std::map to hold bound instruments and Aggregator to hold the instrument's aggregator. + +**For more information about the structure of metric instruments, refer to the Metrics API Design document.** + + +# Metrics SDK Data Path Implementation + +Note: these requirements come from a specification currently under development. Changes and feedback are in [PR #347](https://github.com/open-telemetry/opentelemetry-specification/pull/347) and the current document is linked [here](https://github.com/open-telemetry/opentelemetry-specification/blob/64bbb0c611d849b90916005d7714fa2a7132d0bf/specification/metrics/sdk.md). + +![Data Path Diagram](../images/DataPath.png) + +## **Accumulator** + +The Accumulator is responsible for computing aggregation over a fixed unit of time. It essentially takes a set of captures and turns them into a quantity that can be collected and used for meaningful analysis by maintaining aggregators for each active instrument and each distinct label set. For example, the aggregator for a counter must combine multiple calls to Add(increment) into a single sum. + +Accumulators MUST support a `Checkpoint()` operation which saves a snapshot of the current state for collection and a `Merge()` operation which combines the state from multiple aggregators into one. + +Calls to the Accumulator's `Collect()` sweep through metric instruments with un-exported updates, checkpoints their aggregators, and submits them to the processor/exporter. This and all other accumulator operations should be extremely efficient and follow the shortest code path possible. + +Design choice: We have chosen to implement the Accumulator as the SDK implementation of the Meter interface shown above. + + +## **Aggregator** + +The term *aggregator* refers to an implementation that can combine multiple metric updates into a single, combined state for a specific function. Aggregators MUST support `Update()`, `Checkpoint()`, and `Merge()` operations. `Update()` is called directly from the Metric instrument in response to a metric event, and may be called concurrently. The `Checkpoint()` operation is called to atomically save a snapshot of the Aggregator. The `Merge()` operation supports dimensionality reduction by combining state from multiple aggregators into a single Aggregator state. + +The SDK must include the Counter aggregator which maintains a sum and the gauge aggregator which maintains last value and timestamp. In addition, the SDK should include MinMaxSumCount, Sketch, Histogram, and Exact aggregators +All operations should be atomic in languages that support them. + + +``` +# aggregator.cc +class Aggregator { +public: + explicit Aggregator() { + self.current_ = nullptr + self.checkpoint_ = nullptr + } + + /* + * Update + * + * Updates the current value with the new value. + * + * Arguments: + * value, the new value to update the instrument with. + * + */ + virtual void Update( value); + + /* + * Checkpoint + * + * Stores a snapshot of the current value. + * + */ + virtual void Checkpoint(); + + /* + * Merge + * + * Combines two aggregator values. Update to most recent time stamp. + * + * Arguments: + * other, the aggregator whose value to merge. + * + */ + virtual void Merge(Aggregator other); + + /* + * Getters for various aggregator specific fields + */ + virtual std::vector get_value() {return current_;} + virtual std::vector get_checkpoint() {return checkpoint_;} + virtual core::SystemTimeStamp get_timestamp() {return last_update_timestamp_;} + +private: + std::vector current_; + std::vector checkpoint_; + core::Systemtimestamp last_update_timestamp_; +}; +``` + + + +``` +# counter_aggregator.cc +template +class CounterAggregator : public Aggregator { +public: + explicit CounterAggregator(): current(0), checkpoint(0), + last_update_timestamp(nullptr){} + + void Update(T value) { + // thread lock + // current += value + this->last_update_timestamp = time_ns() + } + + void Checkpoint() { + // thread lock + this->checkpoint = this->current + this->current = 0 + } + + void Merge(CounterAggregator* other) { + // thread lock + // combine checkpoints + // update timestamp to now + } +}; +``` + + +This Counter is an example Aggregator. We plan on implementing all the Aggregators in the specification: Counter, Gauge, MinMaxSumCount, Sketch, Histogram, and Exact. + + +## **Processor** + +The Processor SHOULD act as the primary source of configuration for exporting metrics from the SDK. The two kinds of configuration are: + + +1. Given a metric instrument, choose which concrete aggregator type to apply for in-process aggregation. +2. Given a metric instrument, choose which dimensions to export by (i.e., the "grouping" function). + +During the collection pass, the Processor receives a full set of check-pointed aggregators corresponding to each (Instrument, LabelSet) pair with an active record managed by the Accumulator. According to its own configuration, the Processor at this point determines which dimensions to aggregate for export; it computes a checkpoint of (possibly) reduced-dimension export records ready for export. It can be thought of as the business logic or processing phase in the pipeline. + + +Change of dimensions: The user-facing metric API allows users to supply LabelSets containing an unlimited number of labels for any metric update. Some metric exporters will restrict the set of labels when exporting metric data, either to reduce cost or because of system-imposed requirements. A *change of dimensions* maps input LabelSets with potentially many labels into a LabelSet with a fixed set of label keys. A change of dimensions eliminates labels with keys not in the output LabelSet and fills in empty values for label keys that are not in the input LabelSet. This can be used for different filtering options, rate limiting, and alternate aggregation schemes. Additionally, it will be used to prevent unbounded memory growth through capping collected data. The community is still deciding exactly how metrics data will be pruned and this document will be updated when a decision is made. + + +The following is a pseudo code implementation of a ‘simple’ Processor. + +Note: Josh MacDonald is working on implementing a [‘basic’ Processor](https://github.com/jmacd/opentelemetry-go/blob/jmacd/mexport/sdk/metric/processor/simple/simple.go) which allows for further Configuration that lines up with the specification in Go. He will be finishing the implementation and updating the specification within the next few weeks. + +Design choice: We recommend that we implement the ‘simple’ Processor first as apart of the MVP and then will also implement the ‘basic’ Processor later on. Josh recommended having both for doing different processes. + + +``` +#processor.cc +class Processor { +public: + + explicit Processor(Bool stateful) { + // stateful determines whether the processor computes deltas or lifetime changes + // in metric values + stateful_ = stateful; + } + + /* + * Process + * + * This function chooses which dimensions to aggregate for export. In the + * reference implementation, the UngroupedProcessor does not process records + * and simple passes them along to the next step. + * + * Arguments: + * record, a record containing data collected from the active Accumulator + * in this data pipeline + */ + void Process(Record record); + + /* + * Checkpoint + * + * This function computes a new (possibly dimension-reduced) checkpoint set of + * all instruments in the meter passed to process. + * + */ + Collection Checkpoint(); + + /* + * Finished Collection + * + * Signals to the intergrator that a collection has been completed and + * can now be sent for export. + * + */ + Error FinishedCollection(); + + /* + * Aggregator For + * + * Returns the correct aggregator type for a given metric instrument. Used in + * the instrument constructor to select which aggregator to use. + * + * Arguments: + * kind, the instrument type asking to be assigned an aggregator + * + */ + Aggregator AggregatorFor(MetricKind kind); + +private: + Bool stateful_; + Batch batch_; +}; +``` + + + +## **Controller** + +Controllers generally are responsible for binding the Accumulator, the Processor, and the Exporter. The controller initiates the collection and export pipeline and manages all the moving parts within it. It also governs the flow of data through the SDK components. Users interface with the controller to begin collection process. + +Once the decision has been made to export, the controller must call `Collect()` on the Accumulator, then read the checkpoint from the Processor, then invoke the Exporter. + +Java’s IntervalMetricReader class acts as a parallel to the controller. The user gets an instance of this class, sets the configuration options (like the tick rate) and then the controller takes care of the collection and exporting of metric data from instruments the user defines. + +There are two different controllers: Push and Pull. The “Push” Controller will establish a periodic timer to regularly collect and export metrics. A “Pull” Controller will await a pull request before initiating metric collection. + +We recommend implementing the PushController as the initial implementation of the Controller. This Controller is the base controller in the specification. We may also implement the PullController if we have the time to do it. + + +``` +#push_controller.cc +class PushController { + + explicit PushController(Meter meter, Exporter exporter, + int period, int timeout) { + meter_ = meter; + exporter_ = exporter(); + period_ = period; + timeout_ = timeout; + provider_ = NewMeterProvider(accumulator); + } + + /* + * Provider + * + * Returns the MeterProvider stored by this controller. + * + */ + MeterProvider Provider { + return this.provider_; + } + /* + * Start (THREAD SAFE) + * + * Begins a ticker that periodically collects and exports metrics with a + * configured interval. + * + */ + void Start(); + + /* + * Stop (THREAD SAFE) + * + * Waits for collection interval to end then exports metrics one last time + * before returning. + * + */ + void Stop(); + + /* + * Run + * + * Used to wait between collection intervals. + * + */ + void run(); + + /* + * Tick (THREAD SAFE) + * + * Called at regular intervals, this function collects all values from the + * member variable meter_, then sends them to the processor_ for + * processing. After the records have been processed they are sent to the + * exporter_ to be exported. + * + */ + void tick(); + +private: + mutex lock_; + Meter meter_; + MeterProvider provider_; + Exporter exporter_; + int period_; + int timeout_; +}; +``` + + + +## **Exporter** + +The exporter SHOULD be called with a checkpoint of finished (possibly dimensionally reduced) export records. Most configuration decisions have been made before the exporter is invoked, including which instruments are enabled, which concrete aggregator types to use, and which dimensions to aggregate by. + +There is very little left for the exporter to do other than format the metric updates into the desired format and send them on their way. + +Design choice: Our idea is to take the simple trace example [StdoutExporter](https://github.com/open-telemetry/opentelemetry-cpp/blob/master/examples/simple/stdout_exporter.h) and add Metric functionality to it. This will allow us to verify that what we are implementing in the API and SDK works as intended. The exporter will go through the different metric instruments and print the value stored in their aggregators to stdout, **for simplicity only Sum is shown here, but all aggregators will be implemented**. + + +``` +# stdout_exporter.cc +class StdoutExporter: public exporter { + /* + * Export + * + * For each checkpoint in checkpointSet, print the value found in their + * aggregator to stdout. Returns an ExportResult enum error code. + * + * Arguments, + * checkpointSet, the set of checkpoints to be exported to stdout. + * + */ + ExportResult Export(CheckpointSet checkpointSet) noexcept; + + /* + * Shutdown + * + * Shuts down the channel and cleans up resources as required. + * + */ + void Shutdown(); +}; +``` + + + +``` +enum class ExportResult { + kSuccess, + kFailure, +}; +``` + + + + + +## Test Strategy / Plan + +Since there is a specification we will be following, we will not have to write out user stories for testing. We will generally only be writing functional unit tests for this project. The C++ Open Telemetry repository uses [Googletest](https://github.com/google/googletest) because it provides test coverage reports, also allows us to easily integrate code coverage tools such as [codecov.io](http://codecov.io/) with the project. A required coverage target of 90% will help to ensure that our code is fully tested. + +An open-source header-only testing framework called [Catch2](https://github.com/catchorg/Catch2) is an alternate option which would satisfy our testing needs. It is easy to use, supports behavior driven development, and does not need to be embedded in the project as source files to operate (unlike Googletest). Code coverage would still be possible using this testing framework but would require us to integrate additional tools. This framework may be preferred as an agnostic replacement for Googletest and is widely used in open source projects. + diff --git a/docs/cpp-ostream-exporter-design.md b/docs/cpp-ostream-exporter-design.md new file mode 100644 index 0000000000..45a0de10b4 --- /dev/null +++ b/docs/cpp-ostream-exporter-design.md @@ -0,0 +1,195 @@ +# OStreamExporter Design + +In strongly typed languages typically there will be 2 separate `Exporter` interfaces, one that accepts spans from a tracer (SpanExporter) and one that accepts metrics (MetricsExporter) + +The exporter SHOULD be called with a checkpoint of finished (possibly dimensionally reduced) export records. Most configuration decisions have been made before the exporter is invoked, including which instruments are enabled, which concrete aggregator types to use, and which dimensions to aggregate by. + +## Use Cases + +Monitoring and alerting systems commonly use the data provided through metric events or tracers, after applying various aggregations and converting into various exposition format. After getting the data, the systems need to be able to see the data. The OStreamExporter will be used here to print data through an ostream, this is seen as a simple exporter where the user doesn’t have the burden of implementing or setting up a protocol dependent exporter. + +The OStreamExporter will also be used as a debugging tool for the Metrics API/SDK and Tracing API/SDK which are currently work in progress projects. This exporter will allow contributors to easily diagnose problems when working on the project. + +## Push vs Pull Exporter + +There are two different versions of exporters: Push and Pull. A Push Exporter pushes the data outwards towards a system, in the case of the OStreamExporter it sends its data into an ostream. A Pull Exporter exposes data to some endpoint for another system to grab the data. + +The OStreamExporter will only be implementing a Push Exporter framework. + +## Design Tenets + +* Reliability + * The Exporter should be reliable; data exported should always be accounted for. The data will either all be successfully exported to the destination server, or in the case of failure, the data is dropped. `Export` will always return failure or success to notify the user of the result. + * Thread Safety + * The OStreamExporter can be called simultaneously, however we do not handle this in the Exporter. Synchronization should be done at a lower level. +* Scalability + * The Exporter must be able to operate on sizeable systems with predictable overhead growth. A key requirement of this is that the library does not consume unbounded memory resource. +* Security + * OStreamExporter should only be used for development and testing purpose, where security and privacy is less a concern as it doesn't communicate to external systems. + +## SpanExporter + +`Span Exporter` defines the interface that protocol-specific exporters must implement so that they can be plugged into OpenTelemetry SDK and support sending of telemetry data. + +The goal of the interface is to minimize burden of implementation for protocol-dependent telemetry exporters. The protocol exporter is expected to be primarily a simple telemetry data encoder and transmitter. + +The SpanExporter is called through the SpanProcessor, which passes finished spans to the configured SpanExporter, as soon as they are finished. The SpanProcessor also shutdown the exporter by the Shutdown function within the SpanProcessor. + +![SDK Data Path](./images/SpanDataPath.png) + +The specification states: exporter must support two functions: Export and Shutdown. + + +### `Export(span of recordables)` + +Exports a batch of telemetry data. Protocol exporters that will implement this function are typically expected to serialize and transmit the data to the destination. + +Export() must not block indefinitely. We can rely on printing to an ostream is reasonably performant and doesn't block. + +The specification states: Any retry logic that is required by the exporter is the responsibility of the exporter. The default SDK SHOULD NOT implement retry logic, as the required logic is likely to depend heavily on the specific protocol and backend the spans are being sent to. + +### `Shutdown()` + +Shuts down the exporter. Called when SDK is shut down. This is an opportunity for exporter to do any cleanup required. + +`Shutdown` should be called only once for each `Exporter` instance. After the call to `Shutdown` subsequent calls to `Export` are not allowed and should return a `Failure` result. + +`Shutdown` should not block indefinitely (e.g. if it attempts to flush the data and the destination is unavailable). Language library authors can decide if they want to make the shutdown timeout configurable. + +In the OStreamExporter there is no cleanup to be done, so there is no need to use the timeout within the `Shutdown` function as it will never be blocking. + +``` +class StreamSpanExporter final : public sdktrace::SpanExporter +{ + +private: + bool isShutdown = false; + +public: + /* + This function should never be called concurrently. + */ + sdktrace::ExportResult Export( + const nostd::span> &spans) noexcept + { + + if(isShutdown) + { + return sdktrace::ExportResult::kFailure; + } + + for (auto &recordable : spans) + { + auto span = std::unique_ptr( + static_cast(recordable.release())); + + if (span != nullptr) + { + char trace_id[32] = {0}; + char span_id[16] = {0}; + char parent_span_id[16] = {0}; + + span->GetTraceId().ToLowerBase16(trace_id); + span->GetSpanId().ToLowerBase16(span_id); + span->GetParentSpanId().ToLowerBase16(parent_span_id); + + std::cout << "{" + << "\n name : " << span->GetName() + << "\n trace_id : " << std::string(trace_id, 32) + << "\n span_id : " << std::string(span_id, 16) + << "\n parent_span_id: " << std::string(parent_span_id, 16) + << "\n start : " << span->GetStartTime().time_since_epoch().count() + << "\n duration : " << span->GetDuration().count() + << "\n description : " << span->GetDescription() + << "\n status : " << span->GetStatus() + << "\n attributes : " << span->GetAttributes() << "\n}" + << "\n"; + } + + } + + return sdktrace::ExportResult::kSuccess; + } + + void Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept + { + isShutdown = true; + } + +}; + + +``` + +## MetricsExporter + +The MetricsExporter has the same requirements as the SpanExporter. The exporter will go through the different metric instruments and send the value stored in their aggregators to an ostream, for simplicity only Counter is shown here, but all aggregators will be implemented. Counter, Gauge, MinMaxSumCount, Sketch, Histogram and Exact Aggregators will be supported. + +Exports a batch of telemetry data. Protocol exporters that will implement this function are typically expected to serialize and transmit the data to the destination. + +![SDK Data Path](./images/DataPath.png) + +### `Export(batch of Records)` + +Export() must not block indefinitely. We can rely on printing to an ostream is reasonably performant and doesn't block. + +The specification states: Any retry logic that is required by the exporter is the responsibility of the exporter. The default SDK SHOULD NOT implement retry logic, as the required logic is likely to depend heavily on the specific protocol and backend the spans are being sent to. + +The MetricsExporter is called through the Controller in the SDK data path. The exporter will either be called on a regular interval in the case of a push controller or through manual calls in the case of a pull controller. + +### `Shutdown()` + +Shutdown() is currently not required for the OStreamMetricsExporter. + +``` +class StreamMetricsExporter final : public sdkmeter::MetricsExporter +{ + +private: + bool isShutdown = false; + +public: + sdkmeter::ExportResult Export( + const Collection batch) noexcept + { + + for (auto &metric : batch) + { + + if (metric != nullptr) + { + + if(metric.AggregationType == CounterAggregator) { + std::cout << "{" + << "\n name : " << metric->GetName() + << "\n labels : " << metric->GetLabels() + << "\n sum : " << metric->Value[0] << "\n}" + } + else if(metric.AggregationType == SketchAggregator) { + // Similarly print data + } + // Other Aggreagators will also be handeled, + // Counter, Gauge, MinMaxSumCount, Sketch, Histogram, + // and Exact Aggreagtors + } + + } + + return sdkmeter::ExportResult::kSuccess; + } + +}; +``` + +## Test Strategy / Plan + +In this project, we will follow the TDD rules, and write enough functional unit tests before implementing production code. We will design exhaustive test cases for normal and abnormal inputs, and tests for edge cases. + +In terms of test framework, as is described in the [Metrics API/SDK design document](https://quip-amazon.com/UBXyAuqRzkIj/Metrics-APISDK-C-Design-Document-External), the OStreamExporter will use [Googletest](https://github.com/google/googletest) framework because it provides test coverage reports, and it also integrate code coverage tools such as [codecov.io](http://codecov.io/) in the project. There are already many reference tests such as MockExporter tests written in GoogleTest, making it a clear choice to stick with it as the testing framework. A required coverage target of 90% will help to ensure that our code is fully tested. + +## Future Features + +* Serialize data to another format (json) + +## Contributors +* Hudson Humphries From 68580863db3213b6ed417be3f9d2371da9573f19 Mon Sep 17 00:00:00 2001 From: Ankit Bhargava Date: Fri, 7 Aug 2020 17:19:10 -0400 Subject: [PATCH 08/12] Controller Thread-Safety Fix (#265) --- .../opentelemetry/sdk/metrics/controller.h | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/sdk/include/opentelemetry/sdk/metrics/controller.h b/sdk/include/opentelemetry/sdk/metrics/controller.h index 0bb789e91c..7c03b1b96f 100644 --- a/sdk/include/opentelemetry/sdk/metrics/controller.h +++ b/sdk/include/opentelemetry/sdk/metrics/controller.h @@ -41,10 +41,12 @@ class PushController } /* - * Used to check if the metrics pipeline is currecntly active + * Used to check if the metrics pipeline is currently active * * @param none - * @return true when active, false when on standby + * @return true when active, false when on standby. This is a best guess estimate + * and the boolean from start() should be used to determine wheher the pipeline + * was initiated successfully. */ bool isActive() { return active_.load(); } @@ -59,9 +61,8 @@ class PushController */ bool start() { - if (!active_.load()) + if (!active_.exchange(true)) { - active_ = true; std::thread runner(&PushController::run, this); runner.detach(); return true; @@ -78,9 +79,8 @@ class PushController */ void stop() { - if (active_.load()) + if (active_.exchange(false)) { - active_ = false; while (running_.load()) { std::this_thread::sleep_for( @@ -99,16 +99,14 @@ class PushController */ void run() { - if (!running_.load()) + if (!running_.exchange(true)) { - running_ = true; while (active_.load()) { tick(); std::this_thread::sleep_for(std::chrono::microseconds(period_)); } running_ = false; - ; } } From 7e0287c046ec19ef76c29544ba31fc89a13aba5a Mon Sep 17 00:00:00 2001 From: Janet Vu Date: Fri, 7 Aug 2020 19:18:02 -0400 Subject: [PATCH 09/12] zPages/TraceZ HTTP Server Files (#244) --- WORKSPACE | 2 +- .../ext/zpages/static/tracez_index.h | 45 +++ .../ext/zpages/static/tracez_script.h | 290 ++++++++++++++++++ .../ext/zpages/static/tracez_style.h | 162 ++++++++++ .../ext/zpages/tracez_http_server.h | 176 +++++++++++ .../ext/zpages/zpages_http_server.h | 117 +++++++ ext/src/zpages/BUILD | 1 + ext/src/zpages/CMakeLists.txt | 6 +- ext/src/zpages/tracez_http_server.cc | 159 ++++++++++ 9 files changed, 955 insertions(+), 3 deletions(-) create mode 100644 ext/include/opentelemetry/ext/zpages/static/tracez_index.h create mode 100644 ext/include/opentelemetry/ext/zpages/static/tracez_script.h create mode 100644 ext/include/opentelemetry/ext/zpages/static/tracez_style.h create mode 100644 ext/include/opentelemetry/ext/zpages/tracez_http_server.h create mode 100644 ext/include/opentelemetry/ext/zpages/zpages_http_server.h create mode 100644 ext/src/zpages/tracez_http_server.cc diff --git a/WORKSPACE b/WORKSPACE index 9aff9c7c29..615d1d27ef 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -82,7 +82,7 @@ http_archive( http_archive( name = "github_nlohmann_json", - build_file = "//third_party:nlohmann_json.BUILD", + build_file = "//third_party/json:nlohmann_json.BUILD", sha256 = "69cc88207ce91347ea530b227ff0776db82dcb8de6704e1a3d74f4841bc651cf", urls = [ "https://github.com/nlohmann/json/releases/download/v3.6.1/include.zip", diff --git a/ext/include/opentelemetry/ext/zpages/static/tracez_index.h b/ext/include/opentelemetry/ext/zpages/static/tracez_index.h new file mode 100644 index 0000000000..86b1fe21c8 --- /dev/null +++ b/ext/include/opentelemetry/ext/zpages/static/tracez_index.h @@ -0,0 +1,45 @@ +#pragma once + +const char tracez_index[] = + "" + "" + "" + " " + " zPages TraceZ" + " " + " " + " " + " " + "

zPages TraceZ

" + " Data last fetched:
" + "
" + "

" + "
" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "
Span NameError SamplesRunningLatency Samples
" + " " + "
" + "
Row count: 0
" + "
" + "
" + "
" + " " + "
" + " " + "
" + "
Row count: 0
" + "
" + " " + ""; diff --git a/ext/include/opentelemetry/ext/zpages/static/tracez_script.h b/ext/include/opentelemetry/ext/zpages/static/tracez_script.h new file mode 100644 index 0000000000..63c9f5de59 --- /dev/null +++ b/ext/include/opentelemetry/ext/zpages/static/tracez_script.h @@ -0,0 +1,290 @@ +#pragma once + +const char tracez_script[] = + "" + "window.onload = () => refreshData();" + "" + "const latencies = [" + " '>0s', '>10µs', '>100µs'," + " '>1ms', '>10ms', '>100ms'," + " '>1s', '>10s', '>100s'," + "];" + "" + "const statusCodeDescriptions = {" + " 'OK': 'The operation completed successfully.'," + " 'CANCELLED': 'The operation was cancelled (typically by the caller).'," + " 'UNKNOWN': `Unknown error. An example of where this error may be returned is if a Status " + "value received" + " from another address space belongs to an error-space that is not known in this " + "address space." + " Also errors raised by APIs that do not return enough error information may be " + "converted to" + " this error.`," + " 'INVALID_ARGUMENT': `Client specified an invalid argument. Note that this differs from " + "FAILED_PRECONDITION." + " INVALID_ARGUMENT indicates arguments that are problematic regardless of the state " + "of the" + " system (e.g., a malformed file name).`," + " 'DEADLINE_EXCEEDED': `Deadline expired before operation could complete. For operations that " + "change the state of the" + " system, this error may be returned even if the operation has completed " + "successfully. For" + " example, a successful response from a server could have been delayed long enough " + "for the" + " deadline to expire.`," + " 'NOT_FOUND' : 'Some requested entity (e.g., file or directory) was not found.'," + " 'ALREADY_EXISTS': 'Some entity that we attempted to create (e.g., file or directory) " + "already exists.'," + " 'PERMISSION_DENIED': `The caller does not have permission to execute the specified " + "operation. PERMISSION_DENIED" + " must not be used for rejections caused by exhausting some resource (use " + "RESOURCE_EXHAUSTED" + " instead for those errors). PERMISSION_DENIED must not be used if the caller cannot " + "be" + " identified (use UNAUTHENTICATED instead for those errors).`," + " 'RESOURCE_EXHAUSTED': `Some resource has been exhausted, perhaps a per-user quota, or " + "perhaps the entire file system" + " is out of space.`," + " 'FAILED_PRECONDITION': `Operation was rejected because the system is not in a state " + "required for the operation's" + " execution. For example, directory to be deleted may be non-empty, an rmdir " + "operation is" + " applied to a non-directory, etc.`," + " 'ABORTED': `The operation was aborted, typically due to a concurrency issue like sequencer " + "check" + " failures, transaction aborts, etc`," + " 'OUT_OF_RANGE': `Operation was attempted past the valid range. E.g., seeking or reading " + "past end of file.`," + " 'UNIMPLEMENTED': 'Operation is not implemented or not supported/enabled in this service.'," + " 'INTERNAL': `Internal errors. Means some invariants expected by underlying system has been " + "broken. If you" + " see one of these errors, something is very broken.`," + " 'UNAVAILABLE': `The service is currently unavailable. This is a most likely a transient " + "condition and may be" + " corrected by retrying with a backoff.`," + " 'DATA_LOSS': 'Unrecoverable data loss or corruption.'," + " 'UNAUTHENTICATED': 'The request does not have valid authentication credentials for the " + "operation.'," + "};" + "" + "const units = {'duration': 'ns'};" + "" + "" + "const details = {'status': statusCodeDescriptions};" + "" + "/* Latency info is returned as an array, so they need to be parsed accordingly */" + "const getLatencyCell = (span, i, h) => `${span[h][i]}`;" + "" + "/* Pretty print a cell with a map */" + "const getKeyValueCell = (span, h) => `" + " ${JSON.stringify(span[h], null, 2)}" + " `;" + "" + "/* Standard categories when checking span details */" + "const idCols = ['spanid', 'parentid', 'traceid'];" + "const detailCols = ['attributes']; /* Columns error, running, and latency spans all share */" + "const dateCols = ['start']; /* Categories to change to date */" + "const numCols = ['duration']; /* Categories to change to num */" + "const clickCols = ['error', 'running']; /* Non-latency clickable cols */" + "const arrayCols = { " + " 'latency': getLatencyCell," + " 'events': getKeyValueCell," + " 'attributes': getKeyValueCell" + "};" + "" + "const base_endpt = '/tracez/get/'; /* For making GET requests */" + "" + "/* Maps table types to their approporiate formatting */" + "const tableFormatting = {" + " 'all': {" + " 'url': base_endpt + 'aggregations'," + " 'html_id': 'overview_table'," + " 'sizing': [" + " {'sz': 'md', 'repeats': 1}," + " {'sz': 'sm', 'repeats': 11}," + " ]," + " 'headings': ['name', ...clickCols, 'latency']," + " 'cell_headings': ['name', ...clickCols, ...latencies]," + " }," + " 'error': {" + " 'url': base_endpt + 'error/'," + " 'html_id': 'name_type_detail_table'," + " 'sizing': [" + " {'sz': 'sm', 'repeats': 5}," + " {'sz': 'sm-md', 'repeats': 1}," + " ]," + " 'headings': [...idCols, ...dateCols, 'status', ...detailCols]," + " 'has_subheading': true," + " }," + " 'running': {" + " 'url': base_endpt + 'running/'," + " 'html_id': 'name_type_detail_table'," + " 'sizing': [" + " {'sz': 'sm', 'repeats': 4}," + " {'sz': 'sm-md', 'repeats': 1}," + " ]," + " 'headings': [...idCols, ...dateCols, ...detailCols]," + " 'has_subheading': true," + " 'status': 'pending'," + " }," + " 'latency': {" + " 'url': base_endpt + 'latency/'," + " 'html_id': 'name_type_detail_table'," + " 'sizing': [" + " {'sz': 'sm', 'repeats': 5}," + " {'sz': 'sm-md', 'repeats': 1}," + " ]," + " 'headings': [...idCols, ...dateCols, ...numCols, ...detailCols]," + " 'has_subheading': true," + " 'status': 'ok'" + " }" + "};" + "const getFormat = group => tableFormatting[group];" + "" + "/* Getters using formatting config variable */" + "const getURL = group => getFormat(group)['url'];" + "const getHeadings = group => getFormat(group)['headings'];" + "const getCellHeadings = group => 'cell_headings' in getFormat(group)" + " ? getFormat(group)['cell_headings'] : getHeadings(group); " + "const getSizing = group => getFormat(group)['sizing'];" + "const getStatus = group => isLatency(group) ? 'ok' : getFormat(group)['status'];" + "const getHTML = group => getFormat(group)['html_id'];" + "" + "const isDate = col => new Set(dateCols).has(col);" + "const isLatency = group => !(new Set(clickCols).has(group)); /* non latency clickable cols, " + "change to include latency? */" + "const isArrayCol = group => (new Set(Object.keys(arrayCols)).has(group));" + "const hasCallback = col => new Set(clickCols).has(col); /* Non-latency cb columns */" + "const hideHeader = h => new Set([...clickCols, 'name']).has(h); /* Headers to not show render " + "twice */" + "const hasSubheading = group => isLatency(group) || 'has_subheading' in getFormat(group); " + "const hasStatus = group => isLatency(group) || 'status' in getFormat(group);" + "" + "const toTitlecase = word => word.charAt(0).toUpperCase() + word.slice(1);" + "const updateLastRefreshStr = () => document.getElementById('lastUpdateTime').innerHTML = new " + "Date().toLocaleString();" + "" + "const getStatusHTML = group => !hasStatus(group) ? ''" + " : `All of these spans have status code ${getStatus(group)}`;" + "" + "/* Returns an HTML string that handlles width formatting" + " for a table group */" + "const tableSizing = group => ''" + " + getSizing(group).map(sz =>" + " (``).repeat(sz['repeats']))" + " .join('')" + " + '';" + "" + "/* Returns an HTML string for a table group's headings," + " hiding headings where needed */" + "const tableHeadings = group => ''" + " + getCellHeadings(group).map(h => `${(hideHeader(h) ? '' : h)}`).join('')" + " + '';" + "" + "/* Returns an HTML string, which represents the formatting for" + " the entire header for a table group. This doesn't change, and" + " includes the width formatting and the actual table headers */" + "const tableHeader = group => tableSizing(group) + tableHeadings(group);" + "" + "/* Return formatting for an array-based value based on its header */" + "const getArrayCells = (h, span) => span[h].length" + " ? (span[h].map((_, i) => arrayCols[h](span, i, h))).join('')" + " : (Object.keys(span[h]).length ? arrayCols[h](span, h) : `${emptyContent()}`);" + "" + "const emptyContent = () => `(not set)`;" + "" + "const dateStr = nanosec => {" + " const mainDate = new Date(nanosec / 1000000).toLocaleString();" + " let lostPrecision = String(nanosec % 1000000);" + " while (lostPrecision.length < 6) lostPrecision = 0 + lostPrecision;" + " const endingLocation = mainDate.indexOf('M') - 2;" + " return `${mainDate.substr(0, " + "endingLocation)}:${lostPrecision}${mainDate.substr(endingLocation)}`;" + "};" + "" + "const detailCell = (h, span) => {" + " const detailKey = Object.keys(details[h])[span[h]];" + " const detailVal = details[h][detailKey];" + " return `" + " ${detailKey}" + " ${detailVal}" + " `;" + "};" + "" + "/* Format cells as needed */" + "const getCellContent = (h, span) => {" + " if (h in details) return detailCell(h, span);" + " else if (h in units) return `${span[h]} ${units[h]}`;" + " else if (span[h] === '') return emptyContent();" + " else if (!isDate(h)) return span[h];" + " return dateStr(span[h]);" + "};" + "" + "/* Create cell based on what header we want to render */" + "const getCell = (h, span) => (isArrayCol(h)) ? getArrayCells(h, span)" + " : `` + `${getCellContent(h, span)}`;" + "" + "/* Returns an HTML string with for a span's aggregated data" + " while columns are ordered according to its table group */" + "const tableRow = (group, span) => ''" + " + getHeadings(group).map(h => getCell(h, span)).join('')" + " + '';" + "" + "/* Returns an HTML string from all the data given as" + " table rows, with each row being a group of spans by name */" + "const tableRows = (group, data) => data.map(span => tableRow(group, span)).join('');" + "" + "/* Overwrite a table on the DOM based on the group given by adding" + " its headers and fetching data for its url */" + "function overwriteTable(group, url_end = '') {" + " fetch(getURL(group) + url_end).then(res => res.json())" + " .then(data => {" + " document.getElementById(getHTML(group))" + " .innerHTML = tableHeader(group)" + " + tableRows(group, data);" + " document.getElementById(getHTML(group) + '_count')" + " .innerHTML = data.length;" + " })" + " .catch(err => console.log(err));" + "};" + "" + "/* Adds a title subheading where needed */" + "function updateSubheading(group, name) {" + " if (hasSubheading(group)) {" + " document.getElementById(getHTML(isLatency(group) ? 'latency' : group) + '_header')" + " .innerHTML = `

${name}" + " ${(isLatency(group) ? `${latencies[group]} Bucket` : toTitlecase(group))}" + " Spans

Showing span details for up to 5 most recent spans. " + " ${getStatusHTML(group)}

`;" + " }" + "};" + "" + "/* Overwrites a table on the DOM based on the group and also" + " changes the subheader, since this a looking at sampled spans */" + "function overwriteDetailedTable(group, name) {" + " if (isLatency(group)) overwriteTable('latency', group + '/' + name);" + " else overwriteTable(group, name);" + " updateSubheading(group, name);" + "};" + "" + "/* Append to a table on the DOM based on the group given */" + "function addToTable(group, url_end = '') {" + " fetch(getURL(group) + url_end).then(res => res.json())" + " .then(data => {" + " const rowsStr = tableRows(group, data);" + " if (!rowsStr) console.log(`No rows added for ${group} table`);" + " document.getElementById(getHTML(group))" + " .getElementsByTagName('tbody')[0]" + " .innerHTML += rowsStr;" + " })" + " .catch(err => console.log(err));" + "};" + "" + "const refreshData = () => {" + " updateLastRefreshStr();" + " overwriteTable('all');" + "};" + ""; diff --git a/ext/include/opentelemetry/ext/zpages/static/tracez_style.h b/ext/include/opentelemetry/ext/zpages/static/tracez_style.h new file mode 100644 index 0000000000..2043dfe5ed --- /dev/null +++ b/ext/include/opentelemetry/ext/zpages/static/tracez_style.h @@ -0,0 +1,162 @@ +#pragma once + +const char tracez_style[] = + "" + "body {" + " color: #252525;" + " text-align: center;" + " font-family: monospace, sans-serif;" + " word-break: break-all;" + " font-size: .9em" + "}" + "" + "code {" + " font-size: 12px;" + "}" + "" + "h1 {" + " margin: 20px 0 0;" + "}" + "" + "table {" + " font-family: monospace, sans-serif;" + " border-collapse: collapse;" + " font-size: 1.05em;" + " width: 100%;" + "}" + "" + ".table-wrap {" + " width: 100%;" + " min-width: 700px;" + " max-width: 2000px;" + " margin: auto;" + "}" + "" + "td, th {" + " word-break: break-word;" + " border: 1px solid #f5f5f5;" + " padding: 6px;" + " text-align: center;" + "}" + "" + "#overview_table th, #overview_table tr {" + " border-top: none;" + "}" + "" + "#headers th, #headers tr {" + " border-bottom: none;" + "}" + "" + "#top-right {" + " text-align: right;" + " position: absolute;" + " top: 10px;" + " right: 10px;" + " text-shadow: .5px .5px .25px #fff;" + "}" + "" + "#top-right button {" + " color: #f6a81c;" + " border: 2px solid #f6a81c;" + " padding: 10px;" + " margin: 10px;" + " text-transform: uppercase;" + " letter-spacing: 1px;" + " background-color: white;" + " border-radius: 10px;" + " font-weight: bold;" + "}" + "" + ".right {" + " text-align: right;" + " padding: 10px;" + "}" + "" + ":hover {" + " transition-duration: .15s;" + "}" + "" + "#top-right button:hover {" + " border-color: #4b5fab;" + " color: #4b5fab;" + " cursor: pointer;" + "}" + "" + "tr:nth-child(even) {" + " background-color: #eee;" + "}" + "" + ".click {" + " text-decoration: underline dotted #4b5fab;" + "}" + "" + "tr:hover, td:hover, .click:hover {" + " color: white;" + " background-color: #4b5fab;" + "}" + "" + "tr:hover {" + " background-color: #4b5fabcb;" + "}" + "" + "th {" + " background-color: white;" + " color: #252525;" + "}" + "" + ".click:hover {" + " cursor: pointer;" + " color: #f6a81ccc;" + "}" + "" + ".empty {" + " color: #999;" + "}" + "" + ".sm {" + " width: 7%;" + "}" + "" + ".sm-md {" + " width: 13%;" + "}" + "" + ".md {" + " width: 23%;" + "}" + "" + ".lg {" + " width: 63%;" + "}" + "" + "img {" + " width: 50%;" + " max-width: 500px;" + "}" + "" + ".subhead-name {" + " color: #4b5fab;" + "}" + "" + ".has-tooltip {" + " text-decoration: underline dotted #f6a81c;" + "}" + "" + ".has-tooltip:hover .tooltip {" + " display: block;" + "}" + "" + ".tooltip {" + " display: none;" + " position: absolute;" + "}" + "" + ".tooltip, .tooltip:hover {" + " background: #ffffffd9;" + " padding: 10px;" + " z-index: 1000;" + " color: #252525 !important;" + " border-radius: 10px;" + " margin: 3px 20px 0 0;" + "}" + ""; diff --git a/ext/include/opentelemetry/ext/zpages/tracez_http_server.h b/ext/include/opentelemetry/ext/zpages/tracez_http_server.h new file mode 100644 index 0000000000..9b0717508b --- /dev/null +++ b/ext/include/opentelemetry/ext/zpages/tracez_http_server.h @@ -0,0 +1,176 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "nlohmann/json.hpp" +#include "opentelemetry/ext/zpages/static/tracez_index.h" +#include "opentelemetry/ext/zpages/static/tracez_script.h" +#include "opentelemetry/ext/zpages/static/tracez_style.h" +#include "opentelemetry/ext/zpages/tracez_data_aggregator.h" +#include "opentelemetry/ext/zpages/zpages_http_server.h" + +#define HAVE_HTTP_DEBUG +#define HAVE_CONSOLE_LOG + +using json = nlohmann::json; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace ext +{ +namespace zpages +{ + +class TracezHttpServer : public opentelemetry::ext::zpages::zPagesHttpServer +{ +public: + /** + * Construct the server by initializing the endpoint for querying TraceZ aggregation data and + * files, along with taking ownership of the aggregator whose data is used to send data to the + * frontend + * @param aggregator is the TraceZ Data Aggregator, which calculates aggregation info + * @param host is the host where the TraceZ webpages will be displayed, default being localhost + * @param port is the port where the TraceZ webpages will be displayed, default being 30000 + */ + TracezHttpServer(std::unique_ptr &&aggregator, + const std::string &host = "localhost", + int port = 30000) + : opentelemetry::ext::zpages::zPagesHttpServer("/tracez", host, port), + data_aggregator_(std::move(aggregator)) + { + InitializeTracezEndpoint(*this); + }; + +private: + /** + * Set the HTTP server to use the "Serve" callback to send the appropriate data when queried + * @param server, which should be an instance of this object + */ + void InitializeTracezEndpoint(TracezHttpServer &server) { server[endpoint_] = Serve; } + + /** + * Updates the stored aggregation data (aggregations_) using the data aggregator + */ + void UpdateAggregations(); + + /** + * First updates the stored aggregations, then translates that data from a C++ map to + * a JSON object + * @returns JSON object of collected spans bucket counts by name + */ + json GetAggregations(); + + /** + * Using the stored aggregations, finds the span group with the right name and returns + * its running span data as a JSON, only grabbing the fields needed for the frontend + * @param name of the span group whose running data we want + * @returns JSON representing running span data with the passed in name + */ + json GetRunningSpansJSON(const std::string &name); + + /** + * Using the stored aggregations, finds the span group with the right name and returns + * its error span data as a JSON, only grabbing the fields needed for the frontend + * @param name of the span group whose running data we want + * @returns JSON representing eoor span data with the passed in name + */ + json GetErrorSpansJSON(const std::string &name); + + /** + * Using the stored aggregations, finds the span group with the right name and bucket index + * returning its latency span data as a JSON, only grabbing the fields needed for the frontend + * @param name of the span group whose latency data we want + * @param index of which latency bucket to grab from + * @returns JSON representing bucket span data with the passed in name and latency range + */ + json GetLatencySpansJSON(const std::string &name, int latency_range_index); + + /** + * Returns attributes, which have varied types, from a span data to convert into JSON + * @param sample current span data, whose attributes we want to extract + * @returns JSON representing attributes for a given threadsafe span data + */ + json GetAttributesJSON(const opentelemetry::ext::zpages::ThreadsafeSpanData &sample); + + /** + * Sets the response object with the TraceZ aggregation data based on the request endpoint + * @param req is the HTTP request, which we use to figure out the response to send + * @param resp is the HTTP response we want to send to the frontend, either webpage or TraceZ + * aggregation data + */ + HTTP_SERVER_NS::HttpRequestCallback Serve{ + [&](HTTP_SERVER_NS::HttpRequest const &req, HTTP_SERVER_NS::HttpResponse &resp) { + std::string query = GetQuery(req.uri); // tracez + + if (StartsWith(query, "get")) + { + resp.headers[HTTP_SERVER_NS::CONTENT_TYPE] = "application/json"; + query = GetAfterSlash(query); + if (StartsWith(query, "latency")) + { + auto queried_latency_name = GetAfterSlash(query); + auto queried_latency_index = std::stoi(GetBeforeSlash(queried_latency_name)); + auto queried_name = GetAfterSlash(queried_latency_name); + ReplaceHtmlChars(queried_name); + resp.body = GetLatencySpansJSON(queried_name, queried_latency_index).dump(); + } + else + { + auto queried_name = GetAfterSlash(query); + ReplaceHtmlChars(queried_name); + if (StartsWith(query, "aggregations")) + { + resp.body = GetAggregations().dump(); + } + else if (StartsWith(query, "running")) + { + resp.body = GetRunningSpansJSON(queried_name).dump(); + } + else if (StartsWith(query, "error")) + { + resp.body = GetErrorSpansJSON(queried_name).dump(); + } + else + { + resp.body = json::array().dump(); + } + } + } + else + { + if (StartsWith(query, "script.js")) + { + resp.headers[HTTP_SERVER_NS::CONTENT_TYPE] = "text/javascript"; + resp.body = tracez_script; + } + else if (StartsWith(query, "style.css")) + { + resp.headers[HTTP_SERVER_NS::CONTENT_TYPE] = "text/css"; + resp.body = tracez_style; + } + else if (query.empty() || query == "/tracez" || StartsWith(query, "index.html")) + { + resp.headers[HTTP_SERVER_NS::CONTENT_TYPE] = "text/html"; + resp.body = tracez_index; + } + else + { + resp.headers[HTTP_SERVER_NS::CONTENT_TYPE] = "text/plain"; + resp.body = "Invalid query: " + query; + } + } + + return 200; + }}; + + std::map aggregated_data_; + std::unique_ptr data_aggregator_; +}; + +} // namespace zpages +} // namespace ext +OPENTELEMETRY_END_NAMESPACE diff --git a/ext/include/opentelemetry/ext/zpages/zpages_http_server.h b/ext/include/opentelemetry/ext/zpages/zpages_http_server.h new file mode 100644 index 0000000000..fb43ed0642 --- /dev/null +++ b/ext/include/opentelemetry/ext/zpages/zpages_http_server.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/ext/http/server/http_server.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace ext +{ +namespace zpages +{ + +class zPagesHttpServer : public HTTP_SERVER_NS::HttpServer +{ +protected: + /** + * Construct the server by initializing the endpoint for serving static files, which show up on + * the web if the user is on the given host:port. Static files can be seen relative to the folder + * where the executable was ran. + * @param host is the host where the TraceZ webpages will be displayed + * @param port is the port where the TraceZ webpages will be displayed + * @param endpoint is where this specific zPage will server files + */ + zPagesHttpServer(const std::string &endpoint, + const std::string &host = "127.0.0.1", + int port = 52620) + : HttpServer(), endpoint_(endpoint) + { + std::ostringstream os; + os << host << ":" << port; + setServerName(os.str()); + addListeningPort(port); + }; + + /** + * Helper function that returns query information by isolating it from the base endpoint + * @param uri is the full query + */ + std::string GetQuery(const std::string &uri) + { + if (endpoint_.length() + 1 > uri.length()) + return uri; + return uri.substr(endpoint_.length() + 1); + } + + /** + * Helper that returns whether a str starts with pre + * @param str is the string we're checking + * @param pre is the prefix we're checking against + */ + bool StartsWith(const std::string &str, const std::string &pre) { return str.rfind(pre, 0) == 0; } + + /** + * Helper that returns the remaining string after the leftmost backslash + * @param str is the string we're extracting from + */ + std::string GetAfterSlash(const std::string &str) + { + std::size_t backslash = str.find("/"); + if (backslash == std::string::npos || backslash == str.length()) + return ""; + return str.substr(backslash + 1); + } + + /** + * Helper that returns the remaining string after the leftmost backslash + * @param str is the string we're extracting from + */ + std::string GetBeforeSlash(const std::string &str) + { + std::size_t backslash = str.find("/"); + if (backslash == std::string::npos || backslash == str.length()) + return str; + return str.substr(0, backslash); + } + + /** + * Helper that replaces all occurances a string within a string + * @param str string to modify + * @param search substring to remove from str + * @param replacement string to replace search with whenever search is found + */ + void ReplaceAll(std::string &str, const std::string &search, const std::string &replacement) + { + size_t idx = str.find(search, 0); + while (idx != std::string::npos) + { + str.replace(idx, search.length(), replacement); + idx = str.find(search, idx); + } + } + + /** + * Helper that replaces all special HTML/address base encoded characters + * into what they're originally supposed to be + * @param str string to conduct replacements for + */ + void ReplaceHtmlChars(std::string &str) + { + for (const auto &replace_pair : replace_map_) + { + ReplaceAll(str, replace_pair.first, replace_pair.second); + } + } + + const std::string endpoint_; + const std::unordered_map replace_map_ = {{"%20", " "}}; +}; + +} // namespace zpages +} // namespace ext +OPENTELEMETRY_END_NAMESPACE diff --git a/ext/src/zpages/BUILD b/ext/src/zpages/BUILD index a59d525897..ab9f3507b8 100644 --- a/ext/src/zpages/BUILD +++ b/ext/src/zpages/BUILD @@ -23,5 +23,6 @@ cc_library( "//api", "//ext:headers", "//sdk:headers", + "@github_nlohmann_json//:json", ], ) diff --git a/ext/src/zpages/CMakeLists.txt b/ext/src/zpages/CMakeLists.txt index 851ccd927d..dae0249bf4 100644 --- a/ext/src/zpages/CMakeLists.txt +++ b/ext/src/zpages/CMakeLists.txt @@ -1,8 +1,10 @@ add_library( opentelemetry_zpages - tracez_processor.cc tracez_data_aggregator.cc + tracez_processor.cc + tracez_data_aggregator.cc ../../include/opentelemetry/ext/zpages/tracez_processor.h - ../../include/opentelemetry/ext/zpages/tracez_data_aggregator.h) + ../../include/opentelemetry/ext/zpages/tracez_data_aggregator.h + ../../include/opentelemetry/ext/zpages/tracez_http_server.h) target_include_directories(opentelemetry_zpages PUBLIC ../../include) diff --git a/ext/src/zpages/tracez_http_server.cc b/ext/src/zpages/tracez_http_server.cc new file mode 100644 index 0000000000..ebef512c1f --- /dev/null +++ b/ext/src/zpages/tracez_http_server.cc @@ -0,0 +1,159 @@ +#include "opentelemetry/ext/zpages/tracez_http_server.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace ext +{ +namespace zpages +{ + +json TracezHttpServer::GetAggregations() +{ + aggregated_data_ = data_aggregator_->GetAggregatedTracezData(); + auto counts_json = json::array(); + + for (const auto &aggregation_group : aggregated_data_) + { + const auto &buckets = aggregation_group.second; + const auto &complete_ok_counts = buckets.completed_span_count_per_latency_bucket; + + auto latency_counts = json::array(); + for (unsigned int boundary = 0; boundary < kLatencyBoundaries.size(); boundary++) + { + latency_counts.push_back(complete_ok_counts[boundary]); + } + + counts_json.push_back({{"name", aggregation_group.first}, + {"error", buckets.error_span_count}, + {"running", buckets.running_span_count}, + {"latency", latency_counts}}); + } + return counts_json; +} + +json TracezHttpServer::GetRunningSpansJSON(const std::string &name) +{ + auto running_json = json::array(); + + auto grouping = aggregated_data_.find(name); + + if (grouping != aggregated_data_.end()) + { + const auto &running_samples = grouping->second.sample_running_spans; + for (const auto &sample : running_samples) + { + running_json.push_back({ + {"spanid", std::string(reinterpret_cast(sample.GetSpanId().Id().data()))}, + {"parentid", + std::string(reinterpret_cast(sample.GetParentSpanId().Id().data()))}, + {"traceid", std::string(reinterpret_cast(sample.GetTraceId().Id().data()))}, + {"start", sample.GetStartTime().time_since_epoch().count()}, + {"attributes", GetAttributesJSON(sample)}, + }); + } + } + return running_json; +} + +json TracezHttpServer::GetErrorSpansJSON(const std::string &name) +{ + auto error_json = json::array(); + + auto grouping = aggregated_data_.find(name); + + if (grouping != aggregated_data_.end()) + { + const auto &error_samples = grouping->second.sample_error_spans; + for (const auto &sample : error_samples) + { + error_json.push_back({ + {"spanid", std::string(reinterpret_cast(sample.GetSpanId().Id().data()))}, + {"parentid", + std::string(reinterpret_cast(sample.GetParentSpanId().Id().data()))}, + {"traceid", std::string(reinterpret_cast(sample.GetTraceId().Id().data()))}, + {"start", sample.GetStartTime().time_since_epoch().count()}, + {"status", (unsigned short)sample.GetStatus()}, + {"attributes", GetAttributesJSON(sample)}, + }); + } + } + return error_json; +} + +json TracezHttpServer::GetLatencySpansJSON(const std::string &name, int latency_range_index) +{ + auto latency_json = json::array(); + + auto grouping = aggregated_data_.find(name); + + if (grouping != aggregated_data_.end()) + { + const auto &latency_samples = grouping->second.sample_latency_spans[latency_range_index]; + for (const auto &sample : latency_samples) + { + latency_json.push_back({ + {"spanid", std::string(reinterpret_cast(sample.GetSpanId().Id().data()))}, + {"parentid", + std::string(reinterpret_cast(sample.GetParentSpanId().Id().data()))}, + {"traceid", std::string(reinterpret_cast(sample.GetTraceId().Id().data()))}, + {"start", sample.GetStartTime().time_since_epoch().count()}, + {"duration", sample.GetDuration().count()}, + {"attributes", GetAttributesJSON(sample)}, + }); + } + } + return latency_json; +} + +json TracezHttpServer::GetAttributesJSON( + const opentelemetry::ext::zpages::ThreadsafeSpanData &sample) +{ + auto attributes_json = json::object(); + for (const auto &sample_attribute : sample.GetAttributes()) + { + auto &key = sample_attribute.first; + auto &val = sample_attribute.second; // SpanDataAttributeValue + + /* Convert variant types to into their nonvariant form. This is done this way because + the frontend and JSON doesn't care about type, and variant's get function only allows + const integers or literals */ + + switch (val.index()) + { + case 0: + attributes_json[key] = opentelemetry::nostd::get<0>(val); + break; + case 1: + attributes_json[key] = opentelemetry::nostd::get<1>(val); + break; + case 2: + attributes_json[key] = opentelemetry::nostd::get<2>(val); + break; + case 3: + attributes_json[key] = opentelemetry::nostd::get<3>(val); + break; + case 4: + attributes_json[key] = opentelemetry::nostd::get<4>(val); + break; + case 5: + attributes_json[key] = opentelemetry::nostd::get<5>(val); + break; + case 6: + attributes_json[key] = opentelemetry::nostd::get<6>(val); + break; + case 7: + attributes_json[key] = opentelemetry::nostd::get<7>(val); + break; + case 8: + attributes_json[key] = opentelemetry::nostd::get<8>(val); + break; + case 9: + attributes_json[key] = opentelemetry::nostd::get<9>(val); + break; + } + } + return attributes_json; +} + +} // namespace zpages +} // namespace ext +OPENTELEMETRY_END_NAMESPACE From 119e0309a717ed7e9acefedc693689e7f1211156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Z=C3=BCrcher?= Date: Wed, 12 Aug 2020 16:53:14 +0200 Subject: [PATCH 10/12] fix cmake error when building without tests (#271) --- exporters/ostream/CMakeLists.txt | 26 ++++++++++++++------------ exporters/otlp/CMakeLists.txt | 14 ++++++++------ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/exporters/ostream/CMakeLists.txt b/exporters/ostream/CMakeLists.txt index e82344f120..4818dfd5e2 100644 --- a/exporters/ostream/CMakeLists.txt +++ b/exporters/ostream/CMakeLists.txt @@ -3,18 +3,20 @@ include_directories(include) add_library(opentelemetry_exporter_ostream_metrics src/metrics_exporter.cc) add_library(opentelemetry_exporter_ostream_span src/span_exporter.cc) -add_executable(ostream_metrics_test test/ostream_metrics_test.cc) -add_executable(ostream_span_test test/ostream_span_test.cc) +if(BUILD_TESTING) + add_executable(ostream_metrics_test test/ostream_metrics_test.cc) + add_executable(ostream_span_test test/ostream_span_test.cc) -target_link_libraries( - ostream_span_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} - opentelemetry_exporter_ostream_span) + target_link_libraries( + ostream_span_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_exporter_ostream_span) -target_link_libraries( - ostream_metrics_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} - opentelemetry_exporter_ostream_metrics) + target_link_libraries( + ostream_metrics_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_exporter_ostream_metrics) -gtest_add_tests(TARGET ostream_metrics_test TEST_PREFIX exporter. TEST_LIST - ostream_metrics_test) -gtest_add_tests(TARGET ostream_span_test TEST_PREFIX exporter. TEST_LIST - ostream_span_test) + gtest_add_tests(TARGET ostream_metrics_test TEST_PREFIX exporter. TEST_LIST + ostream_metrics_test) + gtest_add_tests(TARGET ostream_span_test TEST_PREFIX exporter. TEST_LIST + ostream_span_test) +endif() # BUILD_TESTING diff --git a/exporters/otlp/CMakeLists.txt b/exporters/otlp/CMakeLists.txt index 91a3d89337..27ee7de956 100644 --- a/exporters/otlp/CMakeLists.txt +++ b/exporters/otlp/CMakeLists.txt @@ -4,9 +4,11 @@ add_library(opentelemetry_exporter_otprotocol src/recordable.cc) target_link_libraries(opentelemetry_exporter_otprotocol $) -add_executable(recordable_test test/recordable_test.cc) -target_link_libraries( - recordable_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} - opentelemetry_exporter_otprotocol protobuf::libprotobuf) -gtest_add_tests(TARGET recordable_test TEST_PREFIX exporter. TEST_LIST - recordable_test) +if(BUILD_TESTING) + add_executable(recordable_test test/recordable_test.cc) + target_link_libraries( + recordable_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_exporter_otprotocol protobuf::libprotobuf) + gtest_add_tests(TARGET recordable_test TEST_PREFIX exporter. TEST_LIST + recordable_test) +endif() # BUILD_TESTING From 175c08f84e2ecd0f4d919f18ca6140a9ee4e8dce Mon Sep 17 00:00:00 2001 From: Johannes Tax Date: Wed, 12 Aug 2020 08:21:06 -0700 Subject: [PATCH 11/12] Remove Circle CI (#236) --- .circleci/config.yml | 163 ------------------------------------------- 1 file changed, 163 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index ea4d74a365..0000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,163 +0,0 @@ -version: 2.1 - -orbs: - win: circleci/windows@1.0.0 - -jobs: - cmake_test: - resource_class: xlarge - docker: - - image: ubuntu:18.04 - steps: - - checkout - - run: ./ci/setup_ci_environment.sh - - run: ./ci/setup_cmake.sh - - run: ./ci/install_protobuf.sh - - run: ./ci/do_ci.sh cmake.test - - run: ./ci/do_ci.sh cmake.exporter.otprotocol.test - - store_artifacts: - path: ~/build/Testing/Temporary/LastTest.log - destination: Test.log - - plugin_test: - resource_class: xlarge - docker: - - image: ubuntu:18.04 - steps: - - checkout - - run: ./ci/setup_ci_environment.sh - - run: ./ci/setup_cmake.sh - - run: ./ci/do_ci.sh cmake.test_example_plugin - - store_artifacts: - path: ~/build/Testing/Temporary/LastTest.log - destination: Test.log - - store_artifacts: - path: ~/plugin/libexample_plugin.so - destination: libexample_plugin_so - - gcc_48_test: - resource_class: xlarge - docker: - - image: ubuntu:18.04 - steps: - - checkout - - run: ./ci/setup_ci_environment.sh - - run: ./ci/install_bazelisk.sh - - run: ./ci/install_gcc48.sh - - run: CC=/usr/bin/gcc-4.8 ./ci/do_ci.sh bazel.legacy.test - - bazel_test: - resource_class: xlarge - docker: - - image: ubuntu:18.04 - steps: - - checkout - - run: ./ci/setup_ci_environment.sh - - run: ./ci/install_bazelisk.sh - - run: ./ci/do_ci.sh bazel.test - - bazel_noexcept: - resource_class: xlarge - docker: - - image: ubuntu:18.04 - steps: - - checkout - - run: ./ci/setup_ci_environment.sh - - run: ./ci/install_bazelisk.sh - - run: ./ci/do_ci.sh bazel.noexcept - - bazel_asan: - resource_class: xlarge - docker: - - image: ubuntu:18.04 - steps: - - checkout - - run: ./ci/setup_ci_environment.sh - - run: ./ci/install_bazelisk.sh - - run: ./ci/do_ci.sh bazel.asan - - bazel_tsan: - resource_class: xlarge - docker: - - image: ubuntu:18.04 - steps: - - checkout - - run: ./ci/setup_ci_environment.sh - - run: ./ci/install_bazelisk.sh - - run: ./ci/do_ci.sh bazel.tsan - - benchmark: - resource_class: xlarge - docker: - - image: ubuntu:18.04 - steps: - - checkout - - run: ./ci/setup_ci_environment.sh - - run: ./ci/install_bazelisk.sh - - run: env BENCHMARK_DIR=/benchmark ./ci/do_ci.sh benchmark - - store_artifacts: - path: /benchmark - destination: benchmark - - format: - resource_class: xlarge - docker: - - image: ubuntu:18.04 - steps: - - checkout - - run: ./ci/setup_ci_environment.sh - - run: ./ci/install_format_tools.sh - - run: ./ci/do_ci.sh format - - osx_test: - macos: - xcode: "11.0.0" - steps: - - checkout - - run: ./ci/install_osx_bazelisk.sh - - run: ./ci/do_ci.sh bazel.test - - windows: - executor: win/vs2019 - steps: - - checkout - - run: ./ci/setup_windows_cmake.ps1 - - run: ./ci/setup_windows_ci_environment.ps1 - - run: - command: ./ci/install_windows_protobuf.ps1 - no_output_timeout: 15m - - run: ./ci/do_ci.ps1 cmake.test - - run: ./ci/do_ci.sh cmake.exporter.otprotocol.test - - windows_bazel: - executor: win/vs2019 - steps: - - checkout - - run: ./ci/install_windows_bazelisk.ps1 - - run: ./ci/do_ci.ps1 bazel.build - - windows_plugin_test: - executor: win/vs2019 - steps: - - checkout - - run: ./ci/setup_windows_cmake.ps1 - - run: ./ci/setup_windows_ci_environment.ps1 - - run: ./ci/do_ci.ps1 cmake.test_example_plugin - -workflows: - version: 2 - build_and_test: - jobs: - - cmake_test - - plugin_test - - gcc_48_test - - bazel_test - - bazel_noexcept - - bazel_asan - - bazel_tsan - - format - - benchmark - - osx_test - - windows - - windows_bazel - - windows_plugin_test From a55843f58076525e1d2af82b44252d6df1c93578 Mon Sep 17 00:00:00 2001 From: Sam Atac <65615762+satac2@users.noreply.github.com> Date: Wed, 12 Aug 2020 14:51:20 -0500 Subject: [PATCH 12/12] Span auto add context (#252) --- .../opentelemetry/context/context_value.h | 9 ++- .../opentelemetry/context/runtime_context.h | 56 ++++++++++---- .../opentelemetry/context/runtime_def.h | 8 ++ api/include/opentelemetry/plugin/tracer.h | 15 ++-- api/include/opentelemetry/trace/noop.h | 7 +- api/include/opentelemetry/trace/span.h | 27 +++++-- api/include/opentelemetry/trace/tracer.h | 9 ++- api/test/context/runtime_context_test.cc | 27 ++++--- api/test/plugin/dynamic_load_test.cc | 1 + examples/batch/main.cc | 2 + examples/plugin/load/main.cc | 1 + examples/plugin/plugin/tracer.cc | 17 +++-- examples/plugin/plugin/tracer.h | 2 +- examples/simple/main.cc | 1 + exporters/ostream/test/ostream_span_test.cc | 1 + .../otlp/test/otlp_exporter_benchmark.cc | 1 + exporters/otlp/test/otlp_exporter_test.cc | 1 + exporters/otlp/test/recordable_test.cc | 2 +- ext/test/zpages/threadsafe_span_data_test.cc | 1 + .../zpages/tracez_data_aggregator_test.cc | 48 +++++++++--- ext/test/zpages/tracez_processor_test.cc | 48 ++++++++---- sdk/include/opentelemetry/sdk/trace/tracer.h | 2 +- sdk/src/trace/.tracer.cc.swp | Bin 12288 -> 0 bytes sdk/src/trace/span.cc | 25 +++++- sdk/src/trace/span.h | 4 + sdk/src/trace/tracer.cc | 16 +++- sdk/test/trace/always_off_sampler_test.cc | 1 + sdk/test/trace/always_on_sampler_test.cc | 1 + sdk/test/trace/attribute_utils_test.cc | 1 + sdk/test/trace/batch_span_processor_test.cc | 1 + sdk/test/trace/parent_or_else_sampler_test.cc | 1 + sdk/test/trace/probability_sampler_test.cc | 1 + sdk/test/trace/sampler_benchmark.cc | 1 + sdk/test/trace/simple_processor_test.cc | 1 + sdk/test/trace/span_data_test.cc | 1 + sdk/test/trace/tracer_provider_test.cc | 1 + sdk/test/trace/tracer_test.cc | 71 ++++++++++++++---- 37 files changed, 304 insertions(+), 108 deletions(-) create mode 100644 api/include/opentelemetry/context/runtime_def.h delete mode 100644 sdk/src/trace/.tracer.cc.swp diff --git a/api/include/opentelemetry/context/context_value.h b/api/include/opentelemetry/context/context_value.h index 8fd2fabe8c..d03e4289d4 100644 --- a/api/include/opentelemetry/context/context_value.h +++ b/api/include/opentelemetry/context/context_value.h @@ -6,13 +6,18 @@ #include "opentelemetry/nostd/span.h" #include "opentelemetry/nostd/unique_ptr.h" #include "opentelemetry/nostd/variant.h" +#include "opentelemetry/trace/span.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/version.h" OPENTELEMETRY_BEGIN_NAMESPACE namespace context { -using ContextValue = - nostd::variant>; +using ContextValue = nostd::variant, + nostd::shared_ptr>; } // namespace context OPENTELEMETRY_END_NAMESPACE diff --git a/api/include/opentelemetry/context/runtime_context.h b/api/include/opentelemetry/context/runtime_context.h index fabd353a84..cc4b3bb485 100644 --- a/api/include/opentelemetry/context/runtime_context.h +++ b/api/include/opentelemetry/context/runtime_context.h @@ -5,32 +5,53 @@ OPENTELEMETRY_BEGIN_NAMESPACE namespace context { -// Provides a wrapper for propagating the context object globally. In order -// to use either the threadlocal_context.h file must be included or another -// implementation which must be derived from the RuntimeContext can be -// provided. -class RuntimeContext +// The Token object provides is returned when attaching objects to the +// RuntimeContext object and is associated with a context object, and +// can be provided to the RuntimeContext Detach method to remove the +// associated context from the RuntimeContext. +class Token { public: - class Token + bool operator==(const Context &other) noexcept { return context_ == other; } + +private: + friend class RuntimeContext; + + // The ContextDetacher object automatically attempts to detach + // the Token when all copies of the Token are out of scope. + class ContextDetacher { public: - bool operator==(const Context &other) noexcept { return context_ == other; } + ContextDetacher(Context context) : context_(context) {} - ~Token() noexcept { Detach(*this); } + ~ContextDetacher(); private: - friend class RuntimeContext; + Context context_; + }; - // A constructor that sets the token's Context object to the - // one that was passed in. - Token(Context context) noexcept : context_(context){}; + Token() noexcept = default; - Token() noexcept = default; + // A constructor that sets the token's Context object to the + // one that was passed in. + Token(Context context) + { + context_ = context; - Context context_; + detacher_ = nostd::shared_ptr(new ContextDetacher(context_)); }; + Context context_; + nostd::shared_ptr detacher_; +}; + +// Provides a wrapper for propagating the context object globally. In order +// to use either the threadlocal_context.h file must be included or another +// implementation which must be derived from the RuntimeContext can be +// provided. +class RuntimeContext +{ +public: // Return the current context. static Context GetCurrent() noexcept { return context_handler_->InternalGetCurrent(); } @@ -97,5 +118,12 @@ class RuntimeContext virtual bool InternalDetach(Token &token) noexcept = 0; }; + +inline Token::ContextDetacher::~ContextDetacher() +{ + context::Token token; + token.context_ = context_; + context::RuntimeContext::Detach(token); +} } // namespace context OPENTELEMETRY_END_NAMESPACE diff --git a/api/include/opentelemetry/context/runtime_def.h b/api/include/opentelemetry/context/runtime_def.h new file mode 100644 index 0000000000..7a62e2722d --- /dev/null +++ b/api/include/opentelemetry/context/runtime_def.h @@ -0,0 +1,8 @@ +#pragma once + +//#include "opentelemetry/context/runtime_context.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace context +{} +OPENTELEMETRY_BEGIN_NAMESPACE diff --git a/api/include/opentelemetry/plugin/tracer.h b/api/include/opentelemetry/plugin/tracer.h index e2f8bc79f8..619bc2e405 100644 --- a/api/include/opentelemetry/plugin/tracer.h +++ b/api/include/opentelemetry/plugin/tracer.h @@ -13,8 +13,8 @@ namespace plugin class Span final : public trace::Span { public: - Span(std::shared_ptr &&tracer, std::unique_ptr &&span) noexcept - : tracer_{std::move(tracer)}, span_{std::move(span)} + Span(std::shared_ptr &&tracer, nostd::shared_ptr span) noexcept + : tracer_{std::move(tracer)}, span_{span} {} // trace::Span @@ -50,9 +50,11 @@ class Span final : public trace::Span trace::Tracer &tracer() const noexcept override { return *tracer_; } + void SetToken(nostd::unique_ptr &&token) noexcept override {} + private: std::shared_ptr tracer_; - std::unique_ptr span_; + nostd::shared_ptr span_; }; class Tracer final : public trace::Tracer, public std::enable_shared_from_this @@ -64,7 +66,7 @@ class Tracer final : public trace::Tracer, public std::enable_shared_from_this StartSpan( + nostd::shared_ptr StartSpan( nostd::string_view name, const trace::KeyValueIterable &attributes, const trace::StartSpanOptions &options = {}) noexcept override @@ -72,10 +74,9 @@ class Tracer final : public trace::Tracer, public std::enable_shared_from_thistracer().StartSpan(name, attributes, options); if (span == nullptr) { - return nullptr; + return nostd::shared_ptr(nullptr); } - return nostd::unique_ptr{new (std::nothrow) - Span{this->shared_from_this(), std::move(span)}}; + return nostd::shared_ptr{new (std::nothrow) Span{this->shared_from_this(), span}}; } void ForceFlushWithMicroseconds(uint64_t timeout) noexcept override diff --git a/api/include/opentelemetry/trace/noop.h b/api/include/opentelemetry/trace/noop.h index 2d353489d0..542b0ce382 100644 --- a/api/include/opentelemetry/trace/noop.h +++ b/api/include/opentelemetry/trace/noop.h @@ -4,6 +4,7 @@ // This file is part of the internal implementation of OpenTelemetry. Nothing in this file should be // used directly. Please refer to span.h and tracer.h for documentation on these interfaces. +#include "opentelemetry/context/runtime_context.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/unique_ptr.h" #include "opentelemetry/trace/span.h" @@ -48,6 +49,8 @@ class NoopSpan final : public Span Tracer &tracer() const noexcept override { return *tracer_; } + void SetToken(nostd::unique_ptr && /* token */) noexcept override {} + private: std::shared_ptr tracer_; }; @@ -59,11 +62,11 @@ class NoopTracer final : public Tracer, public std::enable_shared_from_this StartSpan(nostd::string_view /*name*/, + nostd::shared_ptr StartSpan(nostd::string_view /*name*/, const KeyValueIterable & /*attributes*/, const StartSpanOptions & /*options*/) noexcept override { - return nostd::unique_ptr{new (std::nothrow) NoopSpan{this->shared_from_this()}}; + return nostd::shared_ptr{new (std::nothrow) NoopSpan{this->shared_from_this()}}; } void ForceFlushWithMicroseconds(uint64_t /*timeout*/) noexcept override {} diff --git a/api/include/opentelemetry/trace/span.h b/api/include/opentelemetry/trace/span.h index 1f8ae415a9..01be49f5db 100644 --- a/api/include/opentelemetry/trace/span.h +++ b/api/include/opentelemetry/trace/span.h @@ -6,11 +6,18 @@ #include "opentelemetry/core/timestamp.h" #include "opentelemetry/nostd/span.h" #include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/unique_ptr.h" #include "opentelemetry/trace/canonical_code.h" #include "opentelemetry/trace/key_value_iterable_view.h" #include "opentelemetry/version.h" +constexpr char SpanKey[] = "span_key"; + OPENTELEMETRY_BEGIN_NAMESPACE +namespace context +{ +class Token; +} namespace trace { enum class SpanKind @@ -22,17 +29,19 @@ enum class SpanKind kConsumer, }; /** - * StartSpanOptions provides options to set properties of a Span at the time of its creation + * StartSpanOptions provides options to set properties of a Span at the time of + * its creation */ struct StartSpanOptions { // Optionally sets the start time of a Span. // - // If the start time of a Span is set, timestamps from both the system clock and steady clock - // must be provided. + // If the start time of a Span is set, timestamps from both the system clock + // and steady clock must be provided. // - // Timestamps from the steady clock can be used to most accurately measure a Span's - // duration, while timestamps from the system clock can be used to most accurately place a Span's + // Timestamps from the steady clock can be used to most accurately measure a + // Span's duration, while timestamps from the system clock can be used to most + // accurately place a Span's // time point relative to other Spans collected across a distributed system. core::SystemTimestamp start_system_time; core::SteadyTimestamp start_steady_time; @@ -74,7 +83,8 @@ class Span Span &operator=(const Span &) = delete; Span &operator=(Span &&) = delete; - // Sets an attribute on the Span. If the Span previously contained a mapping for + // Sets an attribute on the Span. If the Span previously contained a mapping + // for // the key, the old value is replaced. virtual void SetAttribute(nostd::string_view key, const common::AttributeValue &value) noexcept = 0; @@ -128,7 +138,8 @@ class Span attributes.begin(), attributes.end()}); } - // Sets the status of the span. The default status is OK. Only the value of the last call will be + // Sets the status of the span. The default status is OK. Only the value of + // the last call will be // recorded, and implementations are free to ignore previous calls. virtual void SetStatus(CanonicalCode code, nostd::string_view description) noexcept = 0; @@ -153,6 +164,8 @@ class Span virtual bool IsRecording() const noexcept = 0; virtual Tracer &tracer() const noexcept = 0; + + virtual void SetToken(nostd::unique_ptr &&token) noexcept = 0; }; } // namespace trace OPENTELEMETRY_END_NAMESPACE diff --git a/api/include/opentelemetry/trace/tracer.h b/api/include/opentelemetry/trace/tracer.h index 825ff39fb1..68403fa1de 100644 --- a/api/include/opentelemetry/trace/tracer.h +++ b/api/include/opentelemetry/trace/tracer.h @@ -1,5 +1,6 @@ #pragma once +#include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/unique_ptr.h" #include "opentelemetry/trace/span.h" @@ -28,25 +29,25 @@ class Tracer * Attributes will be processed in order, previous attributes with the same * key will be overwritten. */ - virtual nostd::unique_ptr StartSpan(nostd::string_view name, + virtual nostd::shared_ptr StartSpan(nostd::string_view name, const KeyValueIterable &attributes, const StartSpanOptions &options = {}) noexcept = 0; - nostd::unique_ptr StartSpan(nostd::string_view name, + nostd::shared_ptr StartSpan(nostd::string_view name, const StartSpanOptions &options = {}) noexcept { return this->StartSpan(name, {}, options); } template ::value> * = nullptr> - nostd::unique_ptr StartSpan(nostd::string_view name, + nostd::shared_ptr StartSpan(nostd::string_view name, const T &attributes, const StartSpanOptions &options = {}) noexcept { return this->StartSpan(name, KeyValueIterableView(attributes), options); } - nostd::unique_ptr StartSpan( + nostd::shared_ptr StartSpan( nostd::string_view name, std::initializer_list> attributes, const StartSpanOptions &options = {}) noexcept diff --git a/api/test/context/runtime_context_test.cc b/api/test/context/runtime_context_test.cc index 5c0dc565d4..f9e229d57c 100644 --- a/api/test/context/runtime_context_test.cc +++ b/api/test/context/runtime_context_test.cc @@ -10,7 +10,7 @@ TEST(RuntimeContextTest, GetCurrent) { std::map map_test = {{"test_key", (int64_t)123}}; context::Context test_context = context::Context(map_test); - context::RuntimeContext::Token old_context = context::RuntimeContext::Attach(test_context); + context::Token old_context = context::RuntimeContext::Attach(test_context); EXPECT_TRUE(context::RuntimeContext::GetCurrent() == test_context); context::RuntimeContext::Detach(old_context); } @@ -22,8 +22,8 @@ TEST(RuntimeContextTest, Detach) context::Context test_context = context::Context(map_test); context::Context foo_context = context::Context(map_test); - context::RuntimeContext::Token test_context_token = context::RuntimeContext::Attach(test_context); - context::RuntimeContext::Token foo_context_token = context::RuntimeContext::Attach(foo_context); + context::Token test_context_token = context::RuntimeContext::Attach(test_context); + context::Token foo_context_token = context::RuntimeContext::Attach(foo_context); context::RuntimeContext::Detach(foo_context_token); EXPECT_TRUE(context::RuntimeContext::GetCurrent() == test_context); @@ -36,8 +36,8 @@ TEST(RuntimeContextTest, DetachWrongContext) std::map map_test = {{"test_key", (int64_t)123}}; context::Context test_context = context::Context(map_test); context::Context foo_context = context::Context(map_test); - context::RuntimeContext::Token test_context_token = context::RuntimeContext::Attach(test_context); - context::RuntimeContext::Token foo_context_token = context::RuntimeContext::Attach(foo_context); + context::Token test_context_token = context::RuntimeContext::Attach(test_context); + context::Token foo_context_token = context::RuntimeContext::Attach(foo_context); EXPECT_FALSE(context::RuntimeContext::Detach(test_context_token)); context::RuntimeContext::Detach(foo_context_token); context::RuntimeContext::Detach(test_context_token); @@ -50,10 +50,9 @@ TEST(RuntimeContextTest, ThreeAttachDetach) context::Context test_context = context::Context(map_test); context::Context foo_context = context::Context(map_test); context::Context other_context = context::Context(map_test); - context::RuntimeContext::Token test_context_token = context::RuntimeContext::Attach(test_context); - context::RuntimeContext::Token foo_context_token = context::RuntimeContext::Attach(foo_context); - context::RuntimeContext::Token other_context_token = - context::RuntimeContext::Attach(other_context); + context::Token test_context_token = context::RuntimeContext::Attach(test_context); + context::Token foo_context_token = context::RuntimeContext::Attach(foo_context); + context::Token other_context_token = context::RuntimeContext::Attach(other_context); EXPECT_TRUE(context::RuntimeContext::Detach(other_context_token)); EXPECT_TRUE(context::RuntimeContext::Detach(foo_context_token)); @@ -65,9 +64,9 @@ TEST(RuntimeContextTest, ThreeAttachDetach) // 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); + context::Context foo_context = context::Context("foo_key", (int64_t)596); + context::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); } @@ -88,8 +87,8 @@ TEST(RuntimeContextTest, SetValueOtherContext) // 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); + context::Context foo_context = context::Context("foo_key", (int64_t)596); + context::Token old_context_token = context::RuntimeContext::Attach(foo_context); EXPECT_EQ(nostd::get(context::RuntimeContext::GetValue("foo_key")), 596); } diff --git a/api/test/plugin/dynamic_load_test.cc b/api/test/plugin/dynamic_load_test.cc index 3e0e45ecc1..2e630f6649 100644 --- a/api/test/plugin/dynamic_load_test.cc +++ b/api/test/plugin/dynamic_load_test.cc @@ -1,4 +1,5 @@ #include "opentelemetry/plugin/dynamic_load.h" +#include "opentelemetry/context/threadlocal_context.h" #include diff --git a/examples/batch/main.cc b/examples/batch/main.cc index 7d7fa57f36..e159984ddf 100644 --- a/examples/batch/main.cc +++ b/examples/batch/main.cc @@ -4,6 +4,8 @@ #include "opentelemetry/exporters/ostream/span_exporter.h" #include "opentelemetry/sdk/trace/batch_span_processor.h" +#include "opentelemetry/context/threadlocal_context.h" + #include #include diff --git a/examples/plugin/load/main.cc b/examples/plugin/load/main.cc index 3c6799fed0..5348838942 100644 --- a/examples/plugin/load/main.cc +++ b/examples/plugin/load/main.cc @@ -1,3 +1,4 @@ +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/plugin/dynamic_load.h" #include diff --git a/examples/plugin/plugin/tracer.cc b/examples/plugin/plugin/tracer.cc index f054da7f03..db1954a6f3 100644 --- a/examples/plugin/plugin/tracer.cc +++ b/examples/plugin/plugin/tracer.cc @@ -1,11 +1,14 @@ #include "tracer.h" +#include "opentelemetry/context/runtime_context.h" +#include "opentelemetry/nostd/unique_ptr.h" #include -namespace nostd = opentelemetry::nostd; -namespace common = opentelemetry::common; -namespace core = opentelemetry::core; -namespace trace = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; +namespace common = opentelemetry::common; +namespace core = opentelemetry::core; +namespace trace = opentelemetry::trace; +namespace context = opentelemetry::context; namespace { @@ -50,6 +53,8 @@ class Span final : public trace::Span Tracer &tracer() const noexcept override { return *tracer_; } + void SetToken(nostd::unique_ptr &&token) noexcept override {} + private: std::shared_ptr tracer_; std::string name_; @@ -58,11 +63,11 @@ class Span final : public trace::Span Tracer::Tracer(nostd::string_view /*output*/) {} -nostd::unique_ptr Tracer::StartSpan( +nostd::shared_ptr Tracer::StartSpan( nostd::string_view name, const opentelemetry::trace::KeyValueIterable &attributes, const trace::StartSpanOptions &options) noexcept { - return nostd::unique_ptr{ + return nostd::shared_ptr{ new (std::nothrow) Span{this->shared_from_this(), name, attributes, options}}; } diff --git a/examples/plugin/plugin/tracer.h b/examples/plugin/plugin/tracer.h index ad1b98633d..c50b5f458b 100644 --- a/examples/plugin/plugin/tracer.h +++ b/examples/plugin/plugin/tracer.h @@ -11,7 +11,7 @@ class Tracer final : public opentelemetry::trace::Tracer, explicit Tracer(opentelemetry::nostd::string_view output); // opentelemetry::trace::Tracer - opentelemetry::nostd::unique_ptr StartSpan( + opentelemetry::nostd::shared_ptr StartSpan( opentelemetry::nostd::string_view name, const opentelemetry::trace::KeyValueIterable & /*attributes*/, const opentelemetry::trace::StartSpanOptions & /*options */) noexcept override; diff --git a/examples/simple/main.cc b/examples/simple/main.cc index 485d31c2ec..572c3be1a7 100644 --- a/examples/simple/main.cc +++ b/examples/simple/main.cc @@ -1,3 +1,4 @@ +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/sdk/trace/simple_processor.h" #include "opentelemetry/sdk/trace/tracer_provider.h" #include "opentelemetry/trace/provider.h" diff --git a/exporters/ostream/test/ostream_span_test.cc b/exporters/ostream/test/ostream_span_test.cc index e1f0c6b937..5dfecde265 100644 --- a/exporters/ostream/test/ostream_span_test.cc +++ b/exporters/ostream/test/ostream_span_test.cc @@ -1,3 +1,4 @@ +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/sdk/trace/recordable.h" #include "opentelemetry/sdk/trace/simple_processor.h" #include "opentelemetry/sdk/trace/span_data.h" diff --git a/exporters/otlp/test/otlp_exporter_benchmark.cc b/exporters/otlp/test/otlp_exporter_benchmark.cc index 0accff0bf4..30f9046e34 100644 --- a/exporters/otlp/test/otlp_exporter_benchmark.cc +++ b/exporters/otlp/test/otlp_exporter_benchmark.cc @@ -1,3 +1,4 @@ +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/exporters/otlp/otlp_exporter.h" #include "opentelemetry/exporters/otlp/recordable.h" diff --git a/exporters/otlp/test/otlp_exporter_test.cc b/exporters/otlp/test/otlp_exporter_test.cc index 8e2d902a91..581eb8f125 100644 --- a/exporters/otlp/test/otlp_exporter_test.cc +++ b/exporters/otlp/test/otlp_exporter_test.cc @@ -1,4 +1,5 @@ #include "opentelemetry/exporters/otlp/otlp_exporter.h" +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/proto/collector/trace/v1/trace_service_mock.grpc.pb.h" #include "opentelemetry/sdk/trace/simple_processor.h" #include "opentelemetry/sdk/trace/tracer_provider.h" diff --git a/exporters/otlp/test/recordable_test.cc b/exporters/otlp/test/recordable_test.cc index 62ced4ac6b..a349c1f50b 100644 --- a/exporters/otlp/test/recordable_test.cc +++ b/exporters/otlp/test/recordable_test.cc @@ -1,6 +1,6 @@ #include "opentelemetry/exporters/otlp/recordable.h" - #include +#include "opentelemetry/context/threadlocal_context.h" OPENTELEMETRY_BEGIN_NAMESPACE namespace exporter diff --git a/ext/test/zpages/threadsafe_span_data_test.cc b/ext/test/zpages/threadsafe_span_data_test.cc index 4b0b6ce42a..0453d2db11 100644 --- a/ext/test/zpages/threadsafe_span_data_test.cc +++ b/ext/test/zpages/threadsafe_span_data_test.cc @@ -1,4 +1,5 @@ #include "opentelemetry/ext/zpages/threadsafe_span_data.h" +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/nostd/variant.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/trace_id.h" diff --git a/ext/test/zpages/tracez_data_aggregator_test.cc b/ext/test/zpages/tracez_data_aggregator_test.cc index e1e3d13292..035b2d8f26 100644 --- a/ext/test/zpages/tracez_data_aggregator_test.cc +++ b/ext/test/zpages/tracez_data_aggregator_test.cc @@ -2,6 +2,7 @@ #include +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/ext/zpages/tracez_processor.h" #include "opentelemetry/sdk/trace/recordable.h" #include "opentelemetry/sdk/trace/tracer.h" @@ -115,6 +116,7 @@ TEST_F(TracezDataAggregatorTest, SingleRunningSpan) ASSERT_EQ(aggregated_data.sample_running_spans.size(), 1); ASSERT_EQ(aggregated_data.sample_running_spans.front().GetName().data(), span_name1); + span_first->End(); } /** Test to check if data aggregator works as expected when there is exactly one @@ -152,8 +154,9 @@ TEST_F(TracezDataAggregatorTest, SingleCompletedSpan) TEST_F(TracezDataAggregatorTest, SingleErrorSpan) { // Start and end a single error span - tracer->StartSpan(span_name1) - ->SetStatus(opentelemetry::trace::CanonicalCode::CANCELLED, "span cancelled"); + auto span = tracer->StartSpan(span_name1); + span->SetStatus(opentelemetry::trace::CanonicalCode::CANCELLED, "span cancelled"); + span->End(); std::this_thread::sleep_for(milliseconds(500)); auto data = tracez_data_aggregator->GetAggregatedTracezData(); @@ -184,7 +187,7 @@ TEST_F(TracezDataAggregatorTest, MultipleRunningSpans) }); // Start and store spans based on the above map - std::vector> running_span_container; + std::vector> running_span_container; for (auto span_name : running_span_name_to_count) { for (int count = 0; count < span_name.second; count++) @@ -211,6 +214,9 @@ TEST_F(TracezDataAggregatorTest, MultipleRunningSpans) ASSERT_EQ(span_sample.GetName().data(), span_name.first); } } + + for (auto i = running_span_container.begin(); i != running_span_container.end(); i++) + (*i)->End(); } /** Test to check if multiple completed spans updates the aggregated data @@ -311,8 +317,11 @@ TEST_F(TracezDataAggregatorTest, MultipleErrorSpans) for (auto &span_error : span_name_to_error) { for (auto error_desc : span_error.second) - tracer->StartSpan(span_error.first) - ->SetStatus(opentelemetry::trace::CanonicalCode::CANCELLED, error_desc); + { + auto span = tracer->StartSpan(span_error.first); + span->SetStatus(opentelemetry::trace::CanonicalCode::CANCELLED, error_desc); + span->End(); + } } // Give some time and then get data @@ -354,7 +363,7 @@ TEST_F(TracezDataAggregatorTest, RunningSampleSpansOverCapacity) { int running_span_count = 6; // Start and store spans based on the above map - std::vector> running_span_container; + std::vector> running_span_container; for (int count = 0; count < running_span_count; count++) running_span_container.push_back(tracer->StartSpan(span_name1)); @@ -369,6 +378,11 @@ TEST_F(TracezDataAggregatorTest, RunningSampleSpansOverCapacity) VerifySpanCountsInTracezData(span_name1, aggregated_data, 6, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0}); ASSERT_EQ(aggregated_data.sample_running_spans.size(), kMaxNumberOfSampleSpans); + + for (auto i = running_span_container.begin(); i != running_span_container.end(); i++) + { + (*i)->End(); + } } /** @@ -383,8 +397,11 @@ TEST_F(TracezDataAggregatorTest, ErrorSampleSpansOverCapacity) "error span 3", "error span 4", "error span 5", "error span 6"}; for (auto span_error_description : span_error_descriptions) - tracer->StartSpan(span_name1) - ->SetStatus(opentelemetry::trace::CanonicalCode::CANCELLED, span_error_description); + { + auto span = tracer->StartSpan(span_name1); + span->SetStatus(opentelemetry::trace::CanonicalCode::CANCELLED, span_error_description); + span->End(); + } std::this_thread::sleep_for(milliseconds(500)); @@ -471,8 +488,9 @@ TEST_F(TracezDataAggregatorTest, SpanNameInAlphabeticalOrder) auto span_first = tracer->StartSpan(span_name2); tracer->StartSpan(span_name1)->End(); - tracer->StartSpan(span_name3) - ->SetStatus(opentelemetry::trace::CanonicalCode::CANCELLED, "span cancelled"); + auto span_third = tracer->StartSpan(span_name3); + span_third->SetStatus(opentelemetry::trace::CanonicalCode::CANCELLED, "span cancelled"); + span_third->End(); std::this_thread::sleep_for(milliseconds(500)); // Get data and check if span name exists in aggregation auto data = tracez_data_aggregator->GetAggregatedTracezData(); @@ -484,6 +502,7 @@ TEST_F(TracezDataAggregatorTest, SpanNameInAlphabeticalOrder) ASSERT_EQ(spans.first, span_names[span_names_idx]); span_names_idx++; } + span_first->End(); } /** This test checks to see that there is no double counting of running spans @@ -514,6 +533,8 @@ TEST_F(TracezDataAggregatorTest, AdditionToRunningSpans) { ASSERT_EQ(sample_span.GetName().data(), span_name1); } + span_first->End(); + span_second->End(); } /** This test checks to see that once a running span is completed it the @@ -647,8 +668,9 @@ TEST_F(TracezDataAggregatorTest, NoChangeInBetweenCallsToAggregator) tracer->StartSpan(span_name1, start)->End(end); auto running_span = tracer->StartSpan(span_name2); - tracer->StartSpan(span_name3) - ->SetStatus(opentelemetry::trace::CanonicalCode::CANCELLED, "span cancelled"); + auto span = tracer->StartSpan(span_name3); + span->SetStatus(opentelemetry::trace::CanonicalCode::CANCELLED, "span cancelled"); + span->End(); std::this_thread::sleep_for(milliseconds(500)); auto data = tracez_data_aggregator->GetAggregatedTracezData(); std::this_thread::sleep_for(milliseconds(500)); @@ -662,4 +684,6 @@ TEST_F(TracezDataAggregatorTest, NoChangeInBetweenCallsToAggregator) ASSERT_TRUE(data.find(span_name3) != data.end()); VerifySpanCountsInTracezData(span_name3, data.at(span_name3), 0, 1, {0, 0, 0, 0, 0, 0, 0, 0, 0}); + + running_span->End(); } diff --git a/ext/test/zpages/tracez_processor_test.cc b/ext/test/zpages/tracez_processor_test.cc index 9d9fb3afe6..7b4c633926 100644 --- a/ext/test/zpages/tracez_processor_test.cc +++ b/ext/test/zpages/tracez_processor_test.cc @@ -1,4 +1,5 @@ #include "opentelemetry/ext/zpages/tracez_processor.h" +#include "opentelemetry/context/threadlocal_context.h" #include @@ -146,7 +147,7 @@ void GetManySnapshots(std::shared_ptr &processor, int i) * in vector. Used for testing thread safety */ void StartManySpans( - std::vector> &spans, + std::vector> &spans, std::shared_ptr tracer, int i) { @@ -158,7 +159,7 @@ void StartManySpans( * Helper function that ends all spans in the passed in span vector. Used * for testing thread safety */ -void EndAllSpans(std::vector> &spans) +void EndAllSpans(std::vector> &spans) { for (auto &span : spans) span->End(); @@ -186,11 +187,11 @@ class TracezProcessor : public ::testing::Test std::shared_ptr processor; std::shared_ptr tracer; + std::vector span_names; + std::vector> span_vars; + std::unordered_set running; std::vector> completed; - - std::vector span_names; - std::vector> span_vars; }; ///////////////////////////////////////// TESTS /////////////////////////////////// @@ -503,14 +504,17 @@ TEST_F(TracezProcessor, FlushShutdown) */ TEST_F(TracezProcessor, RunningThreadSafety) { - std::vector> spans1; - std::vector> spans2; + std::vector> spans1; + std::vector> spans2; std::thread start1(StartManySpans, std::ref(spans1), tracer, 500); std::thread start2(StartManySpans, std::ref(spans2), tracer, 500); start1.join(); start2.join(); + + EndAllSpans(spans1); + EndAllSpans(spans2); } /* @@ -518,8 +522,9 @@ TEST_F(TracezProcessor, RunningThreadSafety) */ TEST_F(TracezProcessor, CompletedThreadSafety) { - std::vector> spans1; - std::vector> spans2; + std::vector> spans1; + std::vector> spans2; + StartManySpans(spans1, tracer, 500); StartManySpans(spans2, tracer, 500); @@ -535,7 +540,7 @@ TEST_F(TracezProcessor, CompletedThreadSafety) */ TEST_F(TracezProcessor, SnapshotThreadSafety) { - std::vector> spans; + std::vector> spans; std::thread snap1(GetManySnapshots, std::ref(processor), 500); std::thread snap2(GetManySnapshots, std::ref(processor), 500); @@ -550,6 +555,8 @@ TEST_F(TracezProcessor, SnapshotThreadSafety) snap3.join(); snap4.join(); + + EndAllSpans(spans); } /* @@ -557,8 +564,9 @@ TEST_F(TracezProcessor, SnapshotThreadSafety) */ TEST_F(TracezProcessor, RunningCompletedThreadSafety) { - std::vector> spans1; - std::vector> spans2; + std::vector> spans1; + std::vector> spans2; + StartManySpans(spans1, tracer, 500); std::thread start(StartManySpans, std::ref(spans2), tracer, 500); @@ -566,6 +574,8 @@ TEST_F(TracezProcessor, RunningCompletedThreadSafety) start.join(); end.join(); + + EndAllSpans(spans2); } /* @@ -573,13 +583,15 @@ TEST_F(TracezProcessor, RunningCompletedThreadSafety) */ TEST_F(TracezProcessor, RunningSnapshotThreadSafety) { - std::vector> spans; + std::vector> spans; std::thread start(StartManySpans, std::ref(spans), tracer, 500); std::thread snapshots(GetManySnapshots, std::ref(processor), 500); start.join(); snapshots.join(); + + EndAllSpans(spans); } /* @@ -587,7 +599,8 @@ TEST_F(TracezProcessor, RunningSnapshotThreadSafety) */ TEST_F(TracezProcessor, SnapshotCompletedThreadSafety) { - std::vector> spans; + std::vector> spans; + StartManySpans(spans, tracer, 500); std::thread snapshots(GetManySnapshots, std::ref(processor), 500); @@ -602,8 +615,9 @@ TEST_F(TracezProcessor, SnapshotCompletedThreadSafety) */ TEST_F(TracezProcessor, RunningSnapshotCompletedThreadSafety) { - std::vector> spans1; - std::vector> spans2; + std::vector> spans1; + std::vector> spans2; + StartManySpans(spans1, tracer, 500); std::thread start(StartManySpans, std::ref(spans2), tracer, 500); @@ -613,4 +627,6 @@ TEST_F(TracezProcessor, RunningSnapshotCompletedThreadSafety) start.join(); snapshots.join(); end.join(); + + EndAllSpans(spans2); } diff --git a/sdk/include/opentelemetry/sdk/trace/tracer.h b/sdk/include/opentelemetry/sdk/trace/tracer.h index e6f5547f2d..bb28842c75 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer.h @@ -44,7 +44,7 @@ class Tracer final : public trace_api::Tracer, public std::enable_shared_from_th */ std::shared_ptr GetSampler() const noexcept; - nostd::unique_ptr StartSpan( + nostd::shared_ptr StartSpan( nostd::string_view name, const trace_api::KeyValueIterable &attributes, const trace_api::StartSpanOptions &options = {}) noexcept override; diff --git a/sdk/src/trace/.tracer.cc.swp b/sdk/src/trace/.tracer.cc.swp deleted file mode 100644 index 6bb638cf102cbd590d4f77c2a27f76904ae8110b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2&2Q937>6gl^#h8^jYBmAm3ph)7z(W-Cq&|76GiHVlmx1Zg5}t=tlRaBJ2OsF ztyYzKs?-YgR#iRqfW!syM{qAHaDXd7<}rfIDUCpdP4@tz&HbiZ8xU( zuw$PdJ>r@knmow&e{gwccUuvv)Ch{QrOJ_y4{38M_Pq z1e@R+a01MN55YdL7d+d`*i+yGa>uizIj56%D&90nhOCwmyX1O5QNgP%bI)WHGp z;629fgX`cYa2Z?zp8yYh3?5;w55Yg+HuxL-2CjlW=m7z~1TL>xKo=Px17v^+EOMOG9UpUE=~&En3)A_}fwi!OmKe?0BnS$beqV?%w|chBCyeqm2v+h)&h?PG z?9VXvHs>P;e1o8DI%u|-^!l}#ac#V{qFt*)m5bS!TM>2<4qhC!^yb3W89MB0gM|o! zSY?@#L2xdTiOPatK}ew^D+b?B745dr<%lx~;6zWuGiGpc`V!LN*O57t?AazX9rb-&9;yfy>DwE_V zI10fHh4nUAW>*cBT^niZ20J_G$PZT$cGQ6DSS^vkv21xITu(9MazHkI?Y4_=Wos;N zP*&CIKbMQE+R{dqxhn`a#pSJ}=e9#G2)q5%t5xZZ7VnPm#wsnm?FT58UE2e-Fbp?l z3d1nN-O^*jZCXmk0K+G4e-GTK%ZTUDdkJ6gzOzwJJ wj^Mj09(^}brv5rk$D<@()x;f;uf~e3%F;L-ns@znj5`|JbsWs1{M4@Tzt=MKSpWb4 diff --git a/sdk/src/trace/span.cc b/sdk/src/trace/span.cc index 07f9e81b42..0e465edd5a 100644 --- a/sdk/src/trace/span.cc +++ b/sdk/src/trace/span.cc @@ -1,5 +1,6 @@ #include "src/trace/span.h" +#include "opentelemetry/context/runtime_context.h" #include "opentelemetry/version.h" OPENTELEMETRY_BEGIN_NAMESPACE @@ -46,7 +47,10 @@ Span::Span(std::shared_ptr &&tracer, : tracer_{std::move(tracer)}, processor_{processor}, recordable_{processor_->MakeRecordable()}, - start_steady_time{options.start_steady_time} + start_steady_time{options.start_steady_time}, + has_ended_{false}, + token_{nullptr} + { (void)options; if (recordable_ == nullptr) @@ -120,6 +124,19 @@ void Span::UpdateName(nostd::string_view name) noexcept void Span::End(const trace_api::EndSpanOptions &options) noexcept { std::lock_guard lock_guard{mu_}; + + if (has_ended_ == true) + { + return; + } + has_ended_ = true; + + if (token_ != nullptr) + { + context::RuntimeContext::Detach(*token_); + token_.reset(); + } + if (recordable_ == nullptr) { return; @@ -138,6 +155,12 @@ bool Span::IsRecording() const noexcept std::lock_guard lock_guard{mu_}; return recordable_ != nullptr; } + +void Span::SetToken(nostd::unique_ptr &&token) noexcept +{ + token_ = std::move(token); +} + } // namespace trace } // namespace sdk OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/trace/span.h b/sdk/src/trace/span.h index 489833703c..f307aa5c53 100644 --- a/sdk/src/trace/span.h +++ b/sdk/src/trace/span.h @@ -44,12 +44,16 @@ class Span final : public trace_api::Span trace_api::Tracer &tracer() const noexcept override { return *tracer_; } + void SetToken(nostd::unique_ptr &&token) noexcept override; + private: std::shared_ptr tracer_; std::shared_ptr processor_; mutable std::mutex mu_; std::unique_ptr recordable_; opentelemetry::core::SteadyTimestamp start_steady_time; + bool has_ended_; + nostd::unique_ptr token_; }; } // namespace trace } // namespace sdk diff --git a/sdk/src/trace/tracer.cc b/sdk/src/trace/tracer.cc index 3902b79259..0221d35003 100644 --- a/sdk/src/trace/tracer.cc +++ b/sdk/src/trace/tracer.cc @@ -1,5 +1,7 @@ #include "opentelemetry/sdk/trace/tracer.h" +#include "opentelemetry/context/runtime_context.h" +#include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/sdk/common/atomic_shared_ptr.h" #include "opentelemetry/version.h" #include "src/trace/span.h" @@ -28,7 +30,7 @@ std::shared_ptr Tracer::GetSampler() const noexcept return sampler_; } -nostd::unique_ptr Tracer::StartSpan( +nostd::shared_ptr Tracer::StartSpan( nostd::string_view name, const trace_api::KeyValueIterable &attributes, const trace_api::StartSpanOptions &options) noexcept @@ -38,14 +40,20 @@ nostd::unique_ptr Tracer::StartSpan( sampler_->ShouldSample(nullptr, trace_api::TraceId(), name, options.kind, attributes); if (sampling_result.decision == Decision::NOT_RECORD) { - return nostd::unique_ptr{new (std::nothrow) - trace_api::NoopSpan{this->shared_from_this()}}; + auto span = nostd::shared_ptr{ + new (std::nothrow) trace_api::NoopSpan{this->shared_from_this()}}; + + return span; } else { - auto span = nostd::unique_ptr{new (std::nothrow) Span{ + auto span = nostd::shared_ptr{new (std::nothrow) Span{ this->shared_from_this(), processor_.load(), name, attributes, options}}; + span->SetToken( + nostd::unique_ptr(new context::Token(context::RuntimeContext::Attach( + context::RuntimeContext::GetCurrent().SetValue(SpanKey, span))))); + // if the attributes is not nullptr, add attributes to the span. if (sampling_result.attributes) { diff --git a/sdk/test/trace/always_off_sampler_test.cc b/sdk/test/trace/always_off_sampler_test.cc index ce2f11c5b7..f987185c2b 100644 --- a/sdk/test/trace/always_off_sampler_test.cc +++ b/sdk/test/trace/always_off_sampler_test.cc @@ -1,3 +1,4 @@ +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/sdk/trace/samplers/always_off.h" #include diff --git a/sdk/test/trace/always_on_sampler_test.cc b/sdk/test/trace/always_on_sampler_test.cc index a933cd0073..5f6443c096 100644 --- a/sdk/test/trace/always_on_sampler_test.cc +++ b/sdk/test/trace/always_on_sampler_test.cc @@ -1,3 +1,4 @@ +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/nostd/span.h" #include "opentelemetry/sdk/trace/samplers/always_on.h" diff --git a/sdk/test/trace/attribute_utils_test.cc b/sdk/test/trace/attribute_utils_test.cc index ee6d846fbb..15fffc288b 100644 --- a/sdk/test/trace/attribute_utils_test.cc +++ b/sdk/test/trace/attribute_utils_test.cc @@ -1,4 +1,5 @@ #include "opentelemetry/sdk/trace/attribute_utils.h" +#include "opentelemetry/context/threadlocal_context.h" #include diff --git a/sdk/test/trace/batch_span_processor_test.cc b/sdk/test/trace/batch_span_processor_test.cc index 8e9e43c735..d240d06b81 100644 --- a/sdk/test/trace/batch_span_processor_test.cc +++ b/sdk/test/trace/batch_span_processor_test.cc @@ -1,4 +1,5 @@ #include "opentelemetry/sdk/trace/batch_span_processor.h" +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/sdk/trace/span_data.h" #include "opentelemetry/sdk/trace/tracer.h" diff --git a/sdk/test/trace/parent_or_else_sampler_test.cc b/sdk/test/trace/parent_or_else_sampler_test.cc index 311ffae59f..7e376fd91e 100644 --- a/sdk/test/trace/parent_or_else_sampler_test.cc +++ b/sdk/test/trace/parent_or_else_sampler_test.cc @@ -1,5 +1,6 @@ #include #include +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/sdk/trace/samplers/always_off.h" #include "opentelemetry/sdk/trace/samplers/always_on.h" #include "opentelemetry/sdk/trace/samplers/parent_or_else.h" diff --git a/sdk/test/trace/probability_sampler_test.cc b/sdk/test/trace/probability_sampler_test.cc index 75a11a1fc7..cd4815aa46 100644 --- a/sdk/test/trace/probability_sampler_test.cc +++ b/sdk/test/trace/probability_sampler_test.cc @@ -1,3 +1,4 @@ +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/sdk/trace/samplers/probability.h" #include "src/common/random.h" diff --git a/sdk/test/trace/sampler_benchmark.cc b/sdk/test/trace/sampler_benchmark.cc index 2dad8c8128..2068846a00 100644 --- a/sdk/test/trace/sampler_benchmark.cc +++ b/sdk/test/trace/sampler_benchmark.cc @@ -1,3 +1,4 @@ +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/sdk/trace/sampler.h" #include "opentelemetry/sdk/trace/samplers/always_off.h" #include "opentelemetry/sdk/trace/samplers/always_on.h" diff --git a/sdk/test/trace/simple_processor_test.cc b/sdk/test/trace/simple_processor_test.cc index b4819aa0ae..b1da619e64 100644 --- a/sdk/test/trace/simple_processor_test.cc +++ b/sdk/test/trace/simple_processor_test.cc @@ -1,4 +1,5 @@ #include "opentelemetry/sdk/trace/simple_processor.h" +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/nostd/span.h" #include "opentelemetry/sdk/trace/span_data.h" diff --git a/sdk/test/trace/span_data_test.cc b/sdk/test/trace/span_data_test.cc index 3b35490df9..6f30110c65 100644 --- a/sdk/test/trace/span_data_test.cc +++ b/sdk/test/trace/span_data_test.cc @@ -1,4 +1,5 @@ #include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/nostd/variant.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/trace_id.h" diff --git a/sdk/test/trace/tracer_provider_test.cc b/sdk/test/trace/tracer_provider_test.cc index dca3bc3607..5840e3f9e0 100644 --- a/sdk/test/trace/tracer_provider_test.cc +++ b/sdk/test/trace/tracer_provider_test.cc @@ -1,4 +1,5 @@ #include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/sdk/trace/samplers/always_off.h" #include "opentelemetry/sdk/trace/samplers/always_on.h" #include "opentelemetry/sdk/trace/simple_processor.h" diff --git a/sdk/test/trace/tracer_test.cc b/sdk/test/trace/tracer_test.cc index 3d10670e16..62cf776713 100644 --- a/sdk/test/trace/tracer_test.cc +++ b/sdk/test/trace/tracer_test.cc @@ -1,4 +1,5 @@ #include "opentelemetry/sdk/trace/tracer.h" +#include "opentelemetry/context/threadlocal_context.h" #include "opentelemetry/sdk/trace/samplers/always_off.h" #include "opentelemetry/sdk/trace/samplers/always_on.h" #include "opentelemetry/sdk/trace/samplers/parent_or_else.h" @@ -10,8 +11,10 @@ using namespace opentelemetry::sdk::trace; using opentelemetry::core::SteadyTimestamp; using opentelemetry::core::SystemTimestamp; -namespace nostd = opentelemetry::nostd; -namespace common = opentelemetry::common; +namespace nostd = opentelemetry::nostd; +namespace common = opentelemetry::common; +namespace context = opentelemetry::context; +namespace trace = opentelemetry::trace; using opentelemetry::trace::SpanContext; /** @@ -26,7 +29,8 @@ class MockSampler final : public Sampler trace_api::SpanKind /*span_kind*/, const trace_api::KeyValueIterable & /*attributes*/) noexcept override { - // Return two pairs of attributes. These attributes should be added to the span attributes + // Return two pairs of attributes. These attributes should be added to the + // span attributes return {Decision::RECORD_AND_SAMPLE, nostd::unique_ptr>( new const std::map( @@ -105,6 +109,7 @@ TEST(Tracer, ToMockSpanExporter) span_second->End(); ASSERT_EQ(1, spans_received->size()); + ASSERT_EQ("span 2", spans_received->at(0)->GetName()); span_first->End(); @@ -120,7 +125,6 @@ TEST(Tracer, StartSpanSampleOn) auto tracer_on = initTracer(spans_received); tracer_on->StartSpan("span 1")->End(); - ASSERT_EQ(1, spans_received->size()); auto &span_data = spans_received->at(0); @@ -138,7 +142,8 @@ TEST(Tracer, StartSpanSampleOff) // This span will not be recorded. tracer_off->StartSpan("span 2")->End(); - // The span doesn't write any span data because the sampling decision is alway NOT_RECORD. + // The span doesn't write any span data because the sampling decision is alway + // NOT_RECORD. ASSERT_EQ(0, spans_received->size()); } @@ -166,20 +171,26 @@ TEST(Tracer, StartSpanWithOptionsTime) TEST(Tracer, StartSpanWithAttributes) { + std::shared_ptr>> spans_received( new std::vector>); // The default tracer has empty sampling result attribute auto tracer = initTracer(spans_received); // Start a span with all supported scalar attribute types. - tracer->StartSpan("span 1", {{"attr1", "string"}, - {"attr2", false}, - {"attr1", 314159}, - {"attr3", (unsigned int)314159}, - {"attr4", (int64_t)-20}, - {"attr5", (uint64_t)20}, - {"attr6", 3.1}, - {"attr7", "string"}}); + + tracer + ->StartSpan("span 1", {{"attr1", "string"}, + {"attr2", false}, + {"attr1", 314159}, + {"attr3", (unsigned int)314159}, + {"attr4", (int64_t)-20}, + {"attr5", (uint64_t)20}, + {"attr6", 3.1}, + {"attr7", "string"}}) + ->End(); + + ASSERT_EQ(1, spans_received->size()); // Start a span with all supported array attribute types. int listInt[] = {1, 2, 3}; @@ -197,7 +208,8 @@ TEST(Tracer, StartSpanWithAttributes) m["attr5"] = nostd::span(listDouble); m["attr6"] = nostd::span(listBool); m["attr7"] = nostd::span(listStringView); - tracer->StartSpan("span 2", m); + + tracer->StartSpan("span 2", m)->End(); ASSERT_EQ(2, spans_received->size()); @@ -248,8 +260,10 @@ TEST(Tracer, StartSpanWithAttributesCopy) strings->push_back(s1); strings->push_back(s2); strings->push_back(s3); - tracer->StartSpan("span 1", - {{"attr1", *numbers}, {"attr2", nostd::span(*strings)}}); + tracer + ->StartSpan("span 1", + {{"attr1", *numbers}, {"attr2", nostd::span(*strings)}}) + ->End(); } ASSERT_EQ(1, spans_received->size()); @@ -379,3 +393,28 @@ TEST(Tracer, TestParentOrElseSampler) span_parent_off_2->End(); ASSERT_EQ(0, spans_received_parent_off->size()); } + +TEST(Tracer, StartSpanUpdatesRuntimeContext) +{ + + std::shared_ptr>> spans_received( + new std::vector>); + auto tracer = initTracer(spans_received); + + auto span_first = tracer->StartSpan("span 1"); + auto span_second = tracer->StartSpan("span 2"); + + EXPECT_EQ(0, spans_received->size()); + + nostd::get>( + context::RuntimeContext::GetCurrent().GetValue(SpanKey)) + ->End(); + EXPECT_EQ(1, spans_received->size()); + EXPECT_EQ("span 2", spans_received->at(0)->GetName()); + + nostd::get>( + context::RuntimeContext::GetCurrent().GetValue(SpanKey)) + ->End(); + EXPECT_EQ(2, spans_received->size()); + EXPECT_EQ("span 1", spans_received->at(1)->GetName()); +}