From 836680dc66da12b9142a0fa7b928192148276343 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Tue, 12 Dec 2023 10:08:43 +0100 Subject: [PATCH 01/31] add stream_summary with space saving algorithm Signed-off-by: thomas.ebner --- .../opentelemetry/samplers/dynatrace/BUILD | 1 + .../samplers/dynatrace/dynatrace_sampler.cc | 13 +- .../samplers/dynatrace/dynatrace_sampler.h | 4 + .../samplers/dynatrace/stream_summary.h | 225 ++++++++++++++++++ 4 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD b/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD index 551c83ca06da9..4c249216a1d17 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD @@ -31,6 +31,7 @@ envoy_cc_library( "dynatrace_sampler.h", "sampler_config.h", "sampler_config_fetcher.h", + "stream_summary.h", ], deps = [ "//source/common/config:datasource_lib", diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index 47777bc35aee6..866ed0465bee3 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -21,14 +21,13 @@ DynatraceSampler::DynatraceSampler( const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig& config, Server::Configuration::TracerFactoryContext& context) : tenant_id_(config.tenant_id()), cluster_id_(config.cluster_id()), - dt_tracestate_key_(absl::StrCat(absl::string_view(config.tenant_id()), "-", - absl::string_view(config.cluster_id()), "@dt")), - sampler_config_fetcher_(context, config.http_uri(), config.token()), counter_(0) {} + dt_tracestate_entry_(tenant_id_, cluster_id_), + sampler_config_fetcher_(context, config.http_uri(), config.token()), stream_summary_(100), counter_(0) {} SamplingResult DynatraceSampler::shouldSample(const absl::optional parent_context, const std::string& /*trace_id*/, const std::string& /*name*/, OTelSpanKind /*kind*/, - OptRef /*trace_context*/, + OptRef trace_context, const std::vector& /*links*/) { SamplingResult result; @@ -48,6 +47,12 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional result.tracestate = parent_context->tracestate(); } } else { // make a sampling decision + + { + Thread::LockGuard lock(mutex_); + const std::string sampling_key(trace_context->path()); + stream_summary_.offer(sampling_key); + } // this is just a demo, we sample every second request here uint32_t current_counter = ++counter_; bool sample; diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index 9c12458b1357e..442ae0d1c00f2 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -4,8 +4,10 @@ #include "envoy/server/factory_context.h" #include "source/common/common/logger.h" +#include "source/common/common/thread.h" #include "source/common/config/datasource.h" #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h" #include "source/extensions/tracers/opentelemetry/samplers/sampler.h" namespace Envoy { @@ -75,6 +77,8 @@ class DynatraceSampler : public Sampler, Logger::Loggable { std::string cluster_id_; std::string dt_tracestate_key_; SamplerConfigFetcher sampler_config_fetcher_; + StreamSummary stream_summary_; + Thread::MutexBasicLockable mutex_{}; std::atomic counter_; // request counter for dummy sampling }; diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h new file mode 100644 index 0000000000000..82c10f84f0fec --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h @@ -0,0 +1,225 @@ +#pragma once + +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/types/optional.h" + +// port of https://github.com/fzakaria/space-saving/tree/master + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +namespace detail { + +template struct Bucket; + +template using BucketIterator = typename std::list>::iterator; + +template struct Counter { + BucketIterator bucket; + absl::optional item{}; + uint64_t value{}; + uint64_t error{}; + + explicit Counter(BucketIterator bucket) : bucket(bucket) {} + Counter(Counter const&) = delete; + Counter& operator=(Counter const&) = delete; +}; + +template using CounterIterator = typename std::list>::iterator; + +template struct Bucket { + uint64_t value; + std::list> children{}; + + explicit Bucket(uint64_t value) : value(value) {} + Bucket(Bucket const&) = delete; + Bucket& operator=(Bucket const&) = delete; +}; + +} // namespace detail + +template class Counter { +private: + T const& item_; + uint64_t value_; + uint64_t error_; + +public: + Counter(detail::Counter const& c) : item_(*c.item), value_(c.value), error_(c.error) {} + + T const& getItem() const { return item_; } + uint64_t getValue() const { return value_; } + uint64_t getError() const { return error_; } +}; + +template class StreamSummary { +private: + const size_t capacity_; + uint64_t n_{}; + absl::flat_hash_map> cache_{}; + std::list> buckets_{}; + + typename detail::CounterIterator incrementCounter(detail::CounterIterator counter_iter, + const uint64_t increment) { + auto const bucket = counter_iter->bucket; + auto bucketNext = std::prev(bucket); + counter_iter->value += increment; + + detail::CounterIterator elem; + if (bucketNext != buckets_.end() && counter_iter->value == bucketNext->value) { + counter_iter->bucket = bucketNext; + bucketNext->children.splice(bucketNext->children.end(), bucket->children, counter_iter); + elem = std::prev(bucketNext->children.end()); + validate(); + } else { + auto bucketNew = buckets_.emplace(bucket, counter_iter->value); + counter_iter->bucket = bucketNew; + bucketNew->children.splice(bucketNew->children.end(), bucket->children, counter_iter); + elem = std::prev(bucketNew->children.end()); + validate(); + } + if (bucket->children.empty()) { + buckets_.erase(bucket); + } + return elem; + } + + void validate() const { + auto cache_copy = cache_; + auto current_bucket = buckets_.begin(); + while (current_bucket != buckets_.end()) { + auto prev = std::prev(current_bucket); + if (prev != buckets_.end() && prev->value <= current_bucket->value) { + // throw std::runtime_error("buckets should be in descending order."); + // TODO + return; + } + auto current_child = current_bucket->children.begin(); + while (current_child != current_bucket->children.end()) { + if (current_child->bucket != current_bucket) { + // throw std::runtime_error("entry should point to its bucket."); + // TODO + return; + } + if (current_child->value != current_bucket->value) { + // throw std::runtime_error("entry and bucket should have the same value."); + // TODO + return; + } + if (current_child->item) { + auto old_iter = cache_copy.find(*current_child->item); + if (old_iter != cache_copy.end()) { + cache_copy.erase(old_iter); + } + } + current_child++; + } + current_bucket++; + } + if (!cache_copy.empty()) { + // throw std::runtime_error("there should be no dead cached entries."); + // TODO + return; + } + } + +public: + StreamSummary(const size_t capacity) : capacity_(capacity) { + auto& newBucket = buckets_.emplace_back(0); + for (size_t i = 0; i < capacity; ++i) { + newBucket.children.emplace_back(buckets_.begin()); + newBucket.children.emplace_back(buckets_.begin()); + } + validate(); + } + + size_t getCapacity() const { return capacity_; } + + Counter offer(T const& item, const uint64_t increment = 1) { + // TODO + // if (cache_.size() > capacity_) { + // throw std::runtime_error("Capacity of the cache should be bounded."); + // } + + validate(); + + ++n_; + auto iter = cache_.find(item); + if (iter != cache_.end()) { + iter->second = incrementCounter(iter->second, increment); + validate(); + return *iter->second; + } else { + auto minElement = std::prev(buckets_.back().children.end()); + auto originalMinValue = minElement->value; + if (minElement->item) { + // remove old from cache + auto old_iter = cache_.find(*minElement->item); + if (old_iter != cache_.end()) { + cache_.erase(old_iter); + } + } + minElement->item = item; + minElement = incrementCounter(minElement, increment); + cache_[item] = minElement; + // if we aren't full on capacity yet, we don't need to add error since we have seen every item + // so far + if (cache_.size() <= capacity_) { + minElement->error = originalMinValue; + } + validate(); + return *minElement; + } + } + + uint64_t getN() const { return n_; } + + typename std::list> getTopK(const size_t k = SIZE_MAX) const { + std::list> r; + for (auto const& bucket : buckets_) { + for (auto const& child : bucket.children) { + if (child.item) { + r.emplace_back(child); + if (r.size() == k) { + return r; + } + } + } + } + return r; + } + + // this makes using the error somewhat useless + void scaleDown(const float factor) { + n_ = 0; + for (auto bucket_iter = buckets_.begin(); bucket_iter != buckets_.end(); bucket_iter++) { + bucket_iter->value = std::max(bucket_iter->value / factor, 1.); + for (auto& child : bucket_iter->children) { + child.value = std::max(child.value / factor, 1.); + n_ += child.value; + child.error /= factor; + } + auto prev = std::prev(bucket_iter); + if (prev != buckets_.end() && prev->value == bucket_iter->value) { + std::for_each(bucket_iter->children.begin(), bucket_iter->children.end(), + [&prev](detail::Counter& c) { c.bucket = prev; }); + prev->children.splice(prev->children.end(), bucket_iter->children, + bucket_iter->children.begin(), bucket_iter->children.end()); + buckets_.erase(bucket_iter); + bucket_iter = prev; + } + } + validate(); + } +}; + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy From ccc58126a1fae06e732f6c26a71b7ff7b4823e04 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Tue, 12 Dec 2023 13:13:53 +0100 Subject: [PATCH 02/31] add simple getSamplingKey() method Signed-off-by: thomas.ebner --- .../samplers/dynatrace/dynatrace_sampler.cc | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index 866ed0465bee3..9e8a5e849ae83 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -9,13 +9,26 @@ #include "source/extensions/tracers/opentelemetry/span_context.h" #include "source/extensions/tracers/opentelemetry/trace_state.h" +#include "absl/strings/str_cat.h" + namespace Envoy { namespace Extensions { namespace Tracers { namespace OpenTelemetry { -static const char* SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME = - "sampling_extrapolation_set_in_sampler"; +namespace { + +const char* SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME = "sampling_extrapolation_set_in_sampler"; + +std::string getSamplingKey(const Tracing::TraceContext& trace_context) { + const auto method = trace_context.method(); + size_t query_offset = trace_context.path().find('?'); + auto path = trace_context.path().substr( + 0, query_offset != trace_context.path().npos ? query_offset : trace_context.path().size()); + return absl::StrCat(method, "_", path); +} + +} // namespace DynatraceSampler::DynatraceSampler( const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig& config, @@ -48,10 +61,9 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional } } else { // make a sampling decision - { + if (trace_context.has_value()) { Thread::LockGuard lock(mutex_); - const std::string sampling_key(trace_context->path()); - stream_summary_.offer(sampling_key); + stream_summary_.offer(getSamplingKey(trace_context.value())); } // this is just a demo, we sample every second request here uint32_t current_counter = ++counter_; From 3e852c14d237660791e07dc775ccdb539143053c Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Wed, 13 Dec 2023 12:47:08 +0100 Subject: [PATCH 03/31] fix initialization: remove (unintentionally) duplicated line Signed-off-by: thomas.ebner --- .../samplers/dynatrace/stream_summary.h | 45 ++++++++----------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h index 82c10f84f0fec..0082eb9e81024 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h @@ -5,6 +5,7 @@ #include #include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" #include "absl/types/optional.h" // port of https://github.com/fzakaria/space-saving/tree/master @@ -76,13 +77,13 @@ template class StreamSummary { counter_iter->bucket = bucketNext; bucketNext->children.splice(bucketNext->children.end(), bucket->children, counter_iter); elem = std::prev(bucketNext->children.end()); - validate(); + // validate(); } else { auto bucketNew = buckets_.emplace(bucket, counter_iter->value); counter_iter->bucket = bucketNew; bucketNew->children.splice(bucketNew->children.end(), bucket->children, counter_iter); elem = std::prev(bucketNew->children.end()); - validate(); + // validate(); } if (bucket->children.empty()) { buckets_.erase(bucket); @@ -90,27 +91,21 @@ template class StreamSummary { return elem; } - void validate() const { + absl::Status validate_internal() const { auto cache_copy = cache_; auto current_bucket = buckets_.begin(); while (current_bucket != buckets_.end()) { auto prev = std::prev(current_bucket); if (prev != buckets_.end() && prev->value <= current_bucket->value) { - // throw std::runtime_error("buckets should be in descending order."); - // TODO - return; + return absl::InternalError("buckets should be in descending order."); } auto current_child = current_bucket->children.begin(); while (current_child != current_bucket->children.end()) { if (current_child->bucket != current_bucket) { - // throw std::runtime_error("entry should point to its bucket."); - // TODO - return; + return absl::InternalError("entry should point to its bucket."); } if (current_child->value != current_bucket->value) { - // throw std::runtime_error("entry and bucket should have the same value."); - // TODO - return; + return absl::InternalError("entry and bucket should have the same value."); } if (current_child->item) { auto old_iter = cache_copy.find(*current_child->item); @@ -123,10 +118,12 @@ template class StreamSummary { current_bucket++; } if (!cache_copy.empty()) { - // throw std::runtime_error("there should be no dead cached entries."); - // TODO - return; + return absl::InternalError("there should be no dead cached entries."); } + if (cache_.size() > capacity_) { + return absl::InternalError("cache size must not exceed capacity"); + } + return absl::OkStatus(); } public: @@ -134,26 +131,22 @@ template class StreamSummary { auto& newBucket = buckets_.emplace_back(0); for (size_t i = 0; i < capacity; ++i) { newBucket.children.emplace_back(buckets_.begin()); - newBucket.children.emplace_back(buckets_.begin()); } - validate(); + // validate(); } size_t getCapacity() const { return capacity_; } - Counter offer(T const& item, const uint64_t increment = 1) { - // TODO - // if (cache_.size() > capacity_) { - // throw std::runtime_error("Capacity of the cache should be bounded."); - // } + absl::Status validate() const { return validate_internal(); } - validate(); + Counter offer(T const& item, const uint64_t increment = 1) { + // validate(); ++n_; auto iter = cache_.find(item); if (iter != cache_.end()) { iter->second = incrementCounter(iter->second, increment); - validate(); + // validate(); return *iter->second; } else { auto minElement = std::prev(buckets_.back().children.end()); @@ -173,7 +166,7 @@ template class StreamSummary { if (cache_.size() <= capacity_) { minElement->error = originalMinValue; } - validate(); + // validate(); return *minElement; } } @@ -215,7 +208,7 @@ template class StreamSummary { bucket_iter = prev; } } - validate(); + // validate(); } }; From f817c1394a00ce18ab923f7a36f0bf3ac35f1e57 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Wed, 13 Dec 2023 12:48:41 +0100 Subject: [PATCH 04/31] add timer to query topK() Signed-off-by: thomas.ebner --- .../samplers/dynatrace/dynatrace_sampler.cc | 15 ++++++++++++++- .../samplers/dynatrace/dynatrace_sampler.h | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index 9e8a5e849ae83..72db446402d49 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -35,7 +35,19 @@ DynatraceSampler::DynatraceSampler( Server::Configuration::TracerFactoryContext& context) : tenant_id_(config.tenant_id()), cluster_id_(config.cluster_id()), dt_tracestate_entry_(tenant_id_, cluster_id_), - sampler_config_fetcher_(context, config.http_uri(), config.token()), stream_summary_(100), counter_(0) {} + sampler_config_fetcher_(context, config.http_uri(), config.token()), stream_summary_(100), + counter_(0) { + + timer_ = context.serverFactoryContext().mainThreadDispatcher().createTimer([this]() -> void { + auto topK = stream_summary_.getTopK(); + ENVOY_LOG(info, "Hello from sampler timer. topk.size(): {}", topK.size()); + for (auto const& counter : topK) { + ENVOY_LOG(info, "-- {} : {}", counter.getItem(), counter.getValue()); + } + timer_->enableTimer(std::chrono::seconds(20)); + }); + timer_->enableTimer(std::chrono::seconds(10)); +} SamplingResult DynatraceSampler::shouldSample(const absl::optional parent_context, const std::string& /*trace_id*/, @@ -65,6 +77,7 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional Thread::LockGuard lock(mutex_); stream_summary_.offer(getSamplingKey(trace_context.value())); } + // this is just a demo, we sample every second request here uint32_t current_counter = ++counter_; bool sample; diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index 442ae0d1c00f2..66da0bd441b61 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -79,6 +79,7 @@ class DynatraceSampler : public Sampler, Logger::Loggable { SamplerConfigFetcher sampler_config_fetcher_; StreamSummary stream_summary_; Thread::MutexBasicLockable mutex_{}; + Event::TimerPtr timer_; std::atomic counter_; // request counter for dummy sampling }; From a1d5a7767997c7f6dd2137f4f85cac267cb4144a Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Wed, 13 Dec 2023 12:49:28 +0100 Subject: [PATCH 05/31] add initial unit test Signed-off-by: thomas.ebner --- .../opentelemetry/samplers/dynatrace/BUILD | 1 + .../samplers/dynatrace/stream_summary_test.cc | 87 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD b/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD index b989c97dc11d2..0362e66835f58 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD @@ -30,6 +30,7 @@ envoy_extension_cc_test( "dynatrace_sampler_test.cc", "sampler_config_fetcher_test.cc", "sampler_config_test.cc", + "stream_summary_test.cc", ], extension_names = ["envoy.tracers.opentelemetry.samplers.dynatrace"], deps = [ diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc new file mode 100644 index 0000000000000..01baeccc71a42 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc @@ -0,0 +1,87 @@ +#include +#include +#include + +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +TEST(StreamSummaryTest, TestEmpty) { + StreamSummary summary(4); + EXPECT_EQ(summary.getN(), 0); + auto topK = summary.getTopK(); + EXPECT_EQ(topK.size(), 0); + EXPECT_EQ(topK.begin(), topK.end()); + EXPECT_TRUE(summary.validate().ok()); +} + +TEST(StreamSummaryTest, TestSimple) { + StreamSummary summary(4); + summary.offer(4); + summary.offer(4); + summary.offer(4); + summary.offer(3); + summary.offer(3); + summary.offer(4); + summary.offer(1); + summary.offer(2); + + EXPECT_TRUE(summary.validate().ok()); + EXPECT_EQ(summary.getN(), 8); + + auto topK = summary.getTopK(); + EXPECT_EQ(topK.size(), 4); + auto it = topK.begin(); + EXPECT_EQ(it->getValue(), 4); + EXPECT_EQ(it->getItem(), 4); + it++; + EXPECT_EQ(it->getValue(), 2); + EXPECT_EQ(it->getItem(), 3); + it++; + EXPECT_EQ(it->getValue(), 1); + EXPECT_EQ(it->getItem(), 1); + it++; + EXPECT_EQ(it->getValue(), 1); + EXPECT_EQ(it->getItem(), 2); +} + +template std::string toString(T const& list) { + std::ostringstream oss; + for (auto const& counter : list) { + oss << counter.getItem() << "(" << counter.getValue() << ")" + << " "; + } + return oss.str(); +} + +TEST(StreamSummaryTest, TestExtendCapacity) { + StreamSummary summary(3); + summary.offer(0); + EXPECT_TRUE(summary.validate().ok()); + summary.offer(1); + summary.offer(4); + summary.offer(4); + summary.offer(4); + summary.offer(4); + summary.offer(3); + summary.offer(3); + summary.offer(3); + summary.offer(2); + summary.offer(2); + EXPECT_TRUE(summary.validate().ok()); + + auto topK = summary.getTopK(); + EXPECT_EQ(topK.size(), 3); + std::cout << __LINE__ << ": " << toString(summary.getTopK()) << std::endl; +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy From 927e026f288e21c3718d466d0c03c2ed5d49a2e1 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Thu, 14 Dec 2023 07:12:23 +0100 Subject: [PATCH 06/31] extend checks in validate(), extend unit test Signed-off-by: thomas.ebner --- .../samplers/dynatrace/stream_summary.h | 12 +- .../samplers/dynatrace/stream_summary_test.cc | 140 +++++++++++++----- 2 files changed, 113 insertions(+), 39 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h index 0082eb9e81024..83caf0abcf5e3 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h @@ -91,9 +91,10 @@ template class StreamSummary { return elem; } - absl::Status validate_internal() const { + absl::Status validateInternal() const { auto cache_copy = cache_; auto current_bucket = buckets_.begin(); + uint64_t value_sum = 0; while (current_bucket != buckets_.end()) { auto prev = std::prev(current_bucket); if (prev != buckets_.end() && prev->value <= current_bucket->value) { @@ -113,6 +114,7 @@ template class StreamSummary { cache_copy.erase(old_iter); } } + value_sum += current_child->value; current_child++; } current_bucket++; @@ -123,6 +125,9 @@ template class StreamSummary { if (cache_.size() > capacity_) { return absl::InternalError("cache size must not exceed capacity"); } + if (value_sum != n_) { + return absl::InternalError("sum of all counter->value() must be equal to n"); + } return absl::OkStatus(); } @@ -137,7 +142,7 @@ template class StreamSummary { size_t getCapacity() const { return capacity_; } - absl::Status validate() const { return validate_internal(); } + absl::Status validate() const { return validateInternal(); } Counter offer(T const& item, const uint64_t increment = 1) { // validate(); @@ -161,6 +166,9 @@ template class StreamSummary { minElement->item = item; minElement = incrementCounter(minElement, increment); cache_[item] = minElement; + // TODO: I think the following comment and `if (cache_.size() <= capacity_)` do not make + // sense. + // TODO: cache_.size() has to be <= capacity! // if we aren't full on capacity yet, we don't need to add error since we have seen every item // so far if (cache_.size() <= capacity_) { diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc index 01baeccc71a42..b5ef5a5494f88 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc @@ -1,6 +1,10 @@ +#include #include +#include #include +#include #include +#include #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h" @@ -12,8 +16,21 @@ namespace Extensions { namespace Tracers { namespace OpenTelemetry { +namespace { + +template +void CompareCounter(typename std::list>::iterator counter, T item, uint64_t value, + uint64_t error, int line_num) { + SCOPED_TRACE(absl::StrCat(__FUNCTION__, " called from line ", line_num)); + EXPECT_EQ(counter->getValue(), value); + EXPECT_EQ(counter->getItem(), item); + EXPECT_EQ(counter->getError(), error); +} + +} // namespace + TEST(StreamSummaryTest, TestEmpty) { - StreamSummary summary(4); + StreamSummary summary(4); EXPECT_EQ(summary.getN(), 0); auto topK = summary.getTopK(); EXPECT_EQ(topK.size(), 0); @@ -22,15 +39,15 @@ TEST(StreamSummaryTest, TestEmpty) { } TEST(StreamSummaryTest, TestSimple) { - StreamSummary summary(4); - summary.offer(4); - summary.offer(4); - summary.offer(4); - summary.offer(3); - summary.offer(3); - summary.offer(4); - summary.offer(1); - summary.offer(2); + StreamSummary summary(4); + summary.offer('a'); + summary.offer('a'); + summary.offer('b'); + summary.offer('a'); + summary.offer('c'); + summary.offer('b'); + summary.offer('a'); + summary.offer('d'); EXPECT_TRUE(summary.validate().ok()); EXPECT_EQ(summary.getN(), 8); @@ -38,47 +55,96 @@ TEST(StreamSummaryTest, TestSimple) { auto topK = summary.getTopK(); EXPECT_EQ(topK.size(), 4); auto it = topK.begin(); - EXPECT_EQ(it->getValue(), 4); - EXPECT_EQ(it->getItem(), 4); - it++; - EXPECT_EQ(it->getValue(), 2); - EXPECT_EQ(it->getItem(), 3); - it++; - EXPECT_EQ(it->getValue(), 1); - EXPECT_EQ(it->getItem(), 1); - it++; - EXPECT_EQ(it->getValue(), 1); - EXPECT_EQ(it->getItem(), 2); + CompareCounter(it, 'a', 4, 0, __LINE__); + CompareCounter(++it, 'b', 2, 0, __LINE__); + CompareCounter(++it, 'c', 1, 0, __LINE__); + CompareCounter(++it, 'd', 1, 0, __LINE__); } +// TODO: remove template std::string toString(T const& list) { std::ostringstream oss; for (auto const& counter : list) { - oss << counter.getItem() << "(" << counter.getValue() << ")" + oss << counter.getItem() << ":(" << counter.getValue() << "/" << counter.getError() << ")" << " "; } return oss.str(); } TEST(StreamSummaryTest, TestExtendCapacity) { - StreamSummary summary(3); - summary.offer(0); + StreamSummary summary(3); EXPECT_TRUE(summary.validate().ok()); - summary.offer(1); - summary.offer(4); - summary.offer(4); - summary.offer(4); - summary.offer(4); - summary.offer(3); - summary.offer(3); - summary.offer(3); - summary.offer(2); - summary.offer(2); + summary.offer('d'); + summary.offer('a'); + summary.offer('b'); + summary.offer('a'); + summary.offer('a'); + summary.offer('a'); + summary.offer('b'); + summary.offer('c'); + summary.offer('b'); + summary.offer('c'); EXPECT_TRUE(summary.validate().ok()); - auto topK = summary.getTopK(); - EXPECT_EQ(topK.size(), 3); - std::cout << __LINE__ << ": " << toString(summary.getTopK()) << std::endl; + { + auto topK = summary.getTopK(); + auto it = topK.begin(); + EXPECT_EQ(topK.size(), 3); + CompareCounter(it, 'a', 4, 0, __LINE__); + CompareCounter(++it, 'b', 3, 0, __LINE__); + CompareCounter(++it, 'c', 3, 1, __LINE__); + std::cout << __LINE__ << ": " << toString(summary.getTopK()) << std::endl; + } + + // add item 'e', 'c' should be removed. + summary.offer('e'); + { + auto topK = summary.getTopK(); + auto it = topK.begin(); + EXPECT_EQ(topK.size(), 3); + CompareCounter(it, 'a', 4, 0, __LINE__); + CompareCounter(++it, 'e', 4, 3, __LINE__); + CompareCounter(++it, 'b', 3, 0, __LINE__); + } +} + +TEST(StreamSummaryTest, TestRandomInsertOrder) { + std::vector v{'a', 'a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b', + 'c', 'c', 'c', 'c', 'd', 'd', 'd', 'e', 'e', 'f'}; + for (int i = 0; i < 5; ++i) { + // insert order should not matter if all items have a different count in input stream + std::shuffle(v.begin(), v.end(), std::default_random_engine()); + StreamSummary summary(10); + for (auto const c : v) { + summary.offer(c); + } + auto topK = summary.getTopK(); + auto it = topK.begin(); + CompareCounter(it, 'a', 6, 0, __LINE__); + CompareCounter(++it, 'b', 5, 0, __LINE__); + CompareCounter(++it, 'c', 4, 0, __LINE__); + CompareCounter(++it, 'd', 3, 0, __LINE__); + CompareCounter(++it, 'e', 2, 0, __LINE__); + CompareCounter(++it, 'f', 1, 0, __LINE__); + } +} + +TEST(StreamSummaryTest, TestGetK) { + std::vector v{'a', 'a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b', + 'c', 'c', 'c', 'c', 'd', 'd', 'd', 'e', 'e', 'f'}; + std::shuffle(v.begin(), v.end(), std::default_random_engine()); + StreamSummary summary(20); + for (auto const c : v) { + summary.offer(c); + } + EXPECT_EQ(summary.getTopK().size(), 6); + EXPECT_EQ(summary.getTopK(1).size(), 1); + EXPECT_EQ(summary.getTopK(2).size(), 2); + EXPECT_EQ(summary.getTopK(3).size(), 3); + EXPECT_EQ(summary.getTopK(4).size(), 4); + EXPECT_EQ(summary.getTopK(5).size(), 5); + EXPECT_EQ(summary.getTopK(6).size(), 6); + EXPECT_EQ(summary.getTopK(7).size(), 6); } } // namespace OpenTelemetry From 507c8edc74825c9abe55b727e4884cf040650429 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Fri, 15 Dec 2023 13:35:34 +0100 Subject: [PATCH 07/31] Add Sampling controller (currently not funcitonal) Signed-off-by: thomas.ebner --- .../opentelemetry/samplers/dynatrace/BUILD | 1 + .../samplers/dynatrace/dynatrace_sampler.cc | 16 ++-- .../samplers/dynatrace/dynatrace_sampler.h | 2 + .../samplers/dynatrace/sampling_controller.h | 82 +++++++++++++++++++ .../opentelemetry/samplers/dynatrace/BUILD | 1 + .../dynatrace/sampling_controller_test.cc | 24 ++++++ 6 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h create mode 100644 test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD b/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD index 4c249216a1d17..aa55876afb244 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD @@ -31,6 +31,7 @@ envoy_cc_library( "dynatrace_sampler.h", "sampler_config.h", "sampler_config_fetcher.h", + "sampling_controller.h", "stream_summary.h", ], deps = [ diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index 72db446402d49..003260da3f4f2 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -20,11 +20,10 @@ namespace { const char* SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME = "sampling_extrapolation_set_in_sampler"; -std::string getSamplingKey(const Tracing::TraceContext& trace_context) { - const auto method = trace_context.method(); - size_t query_offset = trace_context.path().find('?'); - auto path = trace_context.path().substr( - 0, query_offset != trace_context.path().npos ? query_offset : trace_context.path().size()); +std::string getSamplingKey(const absl::string_view path_query, const absl::string_view method) { + size_t query_offset = path_query.find('?'); + auto path = + path_query.substr(0, query_offset != path_query.npos ? query_offset : path_query.size()); return absl::StrCat(method, "_", path); } @@ -36,7 +35,7 @@ DynatraceSampler::DynatraceSampler( : tenant_id_(config.tenant_id()), cluster_id_(config.cluster_id()), dt_tracestate_entry_(tenant_id_, cluster_id_), sampler_config_fetcher_(context, config.http_uri(), config.token()), stream_summary_(100), - counter_(0) { + sampling_controller_(), counter_(0) { timer_ = context.serverFactoryContext().mainThreadDispatcher().createTimer([this]() -> void { auto topK = stream_summary_.getTopK(); @@ -45,6 +44,9 @@ DynatraceSampler::DynatraceSampler( ENVOY_LOG(info, "-- {} : {}", counter.getItem(), counter.getValue()); } timer_->enableTimer(std::chrono::seconds(20)); + auto n = stream_summary_.getN(); + sampling_controller_.update(topK, n, + sampler_config_fetcher_.getSamplerConfig().getRootSpansPerMinute()); }); timer_->enableTimer(std::chrono::seconds(10)); } @@ -75,7 +77,7 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional if (trace_context.has_value()) { Thread::LockGuard lock(mutex_); - stream_summary_.offer(getSamplingKey(trace_context.value())); + stream_summary_.offer(getSamplingKey(trace_context->path(), trace_context->method())); } // this is just a demo, we sample every second request here diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index 66da0bd441b61..818c21beedc7c 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -7,6 +7,7 @@ #include "source/common/common/thread.h" #include "source/common/config/datasource.h" #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h" #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h" #include "source/extensions/tracers/opentelemetry/samplers/sampler.h" @@ -80,6 +81,7 @@ class DynatraceSampler : public Sampler, Logger::Loggable { StreamSummary stream_summary_; Thread::MutexBasicLockable mutex_{}; Event::TimerPtr timer_; + SamplingController sampling_controller_; std::atomic counter_; // request counter for dummy sampling }; diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h new file mode 100644 index 0000000000000..4f1824bb68dde --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +struct SamplingState { + uint32_t exponent_; + + [[nodiscard]] static uint32_t toMultiplicity(uint32_t exponent) { return 1 << exponent; } + [[nodiscard]] uint32_t getExponent() const { return exponent_; } + [[nodiscard]] uint32_t getMultiplicity() const { return toMultiplicity(exponent_); } + void increaseExponent() { exponent_++; } +}; + +class SamplingController { + +public: + uint64_t + computeEffectiveCount(const std::list>& top_k, + const absl::flat_hash_map& sampling_exponents) { + uint64_t cnt = 0; + for (auto const& counter : top_k) { + auto sampling_state = sampling_exponents.find(counter.getItem()); + if (sampling_state == sampling_exponents.end()) { + continue; + } + cnt += counter.getValue() / sampling_state->second.getMultiplicity(); + } + return cnt; + } + + void update(const std::list>& top_k, const uint64_t total_requests, + const uint64_t total_wanted) { + (void)total_requests; + (void)total_wanted; + + sampling_exponents_.clear(); + // const double top_k_size = top_k.size(); + for (auto const& counter : top_k) { + sampling_exponents_[counter.getItem()] = {0}; + } + auto effect_count = computeEffectiveCount(top_k, sampling_exponents_); + + while (effect_count > total_wanted) { + for (auto const& counter : top_k) { + auto sampling_state = sampling_exponents_.find(counter.getItem()); + sampling_state->second.increaseExponent(); + effect_count = computeEffectiveCount(top_k, sampling_exponents_); + if (effect_count < total_wanted) { + break; + } + } + } + } + + bool shouldSample(const std::string& sampling_key, const std::string& /*trace_id*/) { + auto iter = sampling_exponents_.find(sampling_key); + if (iter == sampling_exponents_.end()) { + return true; + } + return false; + } + + absl::flat_hash_map getSamplingExponents() { + return sampling_exponents_; + } + +private: + absl::flat_hash_map sampling_exponents_; +}; + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD b/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD index 0362e66835f58..831bba2a3944e 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD @@ -30,6 +30,7 @@ envoy_extension_cc_test( "dynatrace_sampler_test.cc", "sampler_config_fetcher_test.cc", "sampler_config_test.cc", + "sampling_controller_test.cc", "stream_summary_test.cc", ], extension_names = ["envoy.tracers.opentelemetry.samplers.dynatrace"], diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc new file mode 100644 index 0000000000000..6c1fe5e6241d4 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc @@ -0,0 +1,24 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +TEST(SamplingControllerTest, TestEmpty) { SamplingController sc; } + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy From c489c0a00be0d1302eac5d8965ac1596b5c0993b Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Mon, 18 Dec 2023 09:09:45 +0100 Subject: [PATCH 08/31] add initial unit test, minor cleanup Signed-off-by: thomas.ebner --- .../samplers/dynatrace/dynatrace_sampler.cc | 3 +- .../samplers/dynatrace/sampling_controller.h | 17 +++---- .../dynatrace/sampling_controller_test.cc | 47 ++++++++++++++++++- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index 003260da3f4f2..a87c1a3bcd78e 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -44,8 +44,7 @@ DynatraceSampler::DynatraceSampler( ENVOY_LOG(info, "-- {} : {}", counter.getItem(), counter.getValue()); } timer_->enableTimer(std::chrono::seconds(20)); - auto n = stream_summary_.getN(); - sampling_controller_.update(topK, n, + sampling_controller_.update(topK, sampler_config_fetcher_.getSamplerConfig().getRootSpansPerMinute()); }); timer_->enableTimer(std::chrono::seconds(10)); diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h index 4f1824bb68dde..0ad27ab6110c1 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h @@ -17,6 +17,7 @@ struct SamplingState { [[nodiscard]] uint32_t getExponent() const { return exponent_; } [[nodiscard]] uint32_t getMultiplicity() const { return toMultiplicity(exponent_); } void increaseExponent() { exponent_++; } + void decreaseExponent() { exponent_--; } }; class SamplingController { @@ -36,24 +37,24 @@ class SamplingController { return cnt; } - void update(const std::list>& top_k, const uint64_t total_requests, - const uint64_t total_wanted) { - (void)total_requests; - (void)total_wanted; + void update(const std::list>& top_k, const uint64_t total_wanted) { sampling_exponents_.clear(); // const double top_k_size = top_k.size(); for (auto const& counter : top_k) { sampling_exponents_[counter.getItem()] = {0}; } - auto effect_count = computeEffectiveCount(top_k, sampling_exponents_); + auto effective_count = computeEffectiveCount(top_k, sampling_exponents_); - while (effect_count > total_wanted) { + while (effective_count > total_wanted) { for (auto const& counter : top_k) { auto sampling_state = sampling_exponents_.find(counter.getItem()); sampling_state->second.increaseExponent(); - effect_count = computeEffectiveCount(top_k, sampling_exponents_); - if (effect_count < total_wanted) { + effective_count = computeEffectiveCount(top_k, sampling_exponents_); + if (effective_count <= total_wanted) { + // we want to be close to total_wanted, but we don't want less than total_wanted samples. + // Therefore, we decrease the exponent again to keep effective_count > total_wanted + sampling_state->second.decreaseExponent(); break; } } diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc index 6c1fe5e6241d4..62c1038532ae2 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc @@ -7,6 +7,7 @@ #include #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -16,7 +17,51 @@ namespace Extensions { namespace Tracers { namespace OpenTelemetry { -TEST(SamplingControllerTest, TestEmpty) { SamplingController sc; } +class SamplingControllerTest : public testing::Test { + +protected: + std::vector getRequests() { + std::vector requests{}; + for (int i = 0; i < 100; i++) { + requests.push_back("GET_asdf"); + } + for (int i = 0; i < 200; i++) { + requests.push_back("POST_asdf"); + } + for (int i = 0; i < 300; i++) { + requests.push_back("GET_xxxx"); + } + return requests; + } +}; + +TEST_F(SamplingControllerTest, TestSimple) { + std::vector requests = getRequests(); + StreamSummary summary(10); + for (auto const& c : requests) { + summary.offer(c); + } + SamplingController sc; + sc.update(summary.getTopK(), 100); + + auto ex = sc.getSamplingExponents(); + EXPECT_EQ(ex["GET_asdf"].getExponent(), 2); + EXPECT_EQ(ex["GET_asdf"].getMultiplicity(), 4); + EXPECT_EQ(ex["POST_asdf"].getExponent(), 2); + EXPECT_EQ(ex["POST_asdf"].getMultiplicity(), 4); + EXPECT_EQ(ex["GET_xxxx"].getExponent(), 3); + EXPECT_EQ(ex["GET_xxxx"].getMultiplicity(), 8); + + // total_wanted > number of requests + sc.update(summary.getTopK(), 1000); + ex = sc.getSamplingExponents(); + EXPECT_EQ(ex["GET_asdf"].getExponent(), 0); + EXPECT_EQ(ex["GET_asdf"].getMultiplicity(), 1); + EXPECT_EQ(ex["POST_asdf"].getExponent(), 0); + EXPECT_EQ(ex["POST_asdf"].getMultiplicity(), 1); + EXPECT_EQ(ex["GET_xxxx"].getExponent(), 0); + EXPECT_EQ(ex["GET_xxxx"].getMultiplicity(), 1); +} } // namespace OpenTelemetry } // namespace Tracers From 41e283cc515751da1d5a997effc2df5ba61a24c8 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Tue, 19 Dec 2023 09:05:44 +0100 Subject: [PATCH 09/31] fix calling shouldSample(). Signed-off-by: thomas.ebner --- source/extensions/tracers/opentelemetry/tracer.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/extensions/tracers/opentelemetry/tracer.cc b/source/extensions/tracers/opentelemetry/tracer.cc index 7f03a8ddcaefd..a9eb59b84e6ae 100644 --- a/source/extensions/tracers/opentelemetry/tracer.cc +++ b/source/extensions/tracers/opentelemetry/tracer.cc @@ -31,7 +31,7 @@ void callSampler(SamplerSharedPtr sampler, const absl::optional spa return; } const auto sampling_result = - sampler->shouldSample(span_context, operation_name, new_span.getTraceIdAsHex(), + sampler->shouldSample(span_context, new_span.getTraceIdAsHex(), operation_name, new_span.spankind(), trace_context, {}); new_span.setSampled(sampling_result.isSampled()); From 92c8bc75504b64b06b1ee13a1f111b1873338150 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Tue, 19 Dec 2023 16:55:56 +0100 Subject: [PATCH 10/31] Use calculated sampling exponent for sampling Signed-off-by: thomas.ebner --- .../samplers/dynatrace/dynatrace_sampler.cc | 62 +++++++++----- .../samplers/dynatrace/dynatrace_sampler.h | 4 +- .../samplers/dynatrace/sampling_controller.h | 82 +++++++++++-------- .../samplers/dynatrace/stream_summary.h | 2 +- .../dynatrace_sampler_integration_test.cc | 6 +- .../dynatrace/dynatrace_sampler_test.cc | 2 +- .../dynatrace/sampling_controller_test.cc | 78 ++++++++++++++---- 7 files changed, 165 insertions(+), 71 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index a87c1a3bcd78e..1c53bfedd30c6 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -4,12 +4,14 @@ #include #include +#include "source/common/common/hash.h" #include "source/common/config/datasource.h" #include "source/extensions/tracers/opentelemetry/samplers/sampler.h" #include "source/extensions/tracers/opentelemetry/span_context.h" #include "source/extensions/tracers/opentelemetry/trace_state.h" #include "absl/strings/str_cat.h" +#include "stream_summary.h" namespace Envoy { namespace Extensions { @@ -18,6 +20,9 @@ namespace OpenTelemetry { namespace { +static constexpr std::chrono::seconds SAMPLING_UPDATE_TIMER_DURATION{20}; +// static constexpr std::chrono::minutes SAMPLING_UPDATE_TIMER_DURATION{1}; + const char* SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME = "sampling_extrapolation_set_in_sampler"; std::string getSamplingKey(const absl::string_view path_query, const absl::string_view method) { @@ -34,24 +39,31 @@ DynatraceSampler::DynatraceSampler( Server::Configuration::TracerFactoryContext& context) : tenant_id_(config.tenant_id()), cluster_id_(config.cluster_id()), dt_tracestate_entry_(tenant_id_, cluster_id_), - sampler_config_fetcher_(context, config.http_uri(), config.token()), stream_summary_(100), - sampling_controller_(), counter_(0) { + sampler_config_fetcher_(context, config.http_uri(), config.token()), + stream_summary_(std::make_unique>(100)), sampling_controller_(), + counter_(0) { timer_ = context.serverFactoryContext().mainThreadDispatcher().createTimer([this]() -> void { - auto topK = stream_summary_.getTopK(); + auto topK = stream_summary_->getTopK(); + // start with a new stream summmary + stream_summary_ = std::make_unique>(100); + ENVOY_LOG(info, "Hello from sampler timer. topk.size(): {}", topK.size()); for (auto const& counter : topK) { ENVOY_LOG(info, "-- {} : {}", counter.getItem(), counter.getValue()); } - timer_->enableTimer(std::chrono::seconds(20)); + ENVOY_LOG(info, "counter_: {}", counter_); + + timer_->enableTimer(SAMPLING_UPDATE_TIMER_DURATION); + // update sampling exponents sampling_controller_.update(topK, sampler_config_fetcher_.getSamplerConfig().getRootSpansPerMinute()); }); - timer_->enableTimer(std::chrono::seconds(10)); + timer_->enableTimer(SAMPLING_UPDATE_TIMER_DURATION); } SamplingResult DynatraceSampler::shouldSample(const absl::optional parent_context, - const std::string& /*trace_id*/, + const std::string& trace_id, const std::string& /*name*/, OTelSpanKind /*kind*/, OptRef trace_context, const std::vector& /*links*/) { @@ -59,6 +71,16 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional SamplingResult result; std::map att; + const std::string sampling_key = + trace_context.has_value() ? getSamplingKey(trace_context->path(), trace_context->method()) + : ""; + + if (sampling_key != "") { + Thread::LockGuard lock(mutex_); + stream_summary_->offer(sampling_key); + } + + auto trace_state = TraceState::fromHeader(parent_context.has_value() ? parent_context->tracestate() : ""); @@ -74,22 +96,24 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional } } else { // make a sampling decision - if (trace_context.has_value()) { - Thread::LockGuard lock(mutex_); - stream_summary_.offer(getSamplingKey(trace_context->path(), trace_context->method())); - } - // this is just a demo, we sample every second request here - uint32_t current_counter = ++counter_; + // uint32_t current_counter = ++counter_; bool sample; int sampling_exponent; - if (current_counter % 2) { - sample = true; - sampling_exponent = 1; - } else { - sample = false; - sampling_exponent = 0; - } + // if (current_counter % 2) { + // sample = true; + // sampling_exponent = 1; + // } else { + // sample = false; + // sampling_exponent = 0; + // } + + // do a decision based on the calculated exponent + // at the moment we use a hash of the trace_id as random number + const auto hash = MurmurHash::murmurHash2(trace_id); + const auto sampling_state = sampling_controller_.getSamplingState(sampling_key); + sample = sampling_state.shouldSample(hash); + sampling_exponent = sampling_state.getExponent(); att[SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME] = std::to_string(sampling_exponent); diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index 818c21beedc7c..1995e59a754a1 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.pb.h" #include "envoy/server/factory_context.h" @@ -78,7 +80,7 @@ class DynatraceSampler : public Sampler, Logger::Loggable { std::string cluster_id_; std::string dt_tracestate_key_; SamplerConfigFetcher sampler_config_fetcher_; - StreamSummary stream_summary_; + std::unique_ptr> stream_summary_; Thread::MutexBasicLockable mutex_{}; Event::TimerPtr timer_; SamplingController sampling_controller_; diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h index 0ad27ab6110c1..a61eec2fe46aa 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h @@ -10,47 +10,45 @@ namespace Extensions { namespace Tracers { namespace OpenTelemetry { -struct SamplingState { - uint32_t exponent_; - +class SamplingState { +public: [[nodiscard]] static uint32_t toMultiplicity(uint32_t exponent) { return 1 << exponent; } [[nodiscard]] uint32_t getExponent() const { return exponent_; } [[nodiscard]] uint32_t getMultiplicity() const { return toMultiplicity(exponent_); } void increaseExponent() { exponent_++; } - void decreaseExponent() { exponent_--; } + void decreaseExponent() { + if (exponent_ > 0) { + exponent_--; + } + } + + bool shouldSample(const uint64_t random_nr) const { return (random_nr % getMultiplicity() == 0); } + +private: + uint32_t exponent_{}; }; class SamplingController { public: - uint64_t - computeEffectiveCount(const std::list>& top_k, - const absl::flat_hash_map& sampling_exponents) { - uint64_t cnt = 0; - for (auto const& counter : top_k) { - auto sampling_state = sampling_exponents.find(counter.getItem()); - if (sampling_state == sampling_exponents.end()) { - continue; - } - cnt += counter.getValue() / sampling_state->second.getMultiplicity(); - } - return cnt; - } - void update(const std::list>& top_k, const uint64_t total_wanted) { - sampling_exponents_.clear(); - // const double top_k_size = top_k.size(); + absl::flat_hash_map new_sampling_exponents; + // start with sampling exponent 0, which means multiplicity == 1 (every span is sampled) for (auto const& counter : top_k) { - sampling_exponents_[counter.getItem()] = {0}; + new_sampling_exponents[counter.getItem()] = {}; } - auto effective_count = computeEffectiveCount(top_k, sampling_exponents_); + + // use the last entry as "rest bucket", which is used for new/unknown requests + rest_bucket_key_ = (top_k.size() > 0) ? top_k.back().getItem() : ""; + + auto effective_count = computeEffectiveCount(top_k, new_sampling_exponents); while (effective_count > total_wanted) { for (auto const& counter : top_k) { - auto sampling_state = sampling_exponents_.find(counter.getItem()); + auto sampling_state = new_sampling_exponents.find(counter.getItem()); sampling_state->second.increaseExponent(); - effective_count = computeEffectiveCount(top_k, sampling_exponents_); + effective_count = computeEffectiveCount(top_k, new_sampling_exponents); if (effective_count <= total_wanted) { // we want to be close to total_wanted, but we don't want less than total_wanted samples. // Therefore, we decrease the exponent again to keep effective_count > total_wanted @@ -59,22 +57,42 @@ class SamplingController { } } } + // TODO: write_lock + sampling_exponents_ = new_sampling_exponents; } - bool shouldSample(const std::string& sampling_key, const std::string& /*trace_id*/) { + SamplingState getSamplingState(const std::string& sampling_key) { + // TODO: read_lock auto iter = sampling_exponents_.find(sampling_key); - if (iter == sampling_exponents_.end()) { - return true; + if (iter == + sampling_exponents_ + .end()) { // we don't have a sampling exponent for this exponent, use "rest bucket" + auto rest_bucket_iter = sampling_exponents_.find(rest_bucket_key_); + if (rest_bucket_iter != sampling_exponents_.end()) { + return rest_bucket_iter->second; + } + return {}; } - return false; - } - - absl::flat_hash_map getSamplingExponents() { - return sampling_exponents_; + return iter->second; } private: absl::flat_hash_map sampling_exponents_; + std::string rest_bucket_key_{}; + + uint64_t + computeEffectiveCount(const std::list>& top_k, + const absl::flat_hash_map& sampling_exponents) { + uint64_t cnt = 0; + for (auto const& counter : top_k) { + auto sampling_state = sampling_exponents.find(counter.getItem()); + if (sampling_state == sampling_exponents.end()) { + continue; + } + cnt += counter.getValue() / sampling_state->second.getMultiplicity(); + } + return cnt; + } }; } // namespace OpenTelemetry diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h index 83caf0abcf5e3..c2b4ddcb645df 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h @@ -132,7 +132,7 @@ template class StreamSummary { } public: - StreamSummary(const size_t capacity) : capacity_(capacity) { + explicit StreamSummary(const size_t capacity) : capacity_(capacity) { auto& newBucket = buckets_.emplace_back(0); for (size_t i = 0; i < capacity; ++i) { newBucket.children.emplace_back(buckets_.begin()); diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc index 998ce7fab4e55..907d8d01af90b 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc @@ -82,7 +82,7 @@ TEST_P(DynatraceSamplerIntegrationTest, TestWithTraceparentAndTracestate) { .get(Http::LowerCaseString("tracestate"))[0] ->value() .getStringView(); - EXPECT_EQ("9712ad40-980df25c@dt=fw4;0;0;0;0;0;1;0,key=value", tracestate_value); + EXPECT_EQ("9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;0,key=value", tracestate_value); } // Sends a request with traceparent but no tracestate header. @@ -110,7 +110,7 @@ TEST_P(DynatraceSamplerIntegrationTest, TestWithTraceparentOnly) { .get(Http::LowerCaseString("tracestate"))[0] ->value() .getStringView(); - EXPECT_EQ("9712ad40-980df25c@dt=fw4;0;0;0;0;0;1;0", tracestate_value); + EXPECT_EQ("9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;0", tracestate_value); } // Sends a request without traceparent and tracestate header. @@ -133,7 +133,7 @@ TEST_P(DynatraceSamplerIntegrationTest, TestWithoutTraceparentAndTracestate) { .get(Http::LowerCaseString("tracestate"))[0] ->value() .getStringView(); - EXPECT_EQ("9712ad40-980df25c@dt=fw4;0;0;0;0;0;1;0", tracestate_value); + EXPECT_EQ("9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;0", tracestate_value); } } // namespace diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc index 7f1cb68297122..b234a4fb966c3 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc @@ -44,7 +44,7 @@ TEST_F(DynatraceSamplerTest, TestWithoutParentContext) { ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); EXPECT_EQ(sampling_result.attributes->size(), 1); - EXPECT_STREQ(sampling_result.tracestate.c_str(), "9712ad40-980df25c@dt=fw4;0;0;0;0;0;1;0"); + EXPECT_STREQ(sampling_result.tracestate.c_str(), "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;0"); EXPECT_TRUE(sampling_result.isRecording()); EXPECT_TRUE(sampling_result.isSampled()); } diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc index 62c1038532ae2..95e89c911ee41 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc @@ -44,23 +44,73 @@ TEST_F(SamplingControllerTest, TestSimple) { SamplingController sc; sc.update(summary.getTopK(), 100); - auto ex = sc.getSamplingExponents(); - EXPECT_EQ(ex["GET_asdf"].getExponent(), 2); - EXPECT_EQ(ex["GET_asdf"].getMultiplicity(), 4); - EXPECT_EQ(ex["POST_asdf"].getExponent(), 2); - EXPECT_EQ(ex["POST_asdf"].getMultiplicity(), 4); - EXPECT_EQ(ex["GET_xxxx"].getExponent(), 3); - EXPECT_EQ(ex["GET_xxxx"].getMultiplicity(), 8); + EXPECT_EQ(sc.getSamplingState("GET_asdf").getExponent(), 2); + EXPECT_EQ(sc.getSamplingState("GET_asdf").getMultiplicity(), 4); + EXPECT_EQ(sc.getSamplingState("POST_asdf").getExponent(), 2); + EXPECT_EQ(sc.getSamplingState("POST_asdf").getMultiplicity(), 4); + EXPECT_EQ(sc.getSamplingState("GET_xxxx").getExponent(), 3); + EXPECT_EQ(sc.getSamplingState("GET_xxxx").getMultiplicity(), 8); // total_wanted > number of requests sc.update(summary.getTopK(), 1000); - ex = sc.getSamplingExponents(); - EXPECT_EQ(ex["GET_asdf"].getExponent(), 0); - EXPECT_EQ(ex["GET_asdf"].getMultiplicity(), 1); - EXPECT_EQ(ex["POST_asdf"].getExponent(), 0); - EXPECT_EQ(ex["POST_asdf"].getMultiplicity(), 1); - EXPECT_EQ(ex["GET_xxxx"].getExponent(), 0); - EXPECT_EQ(ex["GET_xxxx"].getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("GET_asdf").getExponent(), 0); + EXPECT_EQ(sc.getSamplingState("GET_asdf").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("POST_asdf").getExponent(), 0); + EXPECT_EQ(sc.getSamplingState("POST_asdf").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("GET_xxxx").getExponent(), 0); + EXPECT_EQ(sc.getSamplingState("GET_xxxx").getMultiplicity(), 1); +} + +TEST(SamplingStateTest, TestIncreaseDecrease) { + SamplingState sst{}; + EXPECT_EQ(sst.getExponent(), 0); + EXPECT_EQ(sst.getMultiplicity(), 1); + + sst.increaseExponent(); + EXPECT_EQ(sst.getExponent(), 1); + EXPECT_EQ(sst.getMultiplicity(), 2); + + sst.increaseExponent(); + EXPECT_EQ(sst.getExponent(), 2); + EXPECT_EQ(sst.getMultiplicity(), 4); + + for (int i = 0; i < 6; i++) { + sst.increaseExponent(); + } + EXPECT_EQ(sst.getExponent(), 8); + EXPECT_EQ(sst.getMultiplicity(), 256); + + sst.decreaseExponent(); + EXPECT_EQ(sst.getExponent(), 7); + EXPECT_EQ(sst.getMultiplicity(), 128); +} + +TEST(SamplingStateTest, TestShouldSample) { + // default sampling state should sample + SamplingState sst{}; + EXPECT_TRUE(sst.shouldSample(1234)); + EXPECT_TRUE(sst.shouldSample(3456)); + EXPECT_TRUE(sst.shouldSample(12345)); + + // exponent 2, multiplicity 1, even (=not odd) random numbers should be sampled + sst.increaseExponent(); + EXPECT_TRUE(sst.shouldSample(22)); + EXPECT_TRUE(sst.shouldSample(4444444)); + EXPECT_FALSE(sst.shouldSample(21)); + EXPECT_FALSE(sst.shouldSample(111111)); + + for (int i = 0; i < 9; i++) { + sst.increaseExponent(); + } + // exponent 10, multiplicity 1024, + EXPECT_TRUE(sst.shouldSample(1024)); + EXPECT_TRUE(sst.shouldSample(2048)); + EXPECT_TRUE(sst.shouldSample(4096)); + EXPECT_TRUE(sst.shouldSample(10240000000)); + EXPECT_FALSE(sst.shouldSample(1023)); + EXPECT_FALSE(sst.shouldSample(1025)); + EXPECT_FALSE(sst.shouldSample(2047)); + EXPECT_FALSE(sst.shouldSample(2049)); } } // namespace OpenTelemetry From b956bff5579a81df62f47e953cf0018dc56e63ce Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Wed, 20 Dec 2023 07:36:45 +0100 Subject: [PATCH 11/31] set pathInfo in tracestate (only root path random set) Signed-off-by: thomas.ebner --- .../samplers/dynatrace/dynatrace_sampler.cc | 2 +- .../samplers/dynatrace/dynatrace_sampler.h | 19 ++++--- .../dynatrace/dynatrace_tracestate.cc | 47 ++++++++++++++++ .../samplers/dynatrace/dynatrace_tracestate.h | 55 +++++++++++++++++++ .../dynatrace_sampler_integration_test.cc | 12 +++- .../dynatrace/dynatrace_sampler_test.cc | 4 +- .../dynatrace/dynatrace_tracestate_test.cc | 23 ++++++++ 7 files changed, 149 insertions(+), 13 deletions(-) create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.cc create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h create mode 100644 test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate_test.cc diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index 1c53bfedd30c6..6e42df64a4e2a 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -119,7 +119,7 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional result.decision = sample ? Decision::RecordAndSample : Decision::Drop; // create new forward tag and add it to tracestate - FW4Tag new_tag = FW4Tag::create(!sample, sampling_exponent); + FW4Tag new_tag = FW4Tag::create(!sample, sampling_exponent, static_cast(hash)); trace_state = trace_state->set(dt_tracestate_key_, new_tag.asString()); result.tracestate = trace_state->toHeader(); } diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index 1995e59a754a1..309732c19e660 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -20,16 +20,16 @@ namespace OpenTelemetry { class FW4Tag { public: - static FW4Tag createInvalid() { return {false, false, 0}; } + FW4Tag FW4Tag::createInvalid() { return {false, false, 0, 0}; } - static FW4Tag create(bool ignored, int sampling_exponent) { - return {true, ignored, sampling_exponent}; + FW4Tag FW4Tag::create(bool ignored, int sampling_exponent, int root_path_random_) { + return {true, ignored, sampling_exponent, root_path_random_}; } - static FW4Tag create(const std::string& value) { + static FW4Tag FW4Tag::create(const std::string& value) { std::vector tracestate_components = absl::StrSplit(value, ';', absl::AllowEmpty()); - if (tracestate_components.size() < 7) { + if (tracestate_components.size() < 8) { return createInvalid(); } @@ -38,11 +38,14 @@ class FW4Tag { } bool ignored = tracestate_components[5] == "1"; int sampling_exponent = std::stoi(std::string(tracestate_components[6])); - return {true, ignored, sampling_exponent}; + int root_path_random = std::stoi(std::string(tracestate_components[7]), nullptr, 16); + return {true, ignored, sampling_exponent, root_path_random}; } - std::string asString() const { - return absl::StrCat("fw4;0;0;0;0;", ignored_ ? "1" : "0", ";", sampling_exponent_, ";0"); + std::string FW4Tag::asString() const { + std::string ret = absl::StrCat("fw4;0;0;0;0;", ignored_ ? "1" : "0", ";", sampling_exponent_, ";", + absl::Hex(root_path_random_)); + return ret; } bool isValid() const { return valid_; }; diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.cc new file mode 100644 index 0000000000000..aa84db433ca3f --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.cc @@ -0,0 +1,47 @@ +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h" + +#include +#include + +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +FW4Tag FW4Tag::createInvalid() { return {false, false, 0, 0}; } + +FW4Tag FW4Tag::create(bool ignored, int sampling_exponent, int root_path_random_) { + return {true, ignored, sampling_exponent, root_path_random_}; +} + +FW4Tag FW4Tag::create(const std::string& value) { + std::vector tracestate_components = + absl::StrSplit(value, ';', absl::AllowEmpty()); + if (tracestate_components.size() < 8) { + return createInvalid(); + } + + if (tracestate_components[0] != "fw4") { + return createInvalid(); + } + bool ignored = tracestate_components[5] == "1"; + int sampling_exponent = std::stoi(std::string(tracestate_components[6])); + int root_path_random = std::stoi(std::string(tracestate_components[7]), nullptr, 16); + return {true, ignored, sampling_exponent, root_path_random}; +} + +std::string FW4Tag::asString() const { + std::string ret = absl::StrCat("fw4;0;0;0;0;", ignored_ ? "1" : "0", ";", sampling_exponent_, ";", + absl::Hex(root_path_random_)); + return ret; +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h new file mode 100644 index 0000000000000..7069f0943e961 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h @@ -0,0 +1,55 @@ +#pragma once + +#include + +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +class FW4Tag { +public: + static FW4Tag createInvalid(); + + static FW4Tag create(bool ignored, int sampling_exponent, int root_path_random_); + + static FW4Tag create(const std::string& value); + + std::string asString() const; + + bool isValid() const { return valid_; }; + bool isIgnored() const { return ignored_; }; + int getSamplingExponent() const { return sampling_exponent_; }; + +private: + FW4Tag(bool valid, bool ignored, int sampling_exponent, int root_path_random_) + : valid_(valid), ignored_(ignored), sampling_exponent_(sampling_exponent), + root_path_random_(root_path_random_) {} + + bool valid_; + bool ignored_; + int sampling_exponent_; + int root_path_random_; +}; + +// -@dt=fw4;0;0;0;0;;; +class DtTracestateEntry { +public: + DtTracestateEntry(const std::string& tenant_id, const std::string& cluster_id) { + key_ = absl::StrCat(absl::string_view(tenant_id), "-", absl::string_view(cluster_id), "@dt"); + } + + std::string getKey() const { return key_; }; + + bool keyMatches(const std::string& key) { return (key_.compare(key) == 0); } + +private: + std::string key_; +}; +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc index 907d8d01af90b..e909785a1ad92 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc @@ -82,7 +82,10 @@ TEST_P(DynatraceSamplerIntegrationTest, TestWithTraceparentAndTracestate) { .get(Http::LowerCaseString("tracestate"))[0] ->value() .getStringView(); - EXPECT_EQ("9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;0,key=value", tracestate_value); + EXPECT_TRUE(absl::StartsWith(tracestate_value, "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;")) + << "Received tracestate: " << tracestate_value; + EXPECT_TRUE(absl::StrContains(tracestate_value, ",key=value")) + << "Received tracestate: " << tracestate_value; } // Sends a request with traceparent but no tracestate header. @@ -110,7 +113,9 @@ TEST_P(DynatraceSamplerIntegrationTest, TestWithTraceparentOnly) { .get(Http::LowerCaseString("tracestate"))[0] ->value() .getStringView(); - EXPECT_EQ("9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;0", tracestate_value); + // use StartsWith because pathinfo (last element in trace state contains a random value) + EXPECT_TRUE(absl::StartsWith(tracestate_value, "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;")) + << "Received tracestate: " << tracestate_value; } // Sends a request without traceparent and tracestate header. @@ -133,7 +138,8 @@ TEST_P(DynatraceSamplerIntegrationTest, TestWithoutTraceparentAndTracestate) { .get(Http::LowerCaseString("tracestate"))[0] ->value() .getStringView(); - EXPECT_EQ("9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;0", tracestate_value); + EXPECT_TRUE(absl::StartsWith(tracestate_value, "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;")) + << "Received tracestate: " << tracestate_value; } } // namespace diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc index b234a4fb966c3..55f53425b8194 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc @@ -44,7 +44,9 @@ TEST_F(DynatraceSamplerTest, TestWithoutParentContext) { ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); EXPECT_EQ(sampling_result.attributes->size(), 1); - EXPECT_STREQ(sampling_result.tracestate.c_str(), "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;0"); + // EXPECT_TRUE(absl::StartsWith(sampling_result.tracestate, + // "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;")) << "Received tracestate: " << tracestate_value; + EXPECT_STREQ(sampling_result.tracestate.c_str(), "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;f1"); EXPECT_TRUE(sampling_result.isRecording()); EXPECT_TRUE(sampling_result.isSampled()); } diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate_test.cc new file mode 100644 index 0000000000000..3748458d12de4 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate_test.cc @@ -0,0 +1,23 @@ +#include + +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +TEST(DynatraceTracestateTest, TestKey) { + DtTracestateEntry tracestate("98812ad49", "980df25c"); + EXPECT_STREQ(tracestate.getKey().c_str(), "98812ad49-980df25c@dt"); +} + +// TODO: add FW4 TagTests + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy From 612835be4f41ab804b432d63163569933ff6d6b0 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Wed, 20 Dec 2023 10:14:51 +0100 Subject: [PATCH 12/31] FW4Tag handling: change parsing, rename variable, add unit tests Signed-off-by: thomas.ebner --- .../dynatrace/dynatrace_tracestate.cc | 17 ++++-- .../samplers/dynatrace/dynatrace_tracestate.h | 17 +++--- .../dynatrace/dynatrace_tracestate_test.cc | 60 ++++++++++++++++++- 3 files changed, 80 insertions(+), 14 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.cc index aa84db433ca3f..cbeedc8af1ec3 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.cc @@ -4,6 +4,7 @@ #include #include "absl/strings/match.h" +#include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" @@ -15,8 +16,8 @@ namespace OpenTelemetry { FW4Tag FW4Tag::createInvalid() { return {false, false, 0, 0}; } -FW4Tag FW4Tag::create(bool ignored, int sampling_exponent, int root_path_random_) { - return {true, ignored, sampling_exponent, root_path_random_}; +FW4Tag FW4Tag::create(bool ignored, uint32_t sampling_exponent, uint32_t path_info) { + return {true, ignored, sampling_exponent, path_info}; } FW4Tag FW4Tag::create(const std::string& value) { @@ -30,14 +31,18 @@ FW4Tag FW4Tag::create(const std::string& value) { return createInvalid(); } bool ignored = tracestate_components[5] == "1"; - int sampling_exponent = std::stoi(std::string(tracestate_components[6])); - int root_path_random = std::stoi(std::string(tracestate_components[7]), nullptr, 16); - return {true, ignored, sampling_exponent, root_path_random}; + uint32_t sampling_exponent; + uint32_t path_info; + if (absl::SimpleAtoi(tracestate_components[6], &sampling_exponent) && + absl::SimpleHexAtoi(tracestate_components[7], &path_info)) { + return {true, ignored, sampling_exponent, path_info}; + } + return createInvalid(); } std::string FW4Tag::asString() const { std::string ret = absl::StrCat("fw4;0;0;0;0;", ignored_ ? "1" : "0", ";", sampling_exponent_, ";", - absl::Hex(root_path_random_)); + absl::Hex(path_info_)); return ret; } diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h index 7069f0943e961..174f4a0552741 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h @@ -10,11 +10,14 @@ namespace Extensions { namespace Tracers { namespace OpenTelemetry { +// dynatrace trace state entry will be: +// -@dt=fw4;0;0;0;0;;; + class FW4Tag { public: static FW4Tag createInvalid(); - static FW4Tag create(bool ignored, int sampling_exponent, int root_path_random_); + static FW4Tag create(bool ignored, uint32_t sampling_exponent, uint32_t root_path_random_); static FW4Tag create(const std::string& value); @@ -22,20 +25,20 @@ class FW4Tag { bool isValid() const { return valid_; }; bool isIgnored() const { return ignored_; }; - int getSamplingExponent() const { return sampling_exponent_; }; + uint32_t getSamplingExponent() const { return sampling_exponent_; }; + uint32_t getPathInfo() const { return path_info_; }; private: - FW4Tag(bool valid, bool ignored, int sampling_exponent, int root_path_random_) + FW4Tag(bool valid, bool ignored, uint32_t sampling_exponent, uint32_t path_info) : valid_(valid), ignored_(ignored), sampling_exponent_(sampling_exponent), - root_path_random_(root_path_random_) {} + path_info_(path_info) {} bool valid_; bool ignored_; - int sampling_exponent_; - int root_path_random_; + uint32_t sampling_exponent_; + uint32_t path_info_; }; -// -@dt=fw4;0;0;0;0;;; class DtTracestateEntry { public: DtTracestateEntry(const std::string& tenant_id, const std::string& cluster_id) { diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate_test.cc index 3748458d12de4..380a734de5595 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate_test.cc @@ -15,7 +15,65 @@ TEST(DynatraceTracestateTest, TestKey) { EXPECT_STREQ(tracestate.getKey().c_str(), "98812ad49-980df25c@dt"); } -// TODO: add FW4 TagTests +// Test creating an invalid tag +TEST(FW4TagTest, TestInvalid) { + auto tag = FW4Tag::createInvalid(); + EXPECT_FALSE(tag.isValid()); +} + +// Test creating a tag via values +TEST(FW4TagTest, TestCreateFromValues) { + auto tag = FW4Tag::create(false, 2, 235); + EXPECT_TRUE(tag.isValid()); + EXPECT_FALSE(tag.isIgnored()); + EXPECT_EQ(tag.getSamplingExponent(), 2); + EXPECT_EQ(tag.getPathInfo(), 235); + EXPECT_STREQ(tag.asString().c_str(), "fw4;0;0;0;0;0;2;eb"); + + tag = FW4Tag::create(true, 8, 34566); + EXPECT_TRUE(tag.isValid()); + EXPECT_TRUE(tag.isIgnored()); + EXPECT_EQ(tag.getSamplingExponent(), 8); + EXPECT_EQ(tag.getPathInfo(), 34566); + EXPECT_STREQ(tag.asString().c_str(), "fw4;0;0;0;0;1;8;8706"); +} + +// Test creating a tag from a string +TEST(FW4TagTest, TestCreateFromString) { + auto tag = FW4Tag::create("fw4;0;0;0;0;0;4;1a"); + EXPECT_TRUE(tag.isValid()); + EXPECT_FALSE(tag.isIgnored()); + EXPECT_EQ(tag.getSamplingExponent(), 4); + EXPECT_EQ(tag.getPathInfo(), 26); + + tag = FW4Tag::create("fw4;0;0;0;0;1;2;1a2b"); + EXPECT_TRUE(tag.isValid()); + EXPECT_TRUE(tag.isIgnored()); + EXPECT_EQ(tag.getSamplingExponent(), 2); + EXPECT_EQ(tag.getPathInfo(), 6699); + + tag = FW4Tag::create("fw4;6;b3ed5cbd;0;0;0;0;28e;dd24;2h01;3hb3ed5cbd;4h00;5h01;" + "6he929c846864f0b96d59757ad34881a2b;7h032e812f5a9623aa"); + EXPECT_TRUE(tag.isValid()); + EXPECT_FALSE(tag.isIgnored()); + EXPECT_EQ(tag.getSamplingExponent(), 0); + EXPECT_EQ(tag.getPathInfo(), 654); + + tag = FW4Tag::create(""); + EXPECT_FALSE(tag.isValid()); + tag = FW4Tag::create(";"); + EXPECT_FALSE(tag.isValid()); + tag = FW4Tag::create("fw4;;;;;;;;"); + EXPECT_FALSE(tag.isValid()); + tag = FW4Tag::create("fw4;0;0;0;0;1;2"); + EXPECT_FALSE(tag.isValid()); + tag = FW4Tag::create("fw4;0;0;0;0;1;2;asdf"); // invalid path info (not a hex number) + EXPECT_FALSE(tag.isValid()); + tag = FW4Tag::create("fw4;0;0;0;0;1;X;a2"); // invalid exponent (not a hex number) + EXPECT_FALSE(tag.isValid()); + tag = FW4Tag::create("fw3;0;0;0;0;0;0;a2"); + EXPECT_FALSE(tag.isValid()); +} } // namespace OpenTelemetry } // namespace Tracers From e3abd62f4fad627061871e55a44c1dd8d24f33ad Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Wed, 20 Dec 2023 14:48:25 +0100 Subject: [PATCH 13/31] add required synchronization (via mutex) Signed-off-by: thomas.ebner --- .../samplers/dynatrace/dynatrace_sampler.cc | 17 +++++++++++------ .../samplers/dynatrace/dynatrace_sampler.h | 5 +++-- .../samplers/dynatrace/sampling_controller.h | 13 ++++++++----- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index 6e42df64a4e2a..71e1356501341 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -45,19 +45,23 @@ DynatraceSampler::DynatraceSampler( timer_ = context.serverFactoryContext().mainThreadDispatcher().createTimer([this]() -> void { auto topK = stream_summary_->getTopK(); - // start with a new stream summmary - stream_summary_ = std::make_unique>(100); - + { + // create a new stream summmary for the next period + absl::MutexLock lock{&stream_summary_mutex_}; + stream_summary_ = std::make_unique>(100); + } + // TODO: remove: ENVOY_LOG(info, "Hello from sampler timer. topk.size(): {}", topK.size()); for (auto const& counter : topK) { ENVOY_LOG(info, "-- {} : {}", counter.getItem(), counter.getValue()); } ENVOY_LOG(info, "counter_: {}", counter_); - timer_->enableTimer(SAMPLING_UPDATE_TIMER_DURATION); // update sampling exponents sampling_controller_.update(topK, sampler_config_fetcher_.getSamplerConfig().getRootSpansPerMinute()); + + timer_->enableTimer(SAMPLING_UPDATE_TIMER_DURATION); }); timer_->enableTimer(SAMPLING_UPDATE_TIMER_DURATION); } @@ -76,7 +80,7 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional : ""; if (sampling_key != "") { - Thread::LockGuard lock(mutex_); + absl::MutexLock lock{&stream_summary_mutex_}; stream_summary_->offer(sampling_key); } @@ -97,7 +101,8 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional } else { // make a sampling decision // this is just a demo, we sample every second request here - // uint32_t current_counter = ++counter_; + // uint32_t current_counter = counter_; + counter_++; bool sample; int sampling_exponent; // if (current_counter % 2) { diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index 309732c19e660..a5c4010ccee83 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -6,13 +6,14 @@ #include "envoy/server/factory_context.h" #include "source/common/common/logger.h" -#include "source/common/common/thread.h" #include "source/common/config/datasource.h" #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.h" #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h" #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h" #include "source/extensions/tracers/opentelemetry/samplers/sampler.h" +#include "absl/synchronization/mutex.h" + namespace Envoy { namespace Extensions { namespace Tracers { @@ -84,7 +85,7 @@ class DynatraceSampler : public Sampler, Logger::Loggable { std::string dt_tracestate_key_; SamplerConfigFetcher sampler_config_fetcher_; std::unique_ptr> stream_summary_; - Thread::MutexBasicLockable mutex_{}; + mutable absl::Mutex stream_summary_mutex_{}; Event::TimerPtr timer_; SamplingController sampling_controller_; std::atomic counter_; // request counter for dummy sampling diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h index a61eec2fe46aa..6076be762d4cb 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h @@ -5,6 +5,8 @@ #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h" +#include "absl/synchronization/mutex.h" + namespace Envoy { namespace Extensions { namespace Tracers { @@ -57,12 +59,12 @@ class SamplingController { } } } - // TODO: write_lock - sampling_exponents_ = new_sampling_exponents; + absl::WriterMutexLock lock{&mutex_}; + sampling_exponents_ = std::move(new_sampling_exponents); } - SamplingState getSamplingState(const std::string& sampling_key) { - // TODO: read_lock + SamplingState getSamplingState(const std::string& sampling_key) const { + absl::ReaderMutexLock lock{&mutex_}; auto iter = sampling_exponents_.find(sampling_key); if (iter == sampling_exponents_ @@ -79,8 +81,9 @@ class SamplingController { private: absl::flat_hash_map sampling_exponents_; std::string rest_bucket_key_{}; + mutable absl::Mutex mutex_{}; - uint64_t + static uint64_t computeEffectiveCount(const std::list>& top_k, const absl::flat_hash_map& sampling_exponents) { uint64_t cnt = 0; From 353370416a31efeeed5bc1a131b32e32f65723d2 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Thu, 21 Dec 2023 09:09:17 +0100 Subject: [PATCH 14/31] extend test Signed-off-by: thomas.ebner --- .../dynatrace/dynatrace_sampler_test.cc | 27 ++++++++++++++----- .../dynatrace/sampling_controller_test.cc | 1 - 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc index 55f53425b8194..ee209c30306e1 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc @@ -16,6 +16,24 @@ namespace Extensions { namespace Tracers { namespace OpenTelemetry { +namespace { + +const char* trace_id = "67a9a23155e1741b5b35368e08e6ece5"; + +const char* parent_span_id = "9d83def9a4939b7b"; + +const char* dt_tracestate_ignored = + "9712ad40-980df25c@dt=fw4;4;4af38366;0;0;1;2;123;8eae;2h01;3h4af38366;4h00;5h01;" + "6h67a9a23155e1741b5b35368e08e6ece5;7h9d83def9a4939b7b"; +const char* dt_tracestate_sampled = + "9712ad40-980df25c@dt=fw4;4;4af38366;0;0;0;0;123;8eae;2h01;3h4af38366;4h00;5h01;" + "6h67a9a23155e1741b5b35368e08e6ece5;7h9d83def9a4939b7b"; +const char* dt_tracestate_ignored_different_tenant = + "6666ad40-980df25c@dt=fw4;4;4af38366;0;0;1;2;123;8eae;2h01;3h4af38366;4h00;5h01;" + "6h67a9a23155e1741b5b35368e08e6ece5;7h9d83def9a4939b7b"; + +} // namespace + class DynatraceSamplerTest : public testing::Test { const std::string yaml_string_ = R"EOF( @@ -36,17 +54,14 @@ class DynatraceSamplerTest : public testing::Test { std::unique_ptr sampler_; }; -// Verify sampler being invoked with an invalid/empty span context +// Verify sampler being invoked with no parent span context TEST_F(DynatraceSamplerTest, TestWithoutParentContext) { - auto sampling_result = - sampler_->shouldSample(absl::nullopt, "operation_name", "12345", + sampler_->shouldSample(absl::nullopt, trace_id, "operation_name", ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); EXPECT_EQ(sampling_result.attributes->size(), 1); - // EXPECT_TRUE(absl::StartsWith(sampling_result.tracestate, - // "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;")) << "Received tracestate: " << tracestate_value; - EXPECT_STREQ(sampling_result.tracestate.c_str(), "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;f1"); + EXPECT_STREQ(sampling_result.tracestate.c_str(), "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;95"); EXPECT_TRUE(sampling_result.isRecording()); EXPECT_TRUE(sampling_result.isSampled()); } diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc index 95e89c911ee41..472a673effeae 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc @@ -2,7 +2,6 @@ #include #include #include -#include #include #include From 6f79690b47cdb5d9dccc3eb47e58ce835923a509 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Fri, 22 Dec 2023 07:45:27 +0100 Subject: [PATCH 15/31] move creation of SamplerConfigFetcher out of DynatraceSampler to allow to pass a MockSamplerConfigFetcher in unit tests. Signed-off-by: thomas.ebner --- .../samplers/dynatrace/config.cc | 15 +++++++---- .../samplers/dynatrace/dynatrace_sampler.cc | 9 ++++--- .../samplers/dynatrace/dynatrace_sampler.h | 6 +++-- .../dynatrace/sampler_config_fetcher.cc | 25 ++++++++++--------- .../dynatrace/sampler_config_fetcher.h | 24 +++++++++++++----- .../dynatrace/dynatrace_sampler_test.cc | 9 ++++++- .../dynatrace/sampler_config_fetcher_test.cc | 10 ++++---- 7 files changed, 63 insertions(+), 35 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.cc index 9e410e75ea0c8..a818b7b2b6632 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.cc @@ -1,5 +1,7 @@ #include "config.h" +#include + #include "envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.pb.validate.h" #include "source/common/config/utility.h" @@ -16,11 +18,14 @@ DynatraceSamplerFactory::createSampler(const Protobuf::Message& config, Server::Configuration::TracerFactoryContext& context) { auto mptr = Envoy::Config::Utility::translateAnyToFactoryConfig( dynamic_cast(config), context.messageValidationVisitor(), *this); - return std::make_shared( - MessageUtil::downcastAndValidate< - const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig&>( - *mptr, context.messageValidationVisitor()), - context); + + const auto& proto_config = MessageUtil::downcastAndValidate< + const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig&>( + *mptr, context.messageValidationVisitor()); + + SamplerConfigFetcherPtr cf = std::make_unique( + context, proto_config.http_uri(), proto_config.token()); + return std::make_shared(proto_config, context, std::move(cf)); } /** diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index 71e1356501341..ec684c32f7eea 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -36,10 +36,11 @@ std::string getSamplingKey(const absl::string_view path_query, const absl::strin DynatraceSampler::DynatraceSampler( const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig& config, - Server::Configuration::TracerFactoryContext& context) + Server::Configuration::TracerFactoryContext& context, + SamplerConfigFetcherPtr sampler_config_fetcher) : tenant_id_(config.tenant_id()), cluster_id_(config.cluster_id()), dt_tracestate_entry_(tenant_id_, cluster_id_), - sampler_config_fetcher_(context, config.http_uri(), config.token()), + sampler_config_fetcher_(std::move(sampler_config_fetcher)), stream_summary_(std::make_unique>(100)), sampling_controller_(), counter_(0) { @@ -58,8 +59,8 @@ DynatraceSampler::DynatraceSampler( ENVOY_LOG(info, "counter_: {}", counter_); // update sampling exponents - sampling_controller_.update(topK, - sampler_config_fetcher_.getSamplerConfig().getRootSpansPerMinute()); + sampling_controller_.update( + topK, sampler_config_fetcher_->getSamplerConfig().getRootSpansPerMinute()); timer_->enableTimer(SAMPLING_UPDATE_TIMER_DURATION); }); diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index a5c4010ccee83..dfbdb43f3e808 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -69,7 +69,8 @@ class DynatraceSampler : public Sampler, Logger::Loggable { public: DynatraceSampler( const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig& config, - Server::Configuration::TracerFactoryContext& context); + Server::Configuration::TracerFactoryContext& context, + SamplerConfigFetcherPtr sampler_config_fetcher); SamplingResult shouldSample(const absl::optional parent_context, const std::string& trace_id, const std::string& name, @@ -82,8 +83,9 @@ class DynatraceSampler : public Sampler, Logger::Loggable { private: std::string tenant_id_; std::string cluster_id_; + std::string dt_tracestate_key_; - SamplerConfigFetcher sampler_config_fetcher_; + SamplerConfigFetcherPtr sampler_config_fetcher_; std::unique_ptr> stream_summary_; mutable absl::Mutex stream_summary_mutex_{}; Event::TimerPtr timer_; diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc index 7c73c058ee9f2..1ad2dae26e06f 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc @@ -11,9 +11,9 @@ namespace OpenTelemetry { static constexpr std::chrono::seconds INITIAL_TIMER_DURATION{10}; static constexpr std::chrono::minutes TIMER_INTERVAL{5}; -SamplerConfigFetcher::SamplerConfigFetcher(Server::Configuration::TracerFactoryContext& context, - const envoy::config::core::v3::HttpUri& http_uri, - const std::string& token) +SamplerConfigFetcherImpl::SamplerConfigFetcherImpl( + Server::Configuration::TracerFactoryContext& context, + const envoy::config::core::v3::HttpUri& http_uri, const std::string& token) : cluster_manager_(context.serverFactoryContext().clusterManager()), http_uri_(http_uri), parsed_authorization_header_to_add_( {Http::LowerCaseString("authorization"), absl::StrCat("Api-Token ", token)}), @@ -22,7 +22,7 @@ SamplerConfigFetcher::SamplerConfigFetcher(Server::Configuration::TracerFactoryC timer_ = context.serverFactoryContext().mainThreadDispatcher().createTimer([this]() -> void { const auto thread_local_cluster = cluster_manager_.getThreadLocalCluster(http_uri_.cluster()); if (thread_local_cluster == nullptr) { - ENVOY_LOG(error, "SamplerConfigFetcher failed: [cluster = {}] is not configured", + ENVOY_LOG(error, "SamplerConfigFetcherImpl failed: [cluster = {}] is not configured", http_uri_.cluster()); } else { Http::RequestMessagePtr message = Http::Utility::prepareHeaders(http_uri_); @@ -40,31 +40,32 @@ SamplerConfigFetcher::SamplerConfigFetcher(Server::Configuration::TracerFactoryC timer_->enableTimer(std::chrono::seconds(INITIAL_TIMER_DURATION)); } -SamplerConfigFetcher::~SamplerConfigFetcher() { +SamplerConfigFetcherImpl::~SamplerConfigFetcherImpl() { if (active_request_) { active_request_->cancel(); } } -void SamplerConfigFetcher::onSuccess(const Http::AsyncClient::Request& /*request*/, - Http::ResponseMessagePtr&& http_response) { +void SamplerConfigFetcherImpl::onSuccess(const Http::AsyncClient::Request& /*request*/, + Http::ResponseMessagePtr&& http_response) { onRequestDone(); const auto response_code = Http::Utility::getResponseStatus(http_response->headers()); if (response_code != enumToInt(Http::Code::OK)) { - ENVOY_LOG(error, "SamplerConfigFetcher received a non-success status code: {}", response_code); + ENVOY_LOG(error, "SamplerConfigFetcherImpl received a non-success status code: {}", + response_code); } else { - ENVOY_LOG(info, "SamplerConfigFetcher received success status code: {}", response_code); + ENVOY_LOG(info, "SamplerConfigFetcherImpl received success status code: {}", response_code); sampler_config_.parse(http_response->bodyAsString()); } } -void SamplerConfigFetcher::onFailure(const Http::AsyncClient::Request& /*request*/, - Http::AsyncClient::FailureReason reason) { +void SamplerConfigFetcherImpl::onFailure(const Http::AsyncClient::Request& /*request*/, + Http::AsyncClient::FailureReason reason) { onRequestDone(); ENVOY_LOG(info, "The OTLP export request failed. Reason {}", enumToInt(reason)); } -void SamplerConfigFetcher::onRequestDone() { +void SamplerConfigFetcherImpl::onRequestDone() { // TODO: should we re-enable timer after send() to avoid having the request duration added to the // timer? If so, we would need a list containing the active requests (not a single pointer) active_request_ = nullptr; diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.h index b8a0a50085b5f..9e88e34d06943 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -21,11 +22,20 @@ namespace Extensions { namespace Tracers { namespace OpenTelemetry { -class SamplerConfigFetcher : public Logger::Loggable, - public Http::AsyncClient::Callbacks { +class SamplerConfigFetcher { public: - SamplerConfigFetcher(Server::Configuration::TracerFactoryContext& context, - const envoy::config::core::v3::HttpUri& http_uri, const std::string& token); + virtual ~SamplerConfigFetcher() = default; + + virtual const SamplerConfig& getSamplerConfig() const = 0; +}; + +class SamplerConfigFetcherImpl : public SamplerConfigFetcher, + public Logger::Loggable, + public Http::AsyncClient::Callbacks { +public: + SamplerConfigFetcherImpl(Server::Configuration::TracerFactoryContext& context, + const envoy::config::core::v3::HttpUri& http_uri, + const std::string& token); void onSuccess(const Http::AsyncClient::Request& request, Http::ResponseMessagePtr&& response) override; @@ -35,9 +45,9 @@ class SamplerConfigFetcher : public Logger::Loggable, void onBeforeFinalizeUpstreamSpan(Envoy::Tracing::Span& /*span*/, const Http::ResponseHeaderMap* /*response_headers*/) override{}; - const SamplerConfig& getSamplerConfig() const { return sampler_config_; } + const SamplerConfig& getSamplerConfig() const override { return sampler_config_; } - ~SamplerConfigFetcher() override; + ~SamplerConfigFetcherImpl() override; private: Event::TimerPtr timer_; @@ -50,6 +60,8 @@ class SamplerConfigFetcher : public Logger::Loggable, void onRequestDone(); }; +using SamplerConfigFetcherPtr = std::unique_ptr; + } // namespace OpenTelemetry } // namespace Tracers } // namespace Extensions diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc index ee209c30306e1..8411cdc9a9b77 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc @@ -34,6 +34,11 @@ const char* dt_tracestate_ignored_different_tenant = } // namespace +class MockSamplerConfigFetcher : public SamplerConfigFetcher { +public: + MOCK_METHOD(const SamplerConfig&, getSamplerConfig, (), (const override)); +}; + class DynatraceSamplerTest : public testing::Test { const std::string yaml_string_ = R"EOF( @@ -45,7 +50,9 @@ class DynatraceSamplerTest : public testing::Test { DynatraceSamplerTest() { TestUtility::loadFromYaml(yaml_string_, config_); NiceMock context; - sampler_ = std::make_unique(config_, context); + + SamplerConfigFetcherPtr cf = std::make_unique(); + sampler_ = std::make_unique(config_, context, std::move(cf)); EXPECT_STREQ(sampler_->getDescription().c_str(), "DynatraceSampler"); } diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher_test.cc index 709f42624da28..e2ab8abc65c17 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher_test.cc @@ -68,13 +68,13 @@ TEST_F(SamplerConfigFetcherTest, TestRequestIsSent) { EXPECT_CALL(tracerFactoryContext_.server_factory_context_.cluster_manager_.thread_local_cluster_ .async_client_, send_(MessageMatcher("unused-but-machtes-requires-an-arg"), _, _)); - SamplerConfigFetcher configFetcher(tracerFactoryContext_, http_uri_, "tokenval"); + SamplerConfigFetcherImpl configFetcher(tracerFactoryContext_, http_uri_, "tokenval"); timer_->invokeCallback(); } // Test receiving a response with code 200 and valid json TEST_F(SamplerConfigFetcherTest, TestResponseOk) { - SamplerConfigFetcher configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); + SamplerConfigFetcherImpl configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); timer_->invokeCallback(); Http::ResponseMessagePtr message(new Http::ResponseMessageImpl( @@ -87,7 +87,7 @@ TEST_F(SamplerConfigFetcherTest, TestResponseOk) { // Test receiving a response with code 200 and unexpected json TEST_F(SamplerConfigFetcherTest, TestResponseOkInvalidJson) { - SamplerConfigFetcher configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); + SamplerConfigFetcherImpl configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); timer_->invokeCallback(); Http::ResponseMessagePtr message(new Http::ResponseMessageImpl( @@ -101,7 +101,7 @@ TEST_F(SamplerConfigFetcherTest, TestResponseOkInvalidJson) { // Test receiving a response with code != 200 TEST_F(SamplerConfigFetcherTest, TestResponseErrorCode) { - SamplerConfigFetcher configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); + SamplerConfigFetcherImpl configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); timer_->invokeCallback(); Http::ResponseMessagePtr message(new Http::ResponseMessageImpl( @@ -115,7 +115,7 @@ TEST_F(SamplerConfigFetcherTest, TestResponseErrorCode) { // Test sending failed TEST_F(SamplerConfigFetcherTest, TestOnFailure) { - SamplerConfigFetcher configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); + SamplerConfigFetcherImpl configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); timer_->invokeCallback(); configFetcher.onFailure(request_, Http::AsyncClient::FailureReason::Reset); EXPECT_EQ(configFetcher.getSamplerConfig().getRootSpansPerMinute(), From 3793a674fe622cbfc9ed5218713242d85c95261c Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Fri, 22 Dec 2023 07:57:08 +0100 Subject: [PATCH 16/31] SamplerConfig: reset to default value as fallback if no value was received Signed-off-by: thomas.ebner --- .../tracers/opentelemetry/samplers/dynatrace/sampler_config.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h index 4b0ec5a9a1a31..e7fcf692d2eb0 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h @@ -17,16 +17,18 @@ class SamplerConfig { static constexpr uint64_t ROOT_SPANS_PER_MINUTE_DEFAULT = 1000; void parse(const std::string& json) { - root_spans_per_minute_.store(ROOT_SPANS_PER_MINUTE_DEFAULT); // reset to default auto result = Envoy::Json::Factory::loadFromStringNoThrow(json); if (result.ok()) { auto obj = result.value(); if (obj->hasObject("rootSpansPerMinute")) { auto value = obj->getInteger("rootSpansPerMinute", ROOT_SPANS_PER_MINUTE_DEFAULT); root_spans_per_minute_.store(value); + return; } (void)obj; } + // didn't get a value, reset to default + root_spans_per_minute_.store(ROOT_SPANS_PER_MINUTE_DEFAULT); } uint64_t getRootSpansPerMinute() const { return root_spans_per_minute_.load(); } From eb3b1e402f9ecef18ea6ee4fc1388063bdd7e9fb Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Fri, 22 Dec 2023 11:01:18 +0100 Subject: [PATCH 17/31] extend test Signed-off-by: thomas.ebner --- .../samplers/dynatrace/dynatrace_sampler.cc | 39 ++++----- .../samplers/dynatrace/dynatrace_sampler.h | 1 + .../samplers/dynatrace/sampler_config.h | 6 +- .../dynatrace/sampler_config_fetcher.cc | 2 - .../samplers/dynatrace/sampling_controller.h | 2 +- .../dynatrace/dynatrace_sampler_test.cc | 81 ++++++++++++++++++- 6 files changed, 106 insertions(+), 25 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index ec684c32f7eea..67b1d12193e47 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -45,23 +45,7 @@ DynatraceSampler::DynatraceSampler( counter_(0) { timer_ = context.serverFactoryContext().mainThreadDispatcher().createTimer([this]() -> void { - auto topK = stream_summary_->getTopK(); - { - // create a new stream summmary for the next period - absl::MutexLock lock{&stream_summary_mutex_}; - stream_summary_ = std::make_unique>(100); - } - // TODO: remove: - ENVOY_LOG(info, "Hello from sampler timer. topk.size(): {}", topK.size()); - for (auto const& counter : topK) { - ENVOY_LOG(info, "-- {} : {}", counter.getItem(), counter.getValue()); - } - ENVOY_LOG(info, "counter_: {}", counter_); - - // update sampling exponents - sampling_controller_.update( - topK, sampler_config_fetcher_->getSamplerConfig().getRootSpansPerMinute()); - + updateSamplingInfo(); timer_->enableTimer(SAMPLING_UPDATE_TIMER_DURATION); }); timer_->enableTimer(SAMPLING_UPDATE_TIMER_DURATION); @@ -80,7 +64,7 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional trace_context.has_value() ? getSamplingKey(trace_context->path(), trace_context->method()) : ""; - if (sampling_key != "") { + if (!sampling_key.empty()) { absl::MutexLock lock{&stream_summary_mutex_}; stream_summary_->offer(sampling_key); } @@ -136,6 +120,25 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional return result; } +void DynatraceSampler::updateSamplingInfo() { + auto topK = stream_summary_->getTopK(); + { + // create a new stream summmary for the next period + absl::MutexLock lock{&stream_summary_mutex_}; + stream_summary_ = std::make_unique>(100); + } + // TODO: remove: + ENVOY_LOG(error, "Hello from sampler timer. topk.size(): {}", topK.size()); + for (auto const& counter : topK) { + ENVOY_LOG(error, "-- {} : {}", counter.getItem(), counter.getValue()); + } + ENVOY_LOG(info, "counter_: {}", counter_); + + // update sampling exponents + sampling_controller_.update(topK, + sampler_config_fetcher_->getSamplerConfig().getRootSpansPerMinute()); +} + std::string DynatraceSampler::getDescription() const { return "DynatraceSampler"; } } // namespace OpenTelemetry diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index dfbdb43f3e808..72c765601d14f 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -91,6 +91,7 @@ class DynatraceSampler : public Sampler, Logger::Loggable { Event::TimerPtr timer_; SamplingController sampling_controller_; std::atomic counter_; // request counter for dummy sampling + void updateSamplingInfo(); }; } // namespace OpenTelemetry diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h index e7fcf692d2eb0..3e25bb735b7ac 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h @@ -14,7 +14,7 @@ namespace OpenTelemetry { class SamplerConfig { public: - static constexpr uint64_t ROOT_SPANS_PER_MINUTE_DEFAULT = 1000; + static constexpr uint32_t ROOT_SPANS_PER_MINUTE_DEFAULT = 1000; void parse(const std::string& json) { auto result = Envoy::Json::Factory::loadFromStringNoThrow(json); @@ -31,10 +31,10 @@ class SamplerConfig { root_spans_per_minute_.store(ROOT_SPANS_PER_MINUTE_DEFAULT); } - uint64_t getRootSpansPerMinute() const { return root_spans_per_minute_.load(); } + uint32_t getRootSpansPerMinute() const { return root_spans_per_minute_.load(); } private: - std::atomic root_spans_per_minute_ = ROOT_SPANS_PER_MINUTE_DEFAULT; + std::atomic root_spans_per_minute_ = ROOT_SPANS_PER_MINUTE_DEFAULT; }; } // namespace OpenTelemetry diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc index 1ad2dae26e06f..0975d628e519d 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc @@ -66,8 +66,6 @@ void SamplerConfigFetcherImpl::onFailure(const Http::AsyncClient::Request& /*req } void SamplerConfigFetcherImpl::onRequestDone() { - // TODO: should we re-enable timer after send() to avoid having the request duration added to the - // timer? If so, we would need a list containing the active requests (not a single pointer) active_request_ = nullptr; timer_->enableTimer(std::chrono::seconds(TIMER_INTERVAL)); } diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h index 6076be762d4cb..c02412fc350ad 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h @@ -33,7 +33,7 @@ class SamplingState { class SamplingController { public: - void update(const std::list>& top_k, const uint64_t total_wanted) { + void update(const std::list>& top_k, const uint32_t total_wanted) { absl::flat_hash_map new_sampling_exponents; // start with sampling exponent 0, which means multiplicity == 1 (every span is sampled) diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc index 8411cdc9a9b77..62e5a7f0797db 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc @@ -47,6 +47,7 @@ class DynatraceSamplerTest : public testing::Test { )EOF"; public: +<<<<<<< HEAD DynatraceSamplerTest() { TestUtility::loadFromYaml(yaml_string_, config_); NiceMock context; @@ -55,14 +56,26 @@ class DynatraceSamplerTest : public testing::Test { sampler_ = std::make_unique(config_, context, std::move(cf)); EXPECT_STREQ(sampler_->getDescription().c_str(), "DynatraceSampler"); } +======= + DynatraceSamplerTest() { TestUtility::loadFromYaml(yaml_string, config_); } +>>>>>>> 2705f51fef (extend test) protected: + NiceMock tracerFactoryContext_; envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig config_; - std::unique_ptr sampler_; }; +TEST_F(DynatraceSamplerTest, TestGetDescription) { + auto scf = std::make_unique(); + DynatraceSampler sampler(config_, tracerFactoryContext_, std::move(scf)); + EXPECT_STREQ(sampler.getDescription().c_str(), "DynatraceSampler"); +} + // Verify sampler being invoked with no parent span context TEST_F(DynatraceSamplerTest, TestWithoutParentContext) { + auto scf = std::make_unique(); + DynatraceSampler sampler(config_, tracerFactoryContext_, std::move(scf)); + auto sampling_result = sampler_->shouldSample(absl::nullopt, trace_id, "operation_name", ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); @@ -91,6 +104,72 @@ TEST_F(DynatraceSamplerTest, TestWithParentContext) { EXPECT_TRUE(sampling_result.isSampled()); } +TEST_F(DynatraceSamplerTest, TestSampling) { + auto scf = std::make_unique(); + + SamplerConfig config; + // config should allow 200 root spans per minute + config.parse("{\n \"rootSpansPerMinute\" : 200 \n }"); + + EXPECT_CALL(*scf, getSamplerConfig()).WillRepeatedly(testing::ReturnRef(config)); + + auto timer = + new NiceMock(&tracerFactoryContext_.server_factory_context_.dispatcher_); + ON_CALL(tracerFactoryContext_.server_factory_context_.dispatcher_, createTimer_(_)) + .WillByDefault(Invoke([timer](Event::TimerCb) { return timer; })); + + DynatraceSampler sampler(config_, tracerFactoryContext_, std::move(scf)); + Tracing::TestTraceContextImpl trace_context_1{}; + trace_context_1.context_method_ = "GET"; + trace_context_1.context_path_ = "/path"; + Tracing::TestTraceContextImpl trace_context_2{}; + trace_context_2.context_method_ = "POST"; + trace_context_2.context_path_ = "/path"; + Tracing::TestTraceContextImpl trace_context_3{}; + trace_context_3.context_method_ = "POST"; + trace_context_3.context_path_ = "/another_path"; + + // send requests + for (int i = 0; i < 180; i++) { + sampler.shouldSample({}, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, trace_context_1, + {}); + sampler.shouldSample({}, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, trace_context_2, + {}); + } + + sampler.shouldSample({}, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, trace_context_3, + {}); + + // sampler should read update sampling exponents + timer->invokeCallback(); + + // the sampler should not sample every span for 'trace_context_1' + // we call it again 10 times. This should be enough to get at least one ignored span + // 'i' is used as 'random trace_id' + bool ignored = false; + for (int i = 0; i < 10; i++) { + auto result = sampler.shouldSample({}, std::to_string(i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); + if (!result.isSampled()) { + ignored = true; + break; + } + } + EXPECT_TRUE(ignored); + + // trace_context_3 should be sampled + for (int i = 0; i < 10; i++) { + auto result = sampler.shouldSample({}, std::to_string(i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_2, {}); + EXPECT_TRUE(result.isSampled()); + } +} + } // namespace OpenTelemetry } // namespace Tracers } // namespace Extensions From 4c736e81b55da83f5cc024c42bbde5e764fd15b3 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Mon, 15 Jan 2024 14:21:12 +0100 Subject: [PATCH 18/31] add test cases Signed-off-by: thomas.ebner --- .../samplers/dynatrace/dynatrace_sampler.cc | 11 +---------- .../samplers/dynatrace/dynatrace_sampler.h | 2 +- .../samplers/dynatrace/sampler_config.h | 6 +++--- .../dynatrace/sampler_config_fetcher.cc | 9 +++++---- .../dynatrace/sampler_config_fetcher_test.cc | 18 ++++++++++++++++++ .../dynatrace/sampling_controller_test.cc | 19 +++++++++++++++++++ 6 files changed, 47 insertions(+), 18 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index 67b1d12193e47..37750d10d368c 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -20,6 +20,7 @@ namespace OpenTelemetry { namespace { +// TODO: should be one minute static constexpr std::chrono::seconds SAMPLING_UPDATE_TIMER_DURATION{20}; // static constexpr std::chrono::minutes SAMPLING_UPDATE_TIMER_DURATION{1}; @@ -84,19 +85,9 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional result.tracestate = parent_context->tracestate(); } } else { // make a sampling decision - - // this is just a demo, we sample every second request here - // uint32_t current_counter = counter_; counter_++; bool sample; int sampling_exponent; - // if (current_counter % 2) { - // sample = true; - // sampling_exponent = 1; - // } else { - // sample = false; - // sampling_exponent = 0; - // } // do a decision based on the calculated exponent // at the moment we use a hash of the trace_id as random number diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index 72c765601d14f..817a94b480e3a 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -63,7 +63,7 @@ class FW4Tag { }; /** - * @brief A Dynatrace specific sampler * + * @brief A Dynatrace specific sampler */ class DynatraceSampler : public Sampler, Logger::Loggable { public: diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h index 3e25bb735b7ac..46989a1640725 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h @@ -17,11 +17,11 @@ class SamplerConfig { static constexpr uint32_t ROOT_SPANS_PER_MINUTE_DEFAULT = 1000; void parse(const std::string& json) { - auto result = Envoy::Json::Factory::loadFromStringNoThrow(json); + const auto result = Envoy::Json::Factory::loadFromStringNoThrow(json); if (result.ok()) { - auto obj = result.value(); + const auto obj = result.value(); if (obj->hasObject("rootSpansPerMinute")) { - auto value = obj->getInteger("rootSpansPerMinute", ROOT_SPANS_PER_MINUTE_DEFAULT); + const auto value = obj->getInteger("rootSpansPerMinute", ROOT_SPANS_PER_MINUTE_DEFAULT); root_spans_per_minute_.store(value); return; } diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc index 0975d628e519d..dc35d3aef0a9b 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc @@ -50,12 +50,13 @@ void SamplerConfigFetcherImpl::onSuccess(const Http::AsyncClient::Request& /*req Http::ResponseMessagePtr&& http_response) { onRequestDone(); const auto response_code = Http::Utility::getResponseStatus(http_response->headers()); - if (response_code != enumToInt(Http::Code::OK)) { - ENVOY_LOG(error, "SamplerConfigFetcherImpl received a non-success status code: {}", - response_code); - } else { + if (response_code == enumToInt(Http::Code::OK)) { + // TODO: remove log ENVOY_LOG(info, "SamplerConfigFetcherImpl received success status code: {}", response_code); sampler_config_.parse(http_response->bodyAsString()); + } else { + ENVOY_LOG(error, "SamplerConfigFetcherImpl received a non-success status code: {}", + response_code); } } diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher_test.cc index e2ab8abc65c17..7c6f97e01d1fa 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher_test.cc @@ -123,6 +123,24 @@ TEST_F(SamplerConfigFetcherTest, TestOnFailure) { EXPECT_TRUE(timer_->enabled()); } +// Test calling onBeforeFinalizeUpstreamSpan +TEST_F(SamplerConfigFetcherTest, TestOnBeforeFinalizeUpstreamSpan) { + Tracing::MockSpan child_span_; + SamplerConfigFetcherImpl configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); + // onBeforeFinalizeUpstreamSpan() is an empty method, nothing should happen + configFetcher.onBeforeFinalizeUpstreamSpan(child_span_, nullptr); +} + +// Test invoking the timer if no cluster can be found +TEST_F(SamplerConfigFetcherTest, TestNoCluster) { + // simulate no configured cluster, return nullptr. + ON_CALL(tracerFactoryContext_.server_factory_context_.cluster_manager_, getThreadLocalCluster(_)) + .WillByDefault(Return(nullptr)); + SamplerConfigFetcherImpl configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); + timer_->invokeCallback(); + // should not crash or throw. +} + } // namespace OpenTelemetry } // namespace Tracers } // namespace Extensions diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc index 472a673effeae..ee50725383491 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc @@ -60,6 +60,25 @@ TEST_F(SamplingControllerTest, TestSimple) { EXPECT_EQ(sc.getSamplingState("GET_xxxx").getMultiplicity(), 1); } +TEST_F(SamplingControllerTest, TestEmpty) { + StreamSummary summary(10); + SamplingController sc; + sc.update(summary.getTopK(), 100); + + EXPECT_EQ(sc.getSamplingState("GET_something").getExponent(), 0); + EXPECT_EQ(sc.getSamplingState("GET_something").getMultiplicity(), 1); +} + +TEST_F(SamplingControllerTest, TestNonExisting) { + StreamSummary summary(10); + summary.offer("key1"); + SamplingController sc; + sc.update(summary.getTopK(), 100); + + EXPECT_EQ(sc.getSamplingState("key2").getExponent(), 0); + EXPECT_EQ(sc.getSamplingState("key2").getMultiplicity(), 1); +} + TEST(SamplingStateTest, TestIncreaseDecrease) { SamplingState sst{}; EXPECT_EQ(sst.getExponent(), 0); From 0af49f4f165af047665cc85d81b331b3de5906c1 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Thu, 18 Jan 2024 07:32:24 +0100 Subject: [PATCH 19/31] change Locking, add unit test, introduce constant for streamSummary size Signed-off-by: thomas.ebner --- .../samplers/dynatrace/dynatrace_sampler.cc | 24 +-- .../samplers/dynatrace/dynatrace_sampler.h | 2 + .../samplers/dynatrace/sampling_controller.h | 78 +++++++-- .../dynatrace/sampling_controller_test.cc | 153 ++++++++++++++---- .../samplers/dynatrace/stream_summary_test.cc | 1 - 5 files changed, 198 insertions(+), 60 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index 37750d10d368c..a35d2867c964f 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -42,8 +42,8 @@ DynatraceSampler::DynatraceSampler( : tenant_id_(config.tenant_id()), cluster_id_(config.cluster_id()), dt_tracestate_entry_(tenant_id_, cluster_id_), sampler_config_fetcher_(std::move(sampler_config_fetcher)), - stream_summary_(std::make_unique>(100)), sampling_controller_(), - counter_(0) { + stream_summary_(std::make_unique>(STREAM_SUMMARY_SIZE)), + sampling_controller_(), counter_(0) { timer_ = context.serverFactoryContext().mainThreadDispatcher().createTimer([this]() -> void { updateSamplingInfo(); @@ -61,6 +61,7 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional SamplingResult result; std::map att; + // trace_context->path() returns path and query. query part is removed in getSamplingKey() const std::string sampling_key = trace_context.has_value() ? getSamplingKey(trace_context->path(), trace_context->method()) : ""; @@ -112,22 +113,23 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional } void DynatraceSampler::updateSamplingInfo() { + absl::MutexLock lock{&stream_summary_mutex_}; auto topK = stream_summary_->getTopK(); - { - // create a new stream summmary for the next period - absl::MutexLock lock{&stream_summary_mutex_}; - stream_summary_ = std::make_unique>(100); - } - // TODO: remove: - ENVOY_LOG(error, "Hello from sampler timer. topk.size(): {}", topK.size()); + auto last_period_count = stream_summary_->getN(); + + // TODO: remove or convert to debug + ENVOY_LOG(info, "Hello from sampler timer. topk.size(): {}", last_period_count, topK.size()); for (auto const& counter : topK) { - ENVOY_LOG(error, "-- {} : {}", counter.getItem(), counter.getValue()); + ENVOY_LOG(info, "-- {} : {}", counter.getItem(), counter.getValue()); } ENVOY_LOG(info, "counter_: {}", counter_); // update sampling exponents - sampling_controller_.update(topK, + sampling_controller_.update(topK, last_period_count, sampler_config_fetcher_->getSamplerConfig().getRootSpansPerMinute()); + // Note: getTopK() returns references to values in StreamSummary. + // Do not destroy it while topK is used! + stream_summary_ = std::make_unique>(STREAM_SUMMARY_SIZE); } std::string DynatraceSampler::getDescription() const { return "DynatraceSampler"; } diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index 817a94b480e3a..72bf995caf3dc 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -80,6 +80,8 @@ class DynatraceSampler : public Sampler, Logger::Loggable { std::string getDescription() const override; + static constexpr size_t STREAM_SUMMARY_SIZE{100}; + private: std::string tenant_id_; std::string cluster_id_; diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h index c02412fc350ad..37c45de359c65 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h @@ -33,8 +33,11 @@ class SamplingState { class SamplingController { public: - void update(const std::list>& top_k, const uint32_t total_wanted) { + void update(const std::list>& top_k, uint64_t last_period_count, + const uint32_t total_wanted) { + // TODO: remove parameter + (void)last_period_count; absl::flat_hash_map new_sampling_exponents; // start with sampling exponent 0, which means multiplicity == 1 (every span is sampled) for (auto const& counter : top_k) { @@ -44,21 +47,8 @@ class SamplingController { // use the last entry as "rest bucket", which is used for new/unknown requests rest_bucket_key_ = (top_k.size() > 0) ? top_k.back().getItem() : ""; - auto effective_count = computeEffectiveCount(top_k, new_sampling_exponents); + calculateSamplingExponents(top_k, total_wanted, new_sampling_exponents); - while (effective_count > total_wanted) { - for (auto const& counter : top_k) { - auto sampling_state = new_sampling_exponents.find(counter.getItem()); - sampling_state->second.increaseExponent(); - effective_count = computeEffectiveCount(top_k, new_sampling_exponents); - if (effective_count <= total_wanted) { - // we want to be close to total_wanted, but we don't want less than total_wanted samples. - // Therefore, we decrease the exponent again to keep effective_count > total_wanted - sampling_state->second.decreaseExponent(); - break; - } - } - } absl::WriterMutexLock lock{&mutex_}; sampling_exponents_ = std::move(new_sampling_exponents); } @@ -78,6 +68,11 @@ class SamplingController { return iter->second; } + uint64_t getEffectiveCount(const std::list>& top_k) { + absl::ReaderMutexLock lock{&mutex_}; + return computeEffectiveCount(top_k, sampling_exponents_); + } + private: absl::flat_hash_map sampling_exponents_; std::string rest_bucket_key_{}; @@ -92,10 +87,61 @@ class SamplingController { if (sampling_state == sampling_exponents.end()) { continue; } - cnt += counter.getValue() / sampling_state->second.getMultiplicity(); + auto counterVal = counter.getValue(); + auto mul = sampling_state->second.getMultiplicity(); + auto res = counterVal / mul; + cnt += res; } return cnt; } + + void calculateSamplingExponents( + const std::list>& top_k, const uint32_t total_wanted, + absl::flat_hash_map& new_sampling_exponents) { + const auto top_k_size = top_k.size(); + if (top_k_size == 0 || total_wanted == 0) { + return; + } + + // number of requests which are allowed for every entry + const uint32_t allowed_per_entry = total_wanted / top_k_size; // requests which are allowed + + for (auto& counter : top_k) { + // allowed multiplicity for this entry + auto wanted_multiplicity = counter.getValue() / allowed_per_entry; + if (wanted_multiplicity < 0) { + wanted_multiplicity = 1; + } + auto sampling_state = new_sampling_exponents.find(counter.getItem()); + // sampling exponent has to be a power of 2. Find the exponent to have multiplicity near to + // wanted_multiplicity + while (wanted_multiplicity > sampling_state->second.getMultiplicity()) { + sampling_state->second.increaseExponent(); + } + if (wanted_multiplicity < sampling_state->second.getMultiplicity()) { + // we want to have multiplicity <= wanted_multiplicity, therefore exponent is decrease once. + sampling_state->second.decreaseExponent(); + } + } + + auto effective_count = computeEffectiveCount(top_k, new_sampling_exponents); + // There might be entries where allowed_per_entry is greater than their count. + // Therefore, we would sample nubmer of total_wanted requests + // To avoid this, we decrease the exponent of other entries if possible + if (effective_count < total_wanted) { + for (int i = 0; i < 5; i++) { // max tries + for (auto reverse_it = top_k.rbegin(); reverse_it != top_k.rend(); + ++reverse_it) { // start with lowest frequency + auto rev_sampling_state = new_sampling_exponents.find(reverse_it->getItem()); + rev_sampling_state->second.decreaseExponent(); + effective_count = computeEffectiveCount(top_k, new_sampling_exponents); + if (effective_count >= total_wanted) { // we are done + return; + } + } + } + } + } }; } // namespace OpenTelemetry diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc index ee50725383491..4c533304fcb43 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc @@ -5,6 +5,7 @@ #include #include +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h" #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h" #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h" @@ -16,54 +17,142 @@ namespace Extensions { namespace Tracers { namespace OpenTelemetry { -class SamplingControllerTest : public testing::Test { - -protected: - std::vector getRequests() { - std::vector requests{}; - for (int i = 0; i < 100; i++) { - requests.push_back("GET_asdf"); - } - for (int i = 0; i < 200; i++) { - requests.push_back("POST_asdf"); - } - for (int i = 0; i < 300; i++) { - requests.push_back("GET_xxxx"); - } - return requests; +namespace { + +void offerEntry(StreamSummary& summary, const std::string& value, int count) { + for (int i = 0; i < count; i++) { + summary.offer(value); + } +} + +} // namespace + +template std::string toString(T const& list) { + std::ostringstream oss; + for (auto const& counter : list) { + oss << counter.getItem() << ":(" << counter.getValue() << "/" << counter.getError() << ")" + << std::endl; } -}; + return oss.str(); +} + +class SamplingControllerTest : public testing::Test {}; + +TEST_F(SamplingControllerTest, TestManyDifferentRequests) { + StreamSummary summary(DynatraceSampler::STREAM_SUMMARY_SIZE); + offerEntry(summary, "1", 2000); + offerEntry(summary, "2", 1000); + offerEntry(summary, "3", 750); + offerEntry(summary, "4", 100); + offerEntry(summary, "5", 50); + for (int64_t i = 0; i < 2100; i++) { + summary.offer(std::to_string(i + 1000000)); + } + + SamplingController sc; + sc.update(summary.getTopK(), summary.getN(), 1000); + + // std::cout << toString(summary.getTopK()); + + EXPECT_EQ(sc.getEffectiveCount(summary.getTopK()), 1110); + EXPECT_EQ(sc.getSamplingState("1").getMultiplicity(), 128); + EXPECT_EQ(sc.getSamplingState("2").getMultiplicity(), 64); + EXPECT_EQ(sc.getSamplingState("3").getMultiplicity(), 64); + EXPECT_EQ(sc.getSamplingState("4").getMultiplicity(), 8); + EXPECT_EQ(sc.getSamplingState("5").getMultiplicity(), 4); + EXPECT_EQ(sc.getSamplingState("1000000").getMultiplicity(), 2); + EXPECT_EQ(sc.getSamplingState("1000001").getMultiplicity(), 2); + EXPECT_EQ(sc.getSamplingState("1000002").getMultiplicity(), 2); +} + +TEST_F(SamplingControllerTest, TestManyRequests) { + StreamSummary summary(DynatraceSampler::STREAM_SUMMARY_SIZE); + offerEntry(summary, "1", 8600); + offerEntry(summary, "2", 5000); + offerEntry(summary, "3", 4000); + offerEntry(summary, "4", 4000); + offerEntry(summary, "5", 3000); + offerEntry(summary, "6", 30); + offerEntry(summary, "7", 3); + offerEntry(summary, "8", 1); + + SamplingController sc; + sc.update(summary.getTopK(), summary.getN(), 1000); + + // std::cout << toString(summary.getTopK()); + + EXPECT_EQ(sc.getEffectiveCount(summary.getTopK()), 1074); + EXPECT_EQ(sc.getSamplingState("1").getMultiplicity(), 64); + EXPECT_EQ(sc.getSamplingState("2").getMultiplicity(), 32); + EXPECT_EQ(sc.getSamplingState("3").getMultiplicity(), 32); + EXPECT_EQ(sc.getSamplingState("4").getMultiplicity(), 16); + EXPECT_EQ(sc.getSamplingState("5").getMultiplicity(), 8); + EXPECT_EQ(sc.getSamplingState("6").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("7").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("8").getMultiplicity(), 1); +} + +TEST_F(SamplingControllerTest, TestSomeRequests) { + StreamSummary summary(DynatraceSampler::STREAM_SUMMARY_SIZE); + offerEntry(summary, "1", 7500); + offerEntry(summary, "2", 1000); + offerEntry(summary, "3", 1); + offerEntry(summary, "4", 1); + offerEntry(summary, "5", 1); + for (int64_t i = 0; i < 11; i++) { + summary.offer(std::to_string(i + 1000000)); + } + + SamplingController sc; + sc.update(summary.getTopK(), summary.getN(), 1000); + + EXPECT_EQ(sc.getEffectiveCount(summary.getTopK()), 1451); + EXPECT_EQ(sc.getSamplingState("1").getMultiplicity(), 8); + EXPECT_EQ(sc.getSamplingState("2").getMultiplicity(), 2); + EXPECT_EQ(sc.getSamplingState("3").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("4").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("5").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("1000000").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("1000001").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("1000002").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("1000003").getMultiplicity(), 1); +} TEST_F(SamplingControllerTest, TestSimple) { - std::vector requests = getRequests(); StreamSummary summary(10); - for (auto const& c : requests) { - summary.offer(c); - } + // offerEntry data + offerEntry(summary, "GET_xxxx", 300); + offerEntry(summary, "POST_asdf", 200); + offerEntry(summary, "GET_asdf", 100); + SamplingController sc; - sc.update(summary.getTopK(), 100); + sc.update(summary.getTopK(), summary.getN(), 100); - EXPECT_EQ(sc.getSamplingState("GET_asdf").getExponent(), 2); - EXPECT_EQ(sc.getSamplingState("GET_asdf").getMultiplicity(), 4); - EXPECT_EQ(sc.getSamplingState("POST_asdf").getExponent(), 2); - EXPECT_EQ(sc.getSamplingState("POST_asdf").getMultiplicity(), 4); EXPECT_EQ(sc.getSamplingState("GET_xxxx").getExponent(), 3); EXPECT_EQ(sc.getSamplingState("GET_xxxx").getMultiplicity(), 8); + EXPECT_EQ(sc.getSamplingState("POST_asdf").getExponent(), 2); + EXPECT_EQ(sc.getSamplingState("POST_asdf").getMultiplicity(), 4); + + EXPECT_EQ(sc.getSamplingState("GET_asdf").getExponent(), 1); + EXPECT_EQ(sc.getSamplingState("GET_asdf").getMultiplicity(), 2); + // total_wanted > number of requests - sc.update(summary.getTopK(), 1000); - EXPECT_EQ(sc.getSamplingState("GET_asdf").getExponent(), 0); - EXPECT_EQ(sc.getSamplingState("GET_asdf").getMultiplicity(), 1); - EXPECT_EQ(sc.getSamplingState("POST_asdf").getExponent(), 0); - EXPECT_EQ(sc.getSamplingState("POST_asdf").getMultiplicity(), 1); + sc.update(summary.getTopK(), summary.getN(), 1000); EXPECT_EQ(sc.getSamplingState("GET_xxxx").getExponent(), 0); EXPECT_EQ(sc.getSamplingState("GET_xxxx").getMultiplicity(), 1); + + EXPECT_EQ(sc.getSamplingState("POST_asdf").getExponent(), 0); + EXPECT_EQ(sc.getSamplingState("POST_asdf").getMultiplicity(), 1); + + EXPECT_EQ(sc.getSamplingState("GET_asdf").getExponent(), 0); + EXPECT_EQ(sc.getSamplingState("GET_asdf").getMultiplicity(), 1); } TEST_F(SamplingControllerTest, TestEmpty) { StreamSummary summary(10); SamplingController sc; - sc.update(summary.getTopK(), 100); + sc.update(summary.getTopK(), summary.getN(), 100); EXPECT_EQ(sc.getSamplingState("GET_something").getExponent(), 0); EXPECT_EQ(sc.getSamplingState("GET_something").getMultiplicity(), 1); @@ -73,7 +162,7 @@ TEST_F(SamplingControllerTest, TestNonExisting) { StreamSummary summary(10); summary.offer("key1"); SamplingController sc; - sc.update(summary.getTopK(), 100); + sc.update(summary.getTopK(), summary.getN(), 100); EXPECT_EQ(sc.getSamplingState("key2").getExponent(), 0); EXPECT_EQ(sc.getSamplingState("key2").getMultiplicity(), 1); diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc index b5ef5a5494f88..2b40780b03336 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc @@ -93,7 +93,6 @@ TEST(StreamSummaryTest, TestExtendCapacity) { CompareCounter(it, 'a', 4, 0, __LINE__); CompareCounter(++it, 'b', 3, 0, __LINE__); CompareCounter(++it, 'c', 3, 1, __LINE__); - std::cout << __LINE__ << ": " << toString(summary.getTopK()) << std::endl; } // add item 'e', 'c' should be removed. From 875e35c082072f599ea3ffaa515c4d435c092701 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Thu, 25 Jan 2024 08:31:29 +0100 Subject: [PATCH 20/31] introduce MAX_SAMPLING_EXPONENT Signed-off-by: thomas.ebner --- .../opentelemetry/samplers/dynatrace/sampling_controller.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h index 37c45de359c65..a64fa7c45fef4 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h @@ -77,6 +77,7 @@ class SamplingController { absl::flat_hash_map sampling_exponents_; std::string rest_bucket_key_{}; mutable absl::Mutex mutex_{}; + static constexpr uint32_t MAX_EXPONENT = (1 << 4) - 1; // 15 static uint64_t computeEffectiveCount(const std::list>& top_k, @@ -115,7 +116,8 @@ class SamplingController { auto sampling_state = new_sampling_exponents.find(counter.getItem()); // sampling exponent has to be a power of 2. Find the exponent to have multiplicity near to // wanted_multiplicity - while (wanted_multiplicity > sampling_state->second.getMultiplicity()) { + while (wanted_multiplicity > sampling_state->second.getMultiplicity() && + sampling_state->second.getExponent() < MAX_EXPONENT) { sampling_state->second.increaseExponent(); } if (wanted_multiplicity < sampling_state->second.getMultiplicity()) { From 815f363ee8cc20f79b153b26d3c084c54b004fd1 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Thu, 25 Jan 2024 09:59:43 +0100 Subject: [PATCH 21/31] add debug log use a dedicated type for exponents map Signed-off-by: thomas.ebner --- .../samplers/dynatrace/dynatrace_sampler.cc | 20 ++--- .../samplers/dynatrace/dynatrace_sampler.h | 4 +- .../dynatrace/sampler_config_fetcher.cc | 5 +- .../samplers/dynatrace/sampling_controller.h | 39 +++++--- .../samplers/dynatrace/stream_summary.h | 90 ++++++++----------- .../dynatrace/dynatrace_sampler_test.cc | 12 +-- .../dynatrace/sampler_config_fetcher_test.cc | 53 +++++------ .../dynatrace/sampling_controller_test.cc | 14 +-- .../samplers/dynatrace/stream_summary_test.cc | 28 +++--- 9 files changed, 124 insertions(+), 141 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index a35d2867c964f..e34729a99a418 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -42,8 +42,8 @@ DynatraceSampler::DynatraceSampler( : tenant_id_(config.tenant_id()), cluster_id_(config.cluster_id()), dt_tracestate_entry_(tenant_id_, cluster_id_), sampler_config_fetcher_(std::move(sampler_config_fetcher)), - stream_summary_(std::make_unique>(STREAM_SUMMARY_SIZE)), - sampling_controller_(), counter_(0) { + stream_summary_(std::make_unique(STREAM_SUMMARY_SIZE)), + sampling_controller_() { timer_ = context.serverFactoryContext().mainThreadDispatcher().createTimer([this]() -> void { updateSamplingInfo(); @@ -86,7 +86,6 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional result.tracestate = parent_context->tracestate(); } } else { // make a sampling decision - counter_++; bool sample; int sampling_exponent; @@ -114,22 +113,15 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional void DynatraceSampler::updateSamplingInfo() { absl::MutexLock lock{&stream_summary_mutex_}; - auto topK = stream_summary_->getTopK(); + auto top_k = stream_summary_->getTopK(); auto last_period_count = stream_summary_->getN(); - // TODO: remove or convert to debug - ENVOY_LOG(info, "Hello from sampler timer. topk.size(): {}", last_period_count, topK.size()); - for (auto const& counter : topK) { - ENVOY_LOG(info, "-- {} : {}", counter.getItem(), counter.getValue()); - } - ENVOY_LOG(info, "counter_: {}", counter_); - // update sampling exponents - sampling_controller_.update(topK, last_period_count, + sampling_controller_.update(top_k, last_period_count, sampler_config_fetcher_->getSamplerConfig().getRootSpansPerMinute()); // Note: getTopK() returns references to values in StreamSummary. - // Do not destroy it while topK is used! - stream_summary_ = std::make_unique>(STREAM_SUMMARY_SIZE); + // Do not destroy it while top_k is used! + stream_summary_ = std::make_unique(STREAM_SUMMARY_SIZE); } std::string DynatraceSampler::getDescription() const { return "DynatraceSampler"; } diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index 72bf995caf3dc..bf7ba7856c26d 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -88,11 +88,11 @@ class DynatraceSampler : public Sampler, Logger::Loggable { std::string dt_tracestate_key_; SamplerConfigFetcherPtr sampler_config_fetcher_; - std::unique_ptr> stream_summary_; + std::unique_ptr stream_summary_; mutable absl::Mutex stream_summary_mutex_{}; Event::TimerPtr timer_; SamplingController sampling_controller_; - std::atomic counter_; // request counter for dummy sampling + void updateSamplingInfo(); }; diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc index dc35d3aef0a9b..818692bbdc0aa 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc @@ -51,11 +51,10 @@ void SamplerConfigFetcherImpl::onSuccess(const Http::AsyncClient::Request& /*req onRequestDone(); const auto response_code = Http::Utility::getResponseStatus(http_response->headers()); if (response_code == enumToInt(Http::Code::OK)) { - // TODO: remove log - ENVOY_LOG(info, "SamplerConfigFetcherImpl received success status code: {}", response_code); + ENVOY_LOG(debug, "SamplerConfigFetcherImpl received success status code: {}", response_code); sampler_config_.parse(http_response->bodyAsString()); } else { - ENVOY_LOG(error, "SamplerConfigFetcherImpl received a non-success status code: {}", + ENVOY_LOG(warn, "SamplerConfigFetcherImpl received a non-success status code: {}", response_code); } } diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h index a64fa7c45fef4..1757136e0c38f 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h @@ -30,15 +30,15 @@ class SamplingState { uint32_t exponent_{}; }; -class SamplingController { +using StreamSummaryT = StreamSummary; +using TopKListT = std::list>; + +class SamplingController : public Logger::Loggable { public: - void update(const std::list>& top_k, uint64_t last_period_count, - const uint32_t total_wanted) { + void update(const TopKListT& top_k, uint64_t last_period_count, const uint32_t total_wanted) { - // TODO: remove parameter - (void)last_period_count; - absl::flat_hash_map new_sampling_exponents; + SamplingExponentsT new_sampling_exponents; // start with sampling exponent 0, which means multiplicity == 1 (every span is sampled) for (auto const& counter : top_k) { new_sampling_exponents[counter.getItem()] = {}; @@ -48,6 +48,7 @@ class SamplingController { rest_bucket_key_ = (top_k.size() > 0) ? top_k.back().getItem() : ""; calculateSamplingExponents(top_k, total_wanted, new_sampling_exponents); + debug_log(top_k, new_sampling_exponents, last_period_count, total_wanted); absl::WriterMutexLock lock{&mutex_}; sampling_exponents_ = std::move(new_sampling_exponents); @@ -68,20 +69,31 @@ class SamplingController { return iter->second; } - uint64_t getEffectiveCount(const std::list>& top_k) { + uint64_t getEffectiveCount(const TopKListT& top_k) { absl::ReaderMutexLock lock{&mutex_}; return computeEffectiveCount(top_k, sampling_exponents_); } private: - absl::flat_hash_map sampling_exponents_; + using SamplingExponentsT = absl::flat_hash_map; + SamplingExponentsT sampling_exponents_; std::string rest_bucket_key_{}; mutable absl::Mutex mutex_{}; static constexpr uint32_t MAX_EXPONENT = (1 << 4) - 1; // 15 - static uint64_t - computeEffectiveCount(const std::list>& top_k, - const absl::flat_hash_map& sampling_exponents) { + void debug_log(const TopKListT& top_k, const SamplingExponentsT& new_sampling_exponents, + uint64_t last_period_count, const uint32_t total_wanted) { + ENVOY_LOG(debug, + "Updating sampling info. top_k.size(): {}, last_period_count: {}, total_wanted: {}", + top_k.size(), last_period_count, total_wanted); + for (auto const& counter : top_k) { + auto sampling_state = new_sampling_exponents.find(counter.getItem()); + ENVOY_LOG(debug, "- {}: value: {}, exponent: {}", counter.getItem(), counter.getValue(), + sampling_state->second.getExponent()); + } + } + static uint64_t computeEffectiveCount(const TopKListT& top_k, + const SamplingExponentsT& sampling_exponents) { uint64_t cnt = 0; for (auto const& counter : top_k) { auto sampling_state = sampling_exponents.find(counter.getItem()); @@ -96,9 +108,8 @@ class SamplingController { return cnt; } - void calculateSamplingExponents( - const std::list>& top_k, const uint32_t total_wanted, - absl::flat_hash_map& new_sampling_exponents) { + void calculateSamplingExponents(const TopKListT& top_k, const uint32_t total_wanted, + SamplingExponentsT& new_sampling_exponents) { const auto top_k_size = top_k.size(); if (top_k_size == 0 || total_wanted == 0) { return; diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h index c2b4ddcb645df..15332497296e1 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h @@ -4,6 +4,8 @@ #include #include +#include "source/common/common/assert.h" + #include "absl/container/flat_hash_map.h" #include "absl/status/status.h" #include "absl/types/optional.h" @@ -69,21 +71,19 @@ template class StreamSummary { typename detail::CounterIterator incrementCounter(detail::CounterIterator counter_iter, const uint64_t increment) { auto const bucket = counter_iter->bucket; - auto bucketNext = std::prev(bucket); + auto bucket_next = std::prev(bucket); counter_iter->value += increment; detail::CounterIterator elem; - if (bucketNext != buckets_.end() && counter_iter->value == bucketNext->value) { - counter_iter->bucket = bucketNext; - bucketNext->children.splice(bucketNext->children.end(), bucket->children, counter_iter); - elem = std::prev(bucketNext->children.end()); - // validate(); + if (bucket_next != buckets_.end() && counter_iter->value == bucket_next->value) { + counter_iter->bucket = bucket_next; + bucket_next->children.splice(bucket_next->children.end(), bucket->children, counter_iter); + elem = std::prev(bucket_next->children.end()); } else { - auto bucketNew = buckets_.emplace(bucket, counter_iter->value); - counter_iter->bucket = bucketNew; - bucketNew->children.splice(bucketNew->children.end(), bucket->children, counter_iter); - elem = std::prev(bucketNew->children.end()); - // validate(); + auto bucket_new = buckets_.emplace(bucket, counter_iter->value); + counter_iter->bucket = bucket_new; + bucket_new->children.splice(bucket_new->children.end(), bucket->children, counter_iter); + elem = std::prev(bucket_new->children.end()); } if (bucket->children.empty()) { buckets_.erase(bucket); @@ -131,13 +131,20 @@ template class StreamSummary { return absl::OkStatus(); } + inline void validateDbg() { +#if !defined(NDEBUG) + ASSERT(validate().ok()); +#endif + } + public: explicit StreamSummary(const size_t capacity) : capacity_(capacity) { - auto& newBucket = buckets_.emplace_back(0); + auto& new_bucket = buckets_.emplace_back(0); for (size_t i = 0; i < capacity; ++i) { - newBucket.children.emplace_back(buckets_.begin()); + // initialize with empty counters, optional item will not be set + new_bucket.children.emplace_back(buckets_.begin()); } - // validate(); + validateDbg(); } size_t getCapacity() const { return capacity_; } @@ -145,37 +152,33 @@ template class StreamSummary { absl::Status validate() const { return validateInternal(); } Counter offer(T const& item, const uint64_t increment = 1) { - // validate(); - ++n_; auto iter = cache_.find(item); if (iter != cache_.end()) { iter->second = incrementCounter(iter->second, increment); - // validate(); + validateDbg(); return *iter->second; } else { - auto minElement = std::prev(buckets_.back().children.end()); - auto originalMinValue = minElement->value; - if (minElement->item) { + auto min_element = std::prev(buckets_.back().children.end()); + auto original_min_value = min_element->value; + if (min_element + ->item) { // element was already used (otherwise optional item would be not set) // remove old from cache - auto old_iter = cache_.find(*minElement->item); + auto old_iter = cache_.find(*min_element->item); if (old_iter != cache_.end()) { cache_.erase(old_iter); } } - minElement->item = item; - minElement = incrementCounter(minElement, increment); - cache_[item] = minElement; - // TODO: I think the following comment and `if (cache_.size() <= capacity_)` do not make - // sense. - // TODO: cache_.size() has to be <= capacity! - // if we aren't full on capacity yet, we don't need to add error since we have seen every item - // so far + min_element->item = item; + min_element = incrementCounter(min_element, increment); + cache_[item] = min_element; if (cache_.size() <= capacity_) { - minElement->error = originalMinValue; + // should always be true, but keep it to be aligned to reference implementation + // originalMinValue will be 0 if element wasn't already used + min_element->error = original_min_value; } - // validate(); - return *minElement; + validateDbg(); + return *min_element; } } @@ -195,29 +198,6 @@ template class StreamSummary { } return r; } - - // this makes using the error somewhat useless - void scaleDown(const float factor) { - n_ = 0; - for (auto bucket_iter = buckets_.begin(); bucket_iter != buckets_.end(); bucket_iter++) { - bucket_iter->value = std::max(bucket_iter->value / factor, 1.); - for (auto& child : bucket_iter->children) { - child.value = std::max(child.value / factor, 1.); - n_ += child.value; - child.error /= factor; - } - auto prev = std::prev(bucket_iter); - if (prev != buckets_.end() && prev->value == bucket_iter->value) { - std::for_each(bucket_iter->children.begin(), bucket_iter->children.end(), - [&prev](detail::Counter& c) { c.bucket = prev; }); - prev->children.splice(prev->children.end(), bucket_iter->children, - bucket_iter->children.begin(), bucket_iter->children.end()); - buckets_.erase(bucket_iter); - bucket_iter = prev; - } - } - // validate(); - } }; } // namespace OpenTelemetry diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc index 62e5a7f0797db..d2fbadb3f7c20 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc @@ -61,20 +61,20 @@ class DynatraceSamplerTest : public testing::Test { >>>>>>> 2705f51fef (extend test) protected: - NiceMock tracerFactoryContext_; + NiceMock tracer_factory_context_; envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig config_; }; TEST_F(DynatraceSamplerTest, TestGetDescription) { auto scf = std::make_unique(); - DynatraceSampler sampler(config_, tracerFactoryContext_, std::move(scf)); + DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); EXPECT_STREQ(sampler.getDescription().c_str(), "DynatraceSampler"); } // Verify sampler being invoked with no parent span context TEST_F(DynatraceSamplerTest, TestWithoutParentContext) { auto scf = std::make_unique(); - DynatraceSampler sampler(config_, tracerFactoryContext_, std::move(scf)); + DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); auto sampling_result = sampler_->shouldSample(absl::nullopt, trace_id, "operation_name", @@ -114,11 +114,11 @@ TEST_F(DynatraceSamplerTest, TestSampling) { EXPECT_CALL(*scf, getSamplerConfig()).WillRepeatedly(testing::ReturnRef(config)); auto timer = - new NiceMock(&tracerFactoryContext_.server_factory_context_.dispatcher_); - ON_CALL(tracerFactoryContext_.server_factory_context_.dispatcher_, createTimer_(_)) + new NiceMock(&tracer_factory_context_.server_factory_context_.dispatcher_); + ON_CALL(tracer_factory_context_.server_factory_context_.dispatcher_, createTimer_(_)) .WillByDefault(Invoke([timer](Event::TimerCb) { return timer; })); - DynatraceSampler sampler(config_, tracerFactoryContext_, std::move(scf)); + DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); Tracing::TestTraceContextImpl trace_context_1{}; trace_context_1.context_method_ = "GET"; trace_context_1.context_path_ = "/path"; diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher_test.cc index 7c6f97e01d1fa..04650a62040ce 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher_test.cc @@ -26,7 +26,7 @@ using testing::ReturnRef; class SamplerConfigFetcherTest : public testing::Test { public: SamplerConfigFetcherTest() - : request_(&tracerFactoryContext_.server_factory_context_.cluster_manager_ + : request_(&tracer_factory_context_.server_factory_context_.cluster_manager_ .thread_local_cluster_.async_client_) { const std::string yaml_string = R"EOF( cluster: "cluster_name" @@ -35,18 +35,18 @@ class SamplerConfigFetcherTest : public testing::Test { )EOF"; TestUtility::loadFromYaml(yaml_string, http_uri_); - ON_CALL(tracerFactoryContext_.server_factory_context_.cluster_manager_, + ON_CALL(tracer_factory_context_.server_factory_context_.cluster_manager_, getThreadLocalCluster(_)) - .WillByDefault(Return( - &tracerFactoryContext_.server_factory_context_.cluster_manager_.thread_local_cluster_)); - timer_ = - new NiceMock(&tracerFactoryContext_.server_factory_context_.dispatcher_); - ON_CALL(tracerFactoryContext_.server_factory_context_.dispatcher_, createTimer_(_)) + .WillByDefault(Return(&tracer_factory_context_.server_factory_context_.cluster_manager_ + .thread_local_cluster_)); + timer_ = new NiceMock( + &tracer_factory_context_.server_factory_context_.dispatcher_); + ON_CALL(tracer_factory_context_.server_factory_context_.dispatcher_, createTimer_(_)) .WillByDefault(Invoke([this](Event::TimerCb) { return timer_; })); } protected: - NiceMock tracerFactoryContext_; + NiceMock tracer_factory_context_; envoy::config::core::v3::HttpUri http_uri_; NiceMock* timer_; Http::MockAsyncClientRequest request_; @@ -65,60 +65,60 @@ MATCHER_P(MessageMatcher, unusedArg, "") { // Test a request is sent if timer fires TEST_F(SamplerConfigFetcherTest, TestRequestIsSent) { - EXPECT_CALL(tracerFactoryContext_.server_factory_context_.cluster_manager_.thread_local_cluster_ + EXPECT_CALL(tracer_factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_ .async_client_, send_(MessageMatcher("unused-but-machtes-requires-an-arg"), _, _)); - SamplerConfigFetcherImpl configFetcher(tracerFactoryContext_, http_uri_, "tokenval"); + SamplerConfigFetcherImpl config_fetcher(tracer_factory_context_, http_uri_, "tokenval"); timer_->invokeCallback(); } // Test receiving a response with code 200 and valid json TEST_F(SamplerConfigFetcherTest, TestResponseOk) { - SamplerConfigFetcherImpl configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); + SamplerConfigFetcherImpl config_fetcher(tracer_factory_context_, http_uri_, "tokenXASSD"); timer_->invokeCallback(); Http::ResponseMessagePtr message(new Http::ResponseMessageImpl( Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); message->body().add("{\n \"rootSpansPerMinute\" : 4356 \n }"); - configFetcher.onSuccess(request_, std::move(message)); - EXPECT_EQ(configFetcher.getSamplerConfig().getRootSpansPerMinute(), 4356); + config_fetcher.onSuccess(request_, std::move(message)); + EXPECT_EQ(config_fetcher.getSamplerConfig().getRootSpansPerMinute(), 4356); EXPECT_TRUE(timer_->enabled()); } // Test receiving a response with code 200 and unexpected json TEST_F(SamplerConfigFetcherTest, TestResponseOkInvalidJson) { - SamplerConfigFetcherImpl configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); + SamplerConfigFetcherImpl config_fetcher(tracer_factory_context_, http_uri_, "tokenXASSD"); timer_->invokeCallback(); Http::ResponseMessagePtr message(new Http::ResponseMessageImpl( Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); message->body().add("{\n "); - configFetcher.onSuccess(request_, std::move(message)); - EXPECT_EQ(configFetcher.getSamplerConfig().getRootSpansPerMinute(), + config_fetcher.onSuccess(request_, std::move(message)); + EXPECT_EQ(config_fetcher.getSamplerConfig().getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); EXPECT_TRUE(timer_->enabled()); } // Test receiving a response with code != 200 TEST_F(SamplerConfigFetcherTest, TestResponseErrorCode) { - SamplerConfigFetcherImpl configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); + SamplerConfigFetcherImpl config_fetcher(tracer_factory_context_, http_uri_, "tokenXASSD"); timer_->invokeCallback(); Http::ResponseMessagePtr message(new Http::ResponseMessageImpl( Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "401"}}})); message->body().add("{\n \"rootSpansPerMinute\" : 4356 \n }"); - configFetcher.onSuccess(request_, std::move(message)); - EXPECT_EQ(configFetcher.getSamplerConfig().getRootSpansPerMinute(), + config_fetcher.onSuccess(request_, std::move(message)); + EXPECT_EQ(config_fetcher.getSamplerConfig().getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); EXPECT_TRUE(timer_->enabled()); } // Test sending failed TEST_F(SamplerConfigFetcherTest, TestOnFailure) { - SamplerConfigFetcherImpl configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); + SamplerConfigFetcherImpl config_fetcher(tracer_factory_context_, http_uri_, "tokenXASSD"); timer_->invokeCallback(); - configFetcher.onFailure(request_, Http::AsyncClient::FailureReason::Reset); - EXPECT_EQ(configFetcher.getSamplerConfig().getRootSpansPerMinute(), + config_fetcher.onFailure(request_, Http::AsyncClient::FailureReason::Reset); + EXPECT_EQ(config_fetcher.getSamplerConfig().getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); EXPECT_TRUE(timer_->enabled()); } @@ -126,17 +126,18 @@ TEST_F(SamplerConfigFetcherTest, TestOnFailure) { // Test calling onBeforeFinalizeUpstreamSpan TEST_F(SamplerConfigFetcherTest, TestOnBeforeFinalizeUpstreamSpan) { Tracing::MockSpan child_span_; - SamplerConfigFetcherImpl configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); + SamplerConfigFetcherImpl config_fetcher(tracer_factory_context_, http_uri_, "tokenXASSD"); // onBeforeFinalizeUpstreamSpan() is an empty method, nothing should happen - configFetcher.onBeforeFinalizeUpstreamSpan(child_span_, nullptr); + config_fetcher.onBeforeFinalizeUpstreamSpan(child_span_, nullptr); } // Test invoking the timer if no cluster can be found TEST_F(SamplerConfigFetcherTest, TestNoCluster) { // simulate no configured cluster, return nullptr. - ON_CALL(tracerFactoryContext_.server_factory_context_.cluster_manager_, getThreadLocalCluster(_)) + ON_CALL(tracer_factory_context_.server_factory_context_.cluster_manager_, + getThreadLocalCluster(_)) .WillByDefault(Return(nullptr)); - SamplerConfigFetcherImpl configFetcher(tracerFactoryContext_, http_uri_, "tokenXASSD"); + SamplerConfigFetcherImpl config_fetcher(tracer_factory_context_, http_uri_, "tokenXASSD"); timer_->invokeCallback(); // should not crash or throw. } diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc index 4c533304fcb43..b261b48a2c159 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc @@ -19,7 +19,7 @@ namespace OpenTelemetry { namespace { -void offerEntry(StreamSummary& summary, const std::string& value, int count) { +void offerEntry(StreamSummaryT& summary, const std::string& value, int count) { for (int i = 0; i < count; i++) { summary.offer(value); } @@ -39,7 +39,7 @@ template std::string toString(T const& list) { class SamplingControllerTest : public testing::Test {}; TEST_F(SamplingControllerTest, TestManyDifferentRequests) { - StreamSummary summary(DynatraceSampler::STREAM_SUMMARY_SIZE); + StreamSummaryT summary(DynatraceSampler::STREAM_SUMMARY_SIZE); offerEntry(summary, "1", 2000); offerEntry(summary, "2", 1000); offerEntry(summary, "3", 750); @@ -66,7 +66,7 @@ TEST_F(SamplingControllerTest, TestManyDifferentRequests) { } TEST_F(SamplingControllerTest, TestManyRequests) { - StreamSummary summary(DynatraceSampler::STREAM_SUMMARY_SIZE); + StreamSummaryT summary(DynatraceSampler::STREAM_SUMMARY_SIZE); offerEntry(summary, "1", 8600); offerEntry(summary, "2", 5000); offerEntry(summary, "3", 4000); @@ -93,7 +93,7 @@ TEST_F(SamplingControllerTest, TestManyRequests) { } TEST_F(SamplingControllerTest, TestSomeRequests) { - StreamSummary summary(DynatraceSampler::STREAM_SUMMARY_SIZE); + StreamSummaryT summary(DynatraceSampler::STREAM_SUMMARY_SIZE); offerEntry(summary, "1", 7500); offerEntry(summary, "2", 1000); offerEntry(summary, "3", 1); @@ -119,7 +119,7 @@ TEST_F(SamplingControllerTest, TestSomeRequests) { } TEST_F(SamplingControllerTest, TestSimple) { - StreamSummary summary(10); + StreamSummaryT summary(10); // offerEntry data offerEntry(summary, "GET_xxxx", 300); offerEntry(summary, "POST_asdf", 200); @@ -150,7 +150,7 @@ TEST_F(SamplingControllerTest, TestSimple) { } TEST_F(SamplingControllerTest, TestEmpty) { - StreamSummary summary(10); + StreamSummaryT summary(10); SamplingController sc; sc.update(summary.getTopK(), summary.getN(), 100); @@ -159,7 +159,7 @@ TEST_F(SamplingControllerTest, TestEmpty) { } TEST_F(SamplingControllerTest, TestNonExisting) { - StreamSummary summary(10); + StreamSummaryT summary(10); summary.offer("key1"); SamplingController sc; sc.update(summary.getTopK(), summary.getN(), 100); diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc index 2b40780b03336..e610bf10b6521 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc @@ -32,9 +32,9 @@ void CompareCounter(typename std::list>::iterator counter, T item, ui TEST(StreamSummaryTest, TestEmpty) { StreamSummary summary(4); EXPECT_EQ(summary.getN(), 0); - auto topK = summary.getTopK(); - EXPECT_EQ(topK.size(), 0); - EXPECT_EQ(topK.begin(), topK.end()); + auto top_k = summary.getTopK(); + EXPECT_EQ(top_k.size(), 0); + EXPECT_EQ(top_k.begin(), top_k.end()); EXPECT_TRUE(summary.validate().ok()); } @@ -52,9 +52,9 @@ TEST(StreamSummaryTest, TestSimple) { EXPECT_TRUE(summary.validate().ok()); EXPECT_EQ(summary.getN(), 8); - auto topK = summary.getTopK(); - EXPECT_EQ(topK.size(), 4); - auto it = topK.begin(); + auto top_k = summary.getTopK(); + EXPECT_EQ(top_k.size(), 4); + auto it = top_k.begin(); CompareCounter(it, 'a', 4, 0, __LINE__); CompareCounter(++it, 'b', 2, 0, __LINE__); CompareCounter(++it, 'c', 1, 0, __LINE__); @@ -87,9 +87,9 @@ TEST(StreamSummaryTest, TestExtendCapacity) { EXPECT_TRUE(summary.validate().ok()); { - auto topK = summary.getTopK(); - auto it = topK.begin(); - EXPECT_EQ(topK.size(), 3); + auto top_k = summary.getTopK(); + auto it = top_k.begin(); + EXPECT_EQ(top_k.size(), 3); CompareCounter(it, 'a', 4, 0, __LINE__); CompareCounter(++it, 'b', 3, 0, __LINE__); CompareCounter(++it, 'c', 3, 1, __LINE__); @@ -98,9 +98,9 @@ TEST(StreamSummaryTest, TestExtendCapacity) { // add item 'e', 'c' should be removed. summary.offer('e'); { - auto topK = summary.getTopK(); - auto it = topK.begin(); - EXPECT_EQ(topK.size(), 3); + auto top_k = summary.getTopK(); + auto it = top_k.begin(); + EXPECT_EQ(top_k.size(), 3); CompareCounter(it, 'a', 4, 0, __LINE__); CompareCounter(++it, 'e', 4, 3, __LINE__); CompareCounter(++it, 'b', 3, 0, __LINE__); @@ -117,8 +117,8 @@ TEST(StreamSummaryTest, TestRandomInsertOrder) { for (auto const c : v) { summary.offer(c); } - auto topK = summary.getTopK(); - auto it = topK.begin(); + auto top_k = summary.getTopK(); + auto it = top_k.begin(); CompareCounter(it, 'a', 6, 0, __LINE__); CompareCounter(++it, 'b', 5, 0, __LINE__); CompareCounter(++it, 'c', 4, 0, __LINE__); From 3585ce14a8759c34016ea5eb0da9bfe65184c172 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Fri, 26 Jan 2024 07:48:25 +0100 Subject: [PATCH 22/31] move getSamplingKey() to SamplingController, add unit test Signed-off-by: thomas.ebner --- .../samplers/dynatrace/dynatrace_sampler.cc | 12 +++--------- .../samplers/dynatrace/sampling_controller.h | 13 ++++++++++--- .../samplers/dynatrace/sampling_controller_test.cc | 12 ++++++++++++ 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index e34729a99a418..2ed240e7be616 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -26,13 +26,6 @@ static constexpr std::chrono::seconds SAMPLING_UPDATE_TIMER_DURATION{20}; const char* SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME = "sampling_extrapolation_set_in_sampler"; -std::string getSamplingKey(const absl::string_view path_query, const absl::string_view method) { - size_t query_offset = path_query.find('?'); - auto path = - path_query.substr(0, query_offset != path_query.npos ? query_offset : path_query.size()); - return absl::StrCat(method, "_", path); -} - } // namespace DynatraceSampler::DynatraceSampler( @@ -63,8 +56,9 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional // trace_context->path() returns path and query. query part is removed in getSamplingKey() const std::string sampling_key = - trace_context.has_value() ? getSamplingKey(trace_context->path(), trace_context->method()) - : ""; + trace_context.has_value() + ? sampling_controller_.getSamplingKey(trace_context->path(), trace_context->method()) + : ""; if (!sampling_key.empty()) { absl::MutexLock lock{&stream_summary_mutex_}; diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h index 1757136e0c38f..eadb16422db7a 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h @@ -48,7 +48,7 @@ class SamplingController : public Logger::Loggable { rest_bucket_key_ = (top_k.size() > 0) ? top_k.back().getItem() : ""; calculateSamplingExponents(top_k, total_wanted, new_sampling_exponents); - debug_log(top_k, new_sampling_exponents, last_period_count, total_wanted); + logSamplingInfo(top_k, new_sampling_exponents, last_period_count, total_wanted); absl::WriterMutexLock lock{&mutex_}; sampling_exponents_ = std::move(new_sampling_exponents); @@ -74,6 +74,13 @@ class SamplingController : public Logger::Loggable { return computeEffectiveCount(top_k, sampling_exponents_); } + std::string getSamplingKey(const absl::string_view path_query, const absl::string_view method) { + size_t query_offset = path_query.find('?'); + auto path = + path_query.substr(0, query_offset != path_query.npos ? query_offset : path_query.size()); + return absl::StrCat(method, "_", path); + } + private: using SamplingExponentsT = absl::flat_hash_map; SamplingExponentsT sampling_exponents_; @@ -81,8 +88,8 @@ class SamplingController : public Logger::Loggable { mutable absl::Mutex mutex_{}; static constexpr uint32_t MAX_EXPONENT = (1 << 4) - 1; // 15 - void debug_log(const TopKListT& top_k, const SamplingExponentsT& new_sampling_exponents, - uint64_t last_period_count, const uint32_t total_wanted) { + void logSamplingInfo(const TopKListT& top_k, const SamplingExponentsT& new_sampling_exponents, + uint64_t last_period_count, const uint32_t total_wanted) { ENVOY_LOG(debug, "Updating sampling info. top_k.size(): {}, last_period_count: {}, total_wanted: {}", top_k.size(), last_period_count, total_wanted); diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc index b261b48a2c159..d528326414ca8 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc @@ -220,6 +220,18 @@ TEST(SamplingStateTest, TestShouldSample) { EXPECT_FALSE(sst.shouldSample(2049)); } +TEST_F(SamplingControllerTest, TestGetSamplingKey) { + SamplingController sc; + std::string key = sc.getSamplingKey("somepath", "GET"); + EXPECT_STREQ(key.c_str(), "GET_somepath"); + + key = sc.getSamplingKey("somepath?withquery", "POST"); + EXPECT_STREQ(key.c_str(), "POST_somepath"); + + key = sc.getSamplingKey("anotherpath", "PUT"); + EXPECT_STREQ(key.c_str(), "PUT_anotherpath"); +} + } // namespace OpenTelemetry } // namespace Tracers } // namespace Extensions From 0b7c239f935bfefb52ee2dd857871ff02d6e7ebd Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Fri, 26 Jan 2024 09:52:03 +0100 Subject: [PATCH 23/31] split sampling_controller into .h and .cc file Signed-off-by: thomas.ebner --- .../opentelemetry/samplers/dynatrace/BUILD | 2 + .../samplers/dynatrace/config.cc | 2 +- .../samplers/dynatrace/dynatrace_sampler.cc | 31 ++-- .../samplers/dynatrace/sampler_config.cc | 29 ++++ .../samplers/dynatrace/sampler_config.h | 18 +-- .../samplers/dynatrace/sampling_controller.cc | 139 ++++++++++++++++++ .../samplers/dynatrace/sampling_controller.h | 122 ++------------- .../dynatrace/sampling_controller_test.cc | 7 +- 8 files changed, 195 insertions(+), 155 deletions(-) create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.cc create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.cc diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD b/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD index aa55876afb244..441f1dabdd239 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD @@ -25,7 +25,9 @@ envoy_cc_library( name = "dynatrace_sampler_lib", srcs = [ "dynatrace_sampler.cc", + "sampler_config.cc", "sampler_config_fetcher.cc", + "sampling_controller.cc", ], hdrs = [ "dynatrace_sampler.h", diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.cc index a818b7b2b6632..e19943aa92317 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.cc @@ -1,4 +1,4 @@ -#include "config.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/config.h" #include diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index 2ed240e7be616..27ffa1fb7f2b6 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -6,12 +6,17 @@ #include "source/common/common/hash.h" #include "source/common/config/datasource.h" +<<<<<<< HEAD +======= +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/tracestate.h" +>>>>>>> b53b23ded4 (fix include paths, add a few const) #include "source/extensions/tracers/opentelemetry/samplers/sampler.h" #include "source/extensions/tracers/opentelemetry/span_context.h" #include "source/extensions/tracers/opentelemetry/trace_state.h" #include "absl/strings/str_cat.h" -#include "stream_summary.h" namespace Envoy { namespace Extensions { @@ -20,10 +25,7 @@ namespace OpenTelemetry { namespace { -// TODO: should be one minute -static constexpr std::chrono::seconds SAMPLING_UPDATE_TIMER_DURATION{20}; -// static constexpr std::chrono::minutes SAMPLING_UPDATE_TIMER_DURATION{1}; - +constexpr std::chrono::minutes SAMPLING_UPDATE_TIMER_DURATION{1}; const char* SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME = "sampling_extrapolation_set_in_sampler"; } // namespace @@ -69,26 +71,13 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional auto trace_state = TraceState::fromHeader(parent_context.has_value() ? parent_context->tracestate() : ""); - std::string trace_state_value; - - if (trace_state->get(dt_tracestate_key_, trace_state_value)) { - // we found a DT trace decision in tracestate header - if (FW4Tag fw4_tag = FW4Tag::create(trace_state_value); fw4_tag.isValid()) { - result.decision = fw4_tag.isIgnored() ? Decision::Drop : Decision::RecordAndSample; - att[SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME] = - std::to_string(fw4_tag.getSamplingExponent()); - result.tracestate = parent_context->tracestate(); - } - } else { // make a sampling decision - bool sample; - int sampling_exponent; - + } else { // do a decision based on the calculated exponent // at the moment we use a hash of the trace_id as random number const auto hash = MurmurHash::murmurHash2(trace_id); const auto sampling_state = sampling_controller_.getSamplingState(sampling_key); - sample = sampling_state.shouldSample(hash); - sampling_exponent = sampling_state.getExponent(); + const bool sample = sampling_state.shouldSample(hash); + const auto sampling_exponent = sampling_state.getExponent(); att[SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME] = std::to_string(sampling_exponent); diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.cc new file mode 100644 index 0000000000000..ab6ca4b71f450 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.cc @@ -0,0 +1,29 @@ +#include + +#include "source/common/json/json_loader.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +void SamplerConfig::parse(const std::string& json) { + const auto result = Envoy::Json::Factory::loadFromStringNoThrow(json); + if (result.ok()) { + const auto obj = result.value(); + if (obj->hasObject("rootSpansPerMinute")) { + const auto value = obj->getInteger("rootSpansPerMinute", ROOT_SPANS_PER_MINUTE_DEFAULT); + root_spans_per_minute_.store(value); + return; + } + (void)obj; + } + // didn't get a value, reset to default + root_spans_per_minute_.store(ROOT_SPANS_PER_MINUTE_DEFAULT); +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h index 46989a1640725..888f0a2cd3bbb 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h @@ -3,9 +3,6 @@ #include #include #include -#include - -#include "source/common/json/json_loader.h" namespace Envoy { namespace Extensions { @@ -16,20 +13,7 @@ class SamplerConfig { public: static constexpr uint32_t ROOT_SPANS_PER_MINUTE_DEFAULT = 1000; - void parse(const std::string& json) { - const auto result = Envoy::Json::Factory::loadFromStringNoThrow(json); - if (result.ok()) { - const auto obj = result.value(); - if (obj->hasObject("rootSpansPerMinute")) { - const auto value = obj->getInteger("rootSpansPerMinute", ROOT_SPANS_PER_MINUTE_DEFAULT); - root_spans_per_minute_.store(value); - return; - } - (void)obj; - } - // didn't get a value, reset to default - root_spans_per_minute_.store(ROOT_SPANS_PER_MINUTE_DEFAULT); - } + void parse(const std::string& json); uint32_t getRootSpansPerMinute() const { return root_spans_per_minute_.load(); } diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.cc new file mode 100644 index 0000000000000..b9e99246fe20a --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.cc @@ -0,0 +1,139 @@ +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +namespace {} + +void SamplingController::update(const TopKListT& top_k, uint64_t last_period_count, + const uint32_t total_wanted) { + + SamplingExponentsT new_sampling_exponents; + // start with sampling exponent 0, which means multiplicity == 1 (every span is sampled) + for (auto const& counter : top_k) { + new_sampling_exponents[counter.getItem()] = {}; + } + + // use the last entry as "rest bucket", which is used for new/unknown requests + rest_bucket_key_ = (!top_k.empty()) ? top_k.back().getItem() : ""; + + calculateSamplingExponents(top_k, total_wanted, new_sampling_exponents); + logSamplingInfo(top_k, new_sampling_exponents, last_period_count, total_wanted); + + absl::WriterMutexLock lock{&mutex_}; + sampling_exponents_ = std::move(new_sampling_exponents); +} + +uint64_t SamplingController::getEffectiveCount(const TopKListT& top_k) const { + absl::ReaderMutexLock lock{&mutex_}; + return calculateEffectiveCount(top_k, sampling_exponents_); +} + +SamplingState SamplingController::getSamplingState(const std::string& sampling_key) const { + absl::ReaderMutexLock lock{&mutex_}; + auto iter = sampling_exponents_.find(sampling_key); + if (iter == + sampling_exponents_ + .end()) { // we don't have a sampling exponent for this exponent, use "rest bucket" + auto rest_bucket_iter = sampling_exponents_.find(rest_bucket_key_); + if (rest_bucket_iter != sampling_exponents_.end()) { + return rest_bucket_iter->second; + } + return {}; + } + return iter->second; +} + +std::string SamplingController::getSamplingKey(const absl::string_view path_query, + const absl::string_view method) { + const size_t query_offset = path_query.find('?'); + auto path = + path_query.substr(0, query_offset != path_query.npos ? query_offset : path_query.size()); + return absl::StrCat(method, "_", path); +} + +void SamplingController::logSamplingInfo(const TopKListT& top_k, + const SamplingExponentsT& new_sampling_exponents, + uint64_t last_period_count, + const uint32_t total_wanted) const { + ENVOY_LOG(debug, + "Updating sampling info. top_k.size(): {}, last_period_count: {}, total_wanted: {}", + top_k.size(), last_period_count, total_wanted); + for (auto const& counter : top_k) { + auto sampling_state = new_sampling_exponents.find(counter.getItem()); + ENVOY_LOG(debug, "- {}: value: {}, exponent: {}", counter.getItem(), counter.getValue(), + sampling_state->second.getExponent()); + } +} + +uint64_t +SamplingController::calculateEffectiveCount(const TopKListT& top_k, + const SamplingExponentsT& sampling_exponents) const { + uint64_t cnt = 0; + for (auto const& counter : top_k) { + auto sampling_state = sampling_exponents.find(counter.getItem()); + if (sampling_state == sampling_exponents.end()) { + continue; + } + auto counterVal = counter.getValue(); + auto mul = sampling_state->second.getMultiplicity(); + auto res = counterVal / mul; + cnt += res; + } + return cnt; +} + +void SamplingController::calculateSamplingExponents( + const TopKListT& top_k, const uint32_t total_wanted, + SamplingExponentsT& new_sampling_exponents) const { + const auto top_k_size = top_k.size(); + if (top_k_size == 0 || total_wanted == 0) { + return; + } + + // number of requests which are allowed for every entry + const uint32_t allowed_per_entry = total_wanted / top_k_size; + + for (auto& counter : top_k) { + // allowed multiplicity for this entry + auto wanted_multiplicity = counter.getValue() / allowed_per_entry; + if (wanted_multiplicity < 0) { + wanted_multiplicity = 1; + } + auto sampling_state = new_sampling_exponents.find(counter.getItem()); + // sampling exponent has to be a power of 2. Find the exponent to have multiplicity near to + // wanted_multiplicity + while (wanted_multiplicity > sampling_state->second.getMultiplicity() && + sampling_state->second.getExponent() < MAX_EXPONENT) { + sampling_state->second.increaseExponent(); + } + if (wanted_multiplicity < sampling_state->second.getMultiplicity()) { + // we want to have multiplicity <= wanted_multiplicity, therefore exponent is decrease once. + sampling_state->second.decreaseExponent(); + } + } + + auto effective_count = calculateEffectiveCount(top_k, new_sampling_exponents); + // There might be entries where allowed_per_entry is greater than their count. + // Therefore, we would sample nubmer of total_wanted requests + // To avoid this, we decrease the exponent of other entries if possible + if (effective_count < total_wanted) { + for (int i = 0; i < 5; i++) { // max tries + for (auto reverse_it = top_k.rbegin(); reverse_it != top_k.rend(); + ++reverse_it) { // start with lowest frequency + auto rev_sampling_state = new_sampling_exponents.find(reverse_it->getItem()); + rev_sampling_state->second.decreaseExponent(); + effective_count = calculateEffectiveCount(top_k, new_sampling_exponents); + if (effective_count >= total_wanted) { // we are done + return; + } + } + } + } +} +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h index eadb16422db7a..d16e9b7c2d13e 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h @@ -36,50 +36,14 @@ using TopKListT = std::list>; class SamplingController : public Logger::Loggable { public: - void update(const TopKListT& top_k, uint64_t last_period_count, const uint32_t total_wanted) { + void update(const TopKListT& top_k, uint64_t last_period_count, const uint32_t total_wanted); - SamplingExponentsT new_sampling_exponents; - // start with sampling exponent 0, which means multiplicity == 1 (every span is sampled) - for (auto const& counter : top_k) { - new_sampling_exponents[counter.getItem()] = {}; - } - - // use the last entry as "rest bucket", which is used for new/unknown requests - rest_bucket_key_ = (top_k.size() > 0) ? top_k.back().getItem() : ""; - - calculateSamplingExponents(top_k, total_wanted, new_sampling_exponents); - logSamplingInfo(top_k, new_sampling_exponents, last_period_count, total_wanted); + SamplingState getSamplingState(const std::string& sampling_key) const; - absl::WriterMutexLock lock{&mutex_}; - sampling_exponents_ = std::move(new_sampling_exponents); - } + uint64_t getEffectiveCount(const TopKListT& top_k) const; - SamplingState getSamplingState(const std::string& sampling_key) const { - absl::ReaderMutexLock lock{&mutex_}; - auto iter = sampling_exponents_.find(sampling_key); - if (iter == - sampling_exponents_ - .end()) { // we don't have a sampling exponent for this exponent, use "rest bucket" - auto rest_bucket_iter = sampling_exponents_.find(rest_bucket_key_); - if (rest_bucket_iter != sampling_exponents_.end()) { - return rest_bucket_iter->second; - } - return {}; - } - return iter->second; - } - - uint64_t getEffectiveCount(const TopKListT& top_k) { - absl::ReaderMutexLock lock{&mutex_}; - return computeEffectiveCount(top_k, sampling_exponents_); - } - - std::string getSamplingKey(const absl::string_view path_query, const absl::string_view method) { - size_t query_offset = path_query.find('?'); - auto path = - path_query.substr(0, query_offset != path_query.npos ? query_offset : path_query.size()); - return absl::StrCat(method, "_", path); - } + static std::string getSamplingKey(const absl::string_view path_query, + const absl::string_view method); private: using SamplingExponentsT = absl::flat_hash_map; @@ -89,79 +53,13 @@ class SamplingController : public Logger::Loggable { static constexpr uint32_t MAX_EXPONENT = (1 << 4) - 1; // 15 void logSamplingInfo(const TopKListT& top_k, const SamplingExponentsT& new_sampling_exponents, - uint64_t last_period_count, const uint32_t total_wanted) { - ENVOY_LOG(debug, - "Updating sampling info. top_k.size(): {}, last_period_count: {}, total_wanted: {}", - top_k.size(), last_period_count, total_wanted); - for (auto const& counter : top_k) { - auto sampling_state = new_sampling_exponents.find(counter.getItem()); - ENVOY_LOG(debug, "- {}: value: {}, exponent: {}", counter.getItem(), counter.getValue(), - sampling_state->second.getExponent()); - } - } - static uint64_t computeEffectiveCount(const TopKListT& top_k, - const SamplingExponentsT& sampling_exponents) { - uint64_t cnt = 0; - for (auto const& counter : top_k) { - auto sampling_state = sampling_exponents.find(counter.getItem()); - if (sampling_state == sampling_exponents.end()) { - continue; - } - auto counterVal = counter.getValue(); - auto mul = sampling_state->second.getMultiplicity(); - auto res = counterVal / mul; - cnt += res; - } - return cnt; - } - - void calculateSamplingExponents(const TopKListT& top_k, const uint32_t total_wanted, - SamplingExponentsT& new_sampling_exponents) { - const auto top_k_size = top_k.size(); - if (top_k_size == 0 || total_wanted == 0) { - return; - } + uint64_t last_period_count, const uint32_t total_wanted) const; - // number of requests which are allowed for every entry - const uint32_t allowed_per_entry = total_wanted / top_k_size; // requests which are allowed - - for (auto& counter : top_k) { - // allowed multiplicity for this entry - auto wanted_multiplicity = counter.getValue() / allowed_per_entry; - if (wanted_multiplicity < 0) { - wanted_multiplicity = 1; - } - auto sampling_state = new_sampling_exponents.find(counter.getItem()); - // sampling exponent has to be a power of 2. Find the exponent to have multiplicity near to - // wanted_multiplicity - while (wanted_multiplicity > sampling_state->second.getMultiplicity() && - sampling_state->second.getExponent() < MAX_EXPONENT) { - sampling_state->second.increaseExponent(); - } - if (wanted_multiplicity < sampling_state->second.getMultiplicity()) { - // we want to have multiplicity <= wanted_multiplicity, therefore exponent is decrease once. - sampling_state->second.decreaseExponent(); - } - } + uint64_t calculateEffectiveCount(const TopKListT& top_k, + const SamplingExponentsT& sampling_exponents) const; - auto effective_count = computeEffectiveCount(top_k, new_sampling_exponents); - // There might be entries where allowed_per_entry is greater than their count. - // Therefore, we would sample nubmer of total_wanted requests - // To avoid this, we decrease the exponent of other entries if possible - if (effective_count < total_wanted) { - for (int i = 0; i < 5; i++) { // max tries - for (auto reverse_it = top_k.rbegin(); reverse_it != top_k.rend(); - ++reverse_it) { // start with lowest frequency - auto rev_sampling_state = new_sampling_exponents.find(reverse_it->getItem()); - rev_sampling_state->second.decreaseExponent(); - effective_count = computeEffectiveCount(top_k, new_sampling_exponents); - if (effective_count >= total_wanted) { // we are done - return; - } - } - } - } - } + void calculateSamplingExponents(const TopKListT& top_k, const uint32_t total_wanted, + SamplingExponentsT& new_sampling_exponents) const; }; } // namespace OpenTelemetry diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc index d528326414ca8..5b8f1cde6baf0 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc @@ -221,14 +221,13 @@ TEST(SamplingStateTest, TestShouldSample) { } TEST_F(SamplingControllerTest, TestGetSamplingKey) { - SamplingController sc; - std::string key = sc.getSamplingKey("somepath", "GET"); + std::string key = SamplingController::getSamplingKey("somepath", "GET"); EXPECT_STREQ(key.c_str(), "GET_somepath"); - key = sc.getSamplingKey("somepath?withquery", "POST"); + key = SamplingController::getSamplingKey("somepath?withquery", "POST"); EXPECT_STREQ(key.c_str(), "POST_somepath"); - key = sc.getSamplingKey("anotherpath", "PUT"); + key = SamplingController::getSamplingKey("anotherpath", "PUT"); EXPECT_STREQ(key.c_str(), "PUT_anotherpath"); } From 2298feee752ebaf0f4b606ac847ef1729a4e2e47 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Mon, 29 Jan 2024 12:16:34 +0100 Subject: [PATCH 24/31] move stream_summary from DynaTraceSampler to SamplingController Signed-off-by: thomas.ebner --- .../opentelemetry/samplers/dynatrace/config.h | 2 +- .../samplers/dynatrace/dynatrace_sampler.cc | 23 +-- .../samplers/dynatrace/dynatrace_sampler.h | 6 - .../samplers/dynatrace/dynatrace_tracestate.h | 2 +- .../samplers/dynatrace/sampling_controller.cc | 36 +++-- .../samplers/dynatrace/sampling_controller.h | 25 +++- .../dynatrace/sampling_controller_test.cc | 135 ++++++++---------- 7 files changed, 114 insertions(+), 115 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.h index bc1baf6c34515..672040818b651 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.h @@ -18,7 +18,7 @@ namespace OpenTelemetry { class DynatraceSamplerFactory : public SamplerFactory { public: /** - * @brief Create a Sampler which samples every span + * @brief Create a Dynatrace sampler * * @param context * @return SamplerSharedPtr diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index 27ffa1fb7f2b6..2a0005c342daf 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -9,7 +9,6 @@ <<<<<<< HEAD ======= #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h" -#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h" #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/tracestate.h" >>>>>>> b53b23ded4 (fix include paths, add a few const) #include "source/extensions/tracers/opentelemetry/samplers/sampler.h" @@ -36,9 +35,7 @@ DynatraceSampler::DynatraceSampler( SamplerConfigFetcherPtr sampler_config_fetcher) : tenant_id_(config.tenant_id()), cluster_id_(config.cluster_id()), dt_tracestate_entry_(tenant_id_, cluster_id_), - sampler_config_fetcher_(std::move(sampler_config_fetcher)), - stream_summary_(std::make_unique(STREAM_SUMMARY_SIZE)), - sampling_controller_() { + sampling_controller_(std::move(sampler_config_fetcher)) { timer_ = context.serverFactoryContext().mainThreadDispatcher().createTimer([this]() -> void { updateSamplingInfo(); @@ -62,10 +59,7 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional ? sampling_controller_.getSamplingKey(trace_context->path(), trace_context->method()) : ""; - if (!sampling_key.empty()) { - absl::MutexLock lock{&stream_summary_mutex_}; - stream_summary_->offer(sampling_key); - } + sampling_controller_.offer(sampling_key); auto trace_state = @@ -94,18 +88,7 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional return result; } -void DynatraceSampler::updateSamplingInfo() { - absl::MutexLock lock{&stream_summary_mutex_}; - auto top_k = stream_summary_->getTopK(); - auto last_period_count = stream_summary_->getN(); - - // update sampling exponents - sampling_controller_.update(top_k, last_period_count, - sampler_config_fetcher_->getSamplerConfig().getRootSpansPerMinute()); - // Note: getTopK() returns references to values in StreamSummary. - // Do not destroy it while top_k is used! - stream_summary_ = std::make_unique(STREAM_SUMMARY_SIZE); -} +void DynatraceSampler::updateSamplingInfo() { sampling_controller_.update(); } std::string DynatraceSampler::getDescription() const { return "DynatraceSampler"; } diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index bf7ba7856c26d..62f45a7d5cefd 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -80,16 +80,10 @@ class DynatraceSampler : public Sampler, Logger::Loggable { std::string getDescription() const override; - static constexpr size_t STREAM_SUMMARY_SIZE{100}; - private: std::string tenant_id_; std::string cluster_id_; - std::string dt_tracestate_key_; - SamplerConfigFetcherPtr sampler_config_fetcher_; - std::unique_ptr stream_summary_; - mutable absl::Mutex stream_summary_mutex_{}; Event::TimerPtr timer_; SamplingController sampling_controller_; diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h index 174f4a0552741..32be3e750aebf 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h @@ -17,7 +17,7 @@ class FW4Tag { public: static FW4Tag createInvalid(); - static FW4Tag create(bool ignored, uint32_t sampling_exponent, uint32_t root_path_random_); + static FW4Tag create(bool ignored, uint32_t sampling_exponent, uint32_t root_path_random); static FW4Tag create(const std::string& value); diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.cc index b9e99246fe20a..48015efb65aa6 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.cc @@ -7,6 +7,19 @@ namespace OpenTelemetry { namespace {} +void SamplingController::update() { + absl::MutexLock lock{&stream_summary_mutex_}; + const auto top_k = stream_summary_->getTopK(); + const auto last_period_count = stream_summary_->getN(); + + // update sampling exponents + update(top_k, last_period_count, + sampler_config_fetcher_->getSamplerConfig().getRootSpansPerMinute()); + // Note: getTopK() returns references to values in StreamSummary. + // Do not destroy it while top_k is used! + stream_summary_ = std::make_unique(STREAM_SUMMARY_SIZE); +} + void SamplingController::update(const TopKListT& top_k, uint64_t last_period_count, const uint32_t total_wanted) { @@ -20,19 +33,27 @@ void SamplingController::update(const TopKListT& top_k, uint64_t last_period_cou rest_bucket_key_ = (!top_k.empty()) ? top_k.back().getItem() : ""; calculateSamplingExponents(top_k, total_wanted, new_sampling_exponents); + last_effective_count_ = calculateEffectiveCount(top_k, new_sampling_exponents); logSamplingInfo(top_k, new_sampling_exponents, last_period_count, total_wanted); - absl::WriterMutexLock lock{&mutex_}; + absl::WriterMutexLock lock{&sampling_exponents_mutex_}; sampling_exponents_ = std::move(new_sampling_exponents); } -uint64_t SamplingController::getEffectiveCount(const TopKListT& top_k) const { - absl::ReaderMutexLock lock{&mutex_}; - return calculateEffectiveCount(top_k, sampling_exponents_); +uint64_t SamplingController::getEffectiveCount() const { + absl::MutexLock lock{&stream_summary_mutex_}; + return last_effective_count_; +} + +void SamplingController::offer(const std::string& sampling_key) { + if (!sampling_key.empty()) { + absl::MutexLock lock{&stream_summary_mutex_}; + stream_summary_->offer(sampling_key); + } } SamplingState SamplingController::getSamplingState(const std::string& sampling_key) const { - absl::ReaderMutexLock lock{&mutex_}; + absl::ReaderMutexLock lock{&sampling_exponents_mutex_}; auto iter = sampling_exponents_.find(sampling_key); if (iter == sampling_exponents_ @@ -68,9 +89,8 @@ void SamplingController::logSamplingInfo(const TopKListT& top_k, } } -uint64_t -SamplingController::calculateEffectiveCount(const TopKListT& top_k, - const SamplingExponentsT& sampling_exponents) const { +uint64_t SamplingController::calculateEffectiveCount(const TopKListT& top_k, + const SamplingExponentsT& sampling_exponents) { uint64_t cnt = 0; for (auto const& counter : top_k) { auto sampling_state = sampling_exponents.find(counter.getItem()); diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h index d16e9b7c2d13e..e2ff01defc5f8 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h @@ -3,6 +3,7 @@ #include #include +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.h" #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h" #include "absl/synchronization/mutex.h" @@ -36,30 +37,44 @@ using TopKListT = std::list>; class SamplingController : public Logger::Loggable { public: - void update(const TopKListT& top_k, uint64_t last_period_count, const uint32_t total_wanted); + explicit SamplingController(SamplerConfigFetcherPtr sampler_config_fetcher) + : stream_summary_(std::make_unique(STREAM_SUMMARY_SIZE)), + sampler_config_fetcher_(std::move(sampler_config_fetcher)) {} + + void update(); SamplingState getSamplingState(const std::string& sampling_key) const; - uint64_t getEffectiveCount(const TopKListT& top_k) const; + uint64_t getEffectiveCount() const; + + void offer(const std::string& sampling_key); static std::string getSamplingKey(const absl::string_view path_query, const absl::string_view method); + static constexpr size_t STREAM_SUMMARY_SIZE{100}; + private: using SamplingExponentsT = absl::flat_hash_map; SamplingExponentsT sampling_exponents_; + mutable absl::Mutex sampling_exponents_mutex_{}; std::string rest_bucket_key_{}; - mutable absl::Mutex mutex_{}; static constexpr uint32_t MAX_EXPONENT = (1 << 4) - 1; // 15 + std::unique_ptr stream_summary_; + uint64_t last_effective_count_{}; + mutable absl::Mutex stream_summary_mutex_{}; + SamplerConfigFetcherPtr sampler_config_fetcher_; void logSamplingInfo(const TopKListT& top_k, const SamplingExponentsT& new_sampling_exponents, uint64_t last_period_count, const uint32_t total_wanted) const; - uint64_t calculateEffectiveCount(const TopKListT& top_k, - const SamplingExponentsT& sampling_exponents) const; + static uint64_t calculateEffectiveCount(const TopKListT& top_k, + const SamplingExponentsT& sampling_exponents); void calculateSamplingExponents(const TopKListT& top_k, const uint32_t total_wanted, SamplingExponentsT& new_sampling_exponents) const; + + void update(const TopKListT& top_k, uint64_t last_period_count, const uint32_t total_wanted); }; } // namespace OpenTelemetry diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc index 5b8f1cde6baf0..cc4614982e742 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc @@ -7,7 +7,6 @@ #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h" #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h" -#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -19,42 +18,38 @@ namespace OpenTelemetry { namespace { -void offerEntry(StreamSummaryT& summary, const std::string& value, int count) { +void offerEntry(SamplingController& sc, const std::string& value, int count) { for (int i = 0; i < count; i++) { - summary.offer(value); + sc.offer(value); } } } // namespace -template std::string toString(T const& list) { - std::ostringstream oss; - for (auto const& counter : list) { - oss << counter.getItem() << ":(" << counter.getValue() << "/" << counter.getError() << ")" - << std::endl; - } - return oss.str(); -} +class TestSamplerConfigFetcher : public SamplerConfigFetcher { +public: + const SamplerConfig& getSamplerConfig() const { return config; } + SamplerConfig config; +}; class SamplingControllerTest : public testing::Test {}; TEST_F(SamplingControllerTest, TestManyDifferentRequests) { - StreamSummaryT summary(DynatraceSampler::STREAM_SUMMARY_SIZE); - offerEntry(summary, "1", 2000); - offerEntry(summary, "2", 1000); - offerEntry(summary, "3", 750); - offerEntry(summary, "4", 100); - offerEntry(summary, "5", 50); + auto scf = std::make_unique(); + SamplingController sc(std::move(scf)); + + offerEntry(sc, "1", 2000); + offerEntry(sc, "2", 1000); + offerEntry(sc, "3", 750); + offerEntry(sc, "4", 100); + offerEntry(sc, "5", 50); for (int64_t i = 0; i < 2100; i++) { - summary.offer(std::to_string(i + 1000000)); + sc.offer(std::to_string(i + 1000000)); } - SamplingController sc; - sc.update(summary.getTopK(), summary.getN(), 1000); - - // std::cout << toString(summary.getTopK()); + sc.update(); - EXPECT_EQ(sc.getEffectiveCount(summary.getTopK()), 1110); + EXPECT_EQ(sc.getEffectiveCount(), 1110); EXPECT_EQ(sc.getSamplingState("1").getMultiplicity(), 128); EXPECT_EQ(sc.getSamplingState("2").getMultiplicity(), 64); EXPECT_EQ(sc.getSamplingState("3").getMultiplicity(), 64); @@ -66,22 +61,21 @@ TEST_F(SamplingControllerTest, TestManyDifferentRequests) { } TEST_F(SamplingControllerTest, TestManyRequests) { - StreamSummaryT summary(DynatraceSampler::STREAM_SUMMARY_SIZE); - offerEntry(summary, "1", 8600); - offerEntry(summary, "2", 5000); - offerEntry(summary, "3", 4000); - offerEntry(summary, "4", 4000); - offerEntry(summary, "5", 3000); - offerEntry(summary, "6", 30); - offerEntry(summary, "7", 3); - offerEntry(summary, "8", 1); - - SamplingController sc; - sc.update(summary.getTopK(), summary.getN(), 1000); - - // std::cout << toString(summary.getTopK()); - - EXPECT_EQ(sc.getEffectiveCount(summary.getTopK()), 1074); + auto scf = std::make_unique(); + SamplingController sc(std::move(scf)); + + offerEntry(sc, "1", 8600); + offerEntry(sc, "2", 5000); + offerEntry(sc, "3", 4000); + offerEntry(sc, "4", 4000); + offerEntry(sc, "5", 3000); + offerEntry(sc, "6", 30); + offerEntry(sc, "7", 3); + offerEntry(sc, "8", 1); + + sc.update(); + + EXPECT_EQ(sc.getEffectiveCount(), 1074); EXPECT_EQ(sc.getSamplingState("1").getMultiplicity(), 64); EXPECT_EQ(sc.getSamplingState("2").getMultiplicity(), 32); EXPECT_EQ(sc.getSamplingState("3").getMultiplicity(), 32); @@ -93,20 +87,21 @@ TEST_F(SamplingControllerTest, TestManyRequests) { } TEST_F(SamplingControllerTest, TestSomeRequests) { - StreamSummaryT summary(DynatraceSampler::STREAM_SUMMARY_SIZE); - offerEntry(summary, "1", 7500); - offerEntry(summary, "2", 1000); - offerEntry(summary, "3", 1); - offerEntry(summary, "4", 1); - offerEntry(summary, "5", 1); + auto scf = std::make_unique(); + SamplingController sc(std::move(scf)); + + offerEntry(sc, "1", 7500); + offerEntry(sc, "2", 1000); + offerEntry(sc, "3", 1); + offerEntry(sc, "4", 1); + offerEntry(sc, "5", 1); for (int64_t i = 0; i < 11; i++) { - summary.offer(std::to_string(i + 1000000)); + sc.offer(std::to_string(i + 1000000)); } - SamplingController sc; - sc.update(summary.getTopK(), summary.getN(), 1000); + sc.update(); - EXPECT_EQ(sc.getEffectiveCount(summary.getTopK()), 1451); + EXPECT_EQ(sc.getEffectiveCount(), 1451); EXPECT_EQ(sc.getSamplingState("1").getMultiplicity(), 8); EXPECT_EQ(sc.getSamplingState("2").getMultiplicity(), 2); EXPECT_EQ(sc.getSamplingState("3").getMultiplicity(), 1); @@ -119,14 +114,15 @@ TEST_F(SamplingControllerTest, TestSomeRequests) { } TEST_F(SamplingControllerTest, TestSimple) { - StreamSummaryT summary(10); - // offerEntry data - offerEntry(summary, "GET_xxxx", 300); - offerEntry(summary, "POST_asdf", 200); - offerEntry(summary, "GET_asdf", 100); + auto scf = std::make_unique(); + scf->config.parse("{\n \"rootSpansPerMinute\" : 100 \n }"); + SamplingController sc(std::move(scf)); - SamplingController sc; - sc.update(summary.getTopK(), summary.getN(), 100); + offerEntry(sc, "GET_xxxx", 300); + offerEntry(sc, "POST_asdf", 200); + offerEntry(sc, "GET_asdf", 100); + + sc.update(); EXPECT_EQ(sc.getSamplingState("GET_xxxx").getExponent(), 3); EXPECT_EQ(sc.getSamplingState("GET_xxxx").getMultiplicity(), 8); @@ -136,33 +132,24 @@ TEST_F(SamplingControllerTest, TestSimple) { EXPECT_EQ(sc.getSamplingState("GET_asdf").getExponent(), 1); EXPECT_EQ(sc.getSamplingState("GET_asdf").getMultiplicity(), 2); - - // total_wanted > number of requests - sc.update(summary.getTopK(), summary.getN(), 1000); - EXPECT_EQ(sc.getSamplingState("GET_xxxx").getExponent(), 0); - EXPECT_EQ(sc.getSamplingState("GET_xxxx").getMultiplicity(), 1); - - EXPECT_EQ(sc.getSamplingState("POST_asdf").getExponent(), 0); - EXPECT_EQ(sc.getSamplingState("POST_asdf").getMultiplicity(), 1); - - EXPECT_EQ(sc.getSamplingState("GET_asdf").getExponent(), 0); - EXPECT_EQ(sc.getSamplingState("GET_asdf").getMultiplicity(), 1); } TEST_F(SamplingControllerTest, TestEmpty) { - StreamSummaryT summary(10); - SamplingController sc; - sc.update(summary.getTopK(), summary.getN(), 100); + auto scf = std::make_unique(); + SamplingController sc(std::move(scf)); + + sc.update(); EXPECT_EQ(sc.getSamplingState("GET_something").getExponent(), 0); EXPECT_EQ(sc.getSamplingState("GET_something").getMultiplicity(), 1); } TEST_F(SamplingControllerTest, TestNonExisting) { - StreamSummaryT summary(10); - summary.offer("key1"); - SamplingController sc; - sc.update(summary.getTopK(), summary.getN(), 100); + auto scf = std::make_unique(); + SamplingController sc(std::move(scf)); + + sc.offer("key1"); + sc.update(); EXPECT_EQ(sc.getSamplingState("key2").getExponent(), 0); EXPECT_EQ(sc.getSamplingState("key2").getMultiplicity(), 1); From e9c9841174cefdc94c159859da6692e2375e4cc4 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Wed, 31 Jan 2024 08:25:28 +0100 Subject: [PATCH 25/31] calculate exponent as fallback to handle warmup phase Signed-off-by: thomas.ebner --- .../samplers/always_on/always_on_sampler.cc | 2 +- .../samplers/dynatrace/dynatrace_sampler.cc | 19 +- .../samplers/dynatrace/dynatrace_sampler.h | 33 ++-- .../dynatrace/dynatrace_tracestate.cc | 52 ------ .../samplers/dynatrace/dynatrace_tracestate.h | 58 ------ .../samplers/dynatrace/sampling_controller.cc | 35 ++-- .../samplers/dynatrace/sampling_controller.h | 8 +- .../dynatrace_sampler_integration_test.cc | 1 + .../dynatrace/dynatrace_sampler_test.cc | 170 ++++++++++++++++-- .../dynatrace/dynatrace_tracestate_test.cc | 81 --------- .../dynatrace/sampling_controller_test.cc | 38 ++++ 11 files changed, 254 insertions(+), 243 deletions(-) delete mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.cc delete mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h delete mode 100644 test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate_test.cc diff --git a/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.cc index 8d06116cceb3f..d1965b7812de3 100644 --- a/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.cc @@ -18,7 +18,7 @@ SamplingResult AlwaysOnSampler::shouldSample(const absl::optional p OptRef /*trace_context*/, const std::vector& /*links*/) { SamplingResult result; - result.decision = Decision::RECORD_AND_SAMPLE; + result.decision = Decision::RecordAndSample; if (parent_context.has_value()) { result.tracestate = parent_context.value().tracestate(); } diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index 2a0005c342daf..f32f5d9564585 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -6,11 +6,6 @@ #include "source/common/common/hash.h" #include "source/common/config/datasource.h" -<<<<<<< HEAD -======= -#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h" -#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/tracestate.h" ->>>>>>> b53b23ded4 (fix include paths, add a few const) #include "source/extensions/tracers/opentelemetry/samplers/sampler.h" #include "source/extensions/tracers/opentelemetry/span_context.h" #include "source/extensions/tracers/opentelemetry/trace_state.h" @@ -34,7 +29,8 @@ DynatraceSampler::DynatraceSampler( Server::Configuration::TracerFactoryContext& context, SamplerConfigFetcherPtr sampler_config_fetcher) : tenant_id_(config.tenant_id()), cluster_id_(config.cluster_id()), - dt_tracestate_entry_(tenant_id_, cluster_id_), + dt_tracestate_key_(absl::StrCat(absl::string_view(config.tenant_id()), "-", + absl::string_view(config.cluster_id()), "@dt")), sampling_controller_(std::move(sampler_config_fetcher)) { timer_ = context.serverFactoryContext().mainThreadDispatcher().createTimer([this]() -> void { @@ -61,10 +57,19 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional sampling_controller_.offer(sampling_key); - auto trace_state = TraceState::fromHeader(parent_context.has_value() ? parent_context->tracestate() : ""); + std::string trace_state_value; + + if (trace_state->get(dt_tracestate_key_, trace_state_value)) { + // we found a DT trace decision in tracestate header + if (FW4Tag fw4_tag = FW4Tag::create(trace_state_value); fw4_tag.isValid()) { + result.decision = fw4_tag.isIgnored() ? Decision::Drop : Decision::RecordAndSample; + att[SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME] = + std::to_string(fw4_tag.getSamplingExponent()); + result.tracestate = parent_context->tracestate(); + } } else { // do a decision based on the calculated exponent // at the moment we use a hash of the trace_id as random number diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index 62f45a7d5cefd..f4dfbf54bc3d4 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -21,13 +21,13 @@ namespace OpenTelemetry { class FW4Tag { public: - FW4Tag FW4Tag::createInvalid() { return {false, false, 0, 0}; } + static FW4Tag createInvalid() { return {false, false, 0, 0}; } - FW4Tag FW4Tag::create(bool ignored, int sampling_exponent, int root_path_random_) { - return {true, ignored, sampling_exponent, root_path_random_}; + static FW4Tag create(bool ignored, uint32_t sampling_exponent, uint32_t path_info) { + return {true, ignored, sampling_exponent, path_info}; } - static FW4Tag FW4Tag::create(const std::string& value) { + static FW4Tag create(const std::string& value) { std::vector tracestate_components = absl::StrSplit(value, ';', absl::AllowEmpty()); if (tracestate_components.size() < 8) { @@ -38,28 +38,35 @@ class FW4Tag { return createInvalid(); } bool ignored = tracestate_components[5] == "1"; - int sampling_exponent = std::stoi(std::string(tracestate_components[6])); - int root_path_random = std::stoi(std::string(tracestate_components[7]), nullptr, 16); - return {true, ignored, sampling_exponent, root_path_random}; + uint32_t sampling_exponent; + uint32_t path_info; + if (absl::SimpleAtoi(tracestate_components[6], &sampling_exponent) && + absl::SimpleHexAtoi(tracestate_components[7], &path_info)) { + return {true, ignored, sampling_exponent, path_info}; + } + return createInvalid(); } - std::string FW4Tag::asString() const { - std::string ret = absl::StrCat("fw4;0;0;0;0;", ignored_ ? "1" : "0", ";", sampling_exponent_, ";", - absl::Hex(root_path_random_)); + std::string asString() const { + std::string ret = absl::StrCat("fw4;0;0;0;0;", ignored_ ? "1" : "0", ";", sampling_exponent_, + ";", absl::Hex(path_info_)); return ret; } bool isValid() const { return valid_; }; bool isIgnored() const { return ignored_; }; int getSamplingExponent() const { return sampling_exponent_; }; + uint32_t getPathInfo() const { return path_info_; }; private: - FW4Tag(bool valid, bool ignored, int sampling_exponent) - : valid_(valid), ignored_(ignored), sampling_exponent_(sampling_exponent) {} + FW4Tag(bool valid, bool ignored, uint32_t sampling_exponent, uint32_t path_info) + : valid_(valid), ignored_(ignored), sampling_exponent_(sampling_exponent), + path_info_(path_info) {} bool valid_; bool ignored_; - int sampling_exponent_; + uint32_t sampling_exponent_; + uint32_t path_info_; }; /** diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.cc deleted file mode 100644 index cbeedc8af1ec3..0000000000000 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.cc +++ /dev/null @@ -1,52 +0,0 @@ -#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h" - -#include -#include - -#include "absl/strings/match.h" -#include "absl/strings/numbers.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_split.h" -#include "absl/strings/string_view.h" - -namespace Envoy { -namespace Extensions { -namespace Tracers { -namespace OpenTelemetry { - -FW4Tag FW4Tag::createInvalid() { return {false, false, 0, 0}; } - -FW4Tag FW4Tag::create(bool ignored, uint32_t sampling_exponent, uint32_t path_info) { - return {true, ignored, sampling_exponent, path_info}; -} - -FW4Tag FW4Tag::create(const std::string& value) { - std::vector tracestate_components = - absl::StrSplit(value, ';', absl::AllowEmpty()); - if (tracestate_components.size() < 8) { - return createInvalid(); - } - - if (tracestate_components[0] != "fw4") { - return createInvalid(); - } - bool ignored = tracestate_components[5] == "1"; - uint32_t sampling_exponent; - uint32_t path_info; - if (absl::SimpleAtoi(tracestate_components[6], &sampling_exponent) && - absl::SimpleHexAtoi(tracestate_components[7], &path_info)) { - return {true, ignored, sampling_exponent, path_info}; - } - return createInvalid(); -} - -std::string FW4Tag::asString() const { - std::string ret = absl::StrCat("fw4;0;0;0;0;", ignored_ ? "1" : "0", ";", sampling_exponent_, ";", - absl::Hex(path_info_)); - return ret; -} - -} // namespace OpenTelemetry -} // namespace Tracers -} // namespace Extensions -} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h deleted file mode 100644 index 32be3e750aebf..0000000000000 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include - -#include "absl/strings/str_cat.h" -#include "absl/strings/string_view.h" - -namespace Envoy { -namespace Extensions { -namespace Tracers { -namespace OpenTelemetry { - -// dynatrace trace state entry will be: -// -@dt=fw4;0;0;0;0;;; - -class FW4Tag { -public: - static FW4Tag createInvalid(); - - static FW4Tag create(bool ignored, uint32_t sampling_exponent, uint32_t root_path_random); - - static FW4Tag create(const std::string& value); - - std::string asString() const; - - bool isValid() const { return valid_; }; - bool isIgnored() const { return ignored_; }; - uint32_t getSamplingExponent() const { return sampling_exponent_; }; - uint32_t getPathInfo() const { return path_info_; }; - -private: - FW4Tag(bool valid, bool ignored, uint32_t sampling_exponent, uint32_t path_info) - : valid_(valid), ignored_(ignored), sampling_exponent_(sampling_exponent), - path_info_(path_info) {} - - bool valid_; - bool ignored_; - uint32_t sampling_exponent_; - uint32_t path_info_; -}; - -class DtTracestateEntry { -public: - DtTracestateEntry(const std::string& tenant_id, const std::string& cluster_id) { - key_ = absl::StrCat(absl::string_view(tenant_id), "-", absl::string_view(cluster_id), "@dt"); - } - - std::string getKey() const { return key_; }; - - bool keyMatches(const std::string& key) { return (key_.compare(key) == 0); } - -private: - std::string key_; -}; -} // namespace OpenTelemetry -} // namespace Tracers -} // namespace Extensions -} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.cc index 48015efb65aa6..17043609ce2a6 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.cc @@ -8,7 +8,7 @@ namespace OpenTelemetry { namespace {} void SamplingController::update() { - absl::MutexLock lock{&stream_summary_mutex_}; + absl::WriterMutexLock lock{&stream_summary_mutex_}; const auto top_k = stream_summary_->getTopK(); const auto last_period_count = stream_summary_->getN(); @@ -26,7 +26,7 @@ void SamplingController::update(const TopKListT& top_k, uint64_t last_period_cou SamplingExponentsT new_sampling_exponents; // start with sampling exponent 0, which means multiplicity == 1 (every span is sampled) for (auto const& counter : top_k) { - new_sampling_exponents[counter.getItem()] = {}; + new_sampling_exponents[counter.getItem()] = SamplingState(0); } // use the last entry as "rest bucket", which is used for new/unknown requests @@ -41,30 +41,41 @@ void SamplingController::update(const TopKListT& top_k, uint64_t last_period_cou } uint64_t SamplingController::getEffectiveCount() const { - absl::MutexLock lock{&stream_summary_mutex_}; + absl::ReaderMutexLock lock{&stream_summary_mutex_}; return last_effective_count_; } void SamplingController::offer(const std::string& sampling_key) { if (!sampling_key.empty()) { - absl::MutexLock lock{&stream_summary_mutex_}; + absl::WriterMutexLock lock{&stream_summary_mutex_}; stream_summary_->offer(sampling_key); } } SamplingState SamplingController::getSamplingState(const std::string& sampling_key) const { - absl::ReaderMutexLock lock{&sampling_exponents_mutex_}; - auto iter = sampling_exponents_.find(sampling_key); - if (iter == - sampling_exponents_ - .end()) { // we don't have a sampling exponent for this exponent, use "rest bucket" + { // scope for lock + absl::ReaderMutexLock sax_lock{&sampling_exponents_mutex_}; + auto iter = sampling_exponents_.find(sampling_key); + if (iter != sampling_exponents_.end()) { + return iter->second; + } + + // try to use "rest bucket" auto rest_bucket_iter = sampling_exponents_.find(rest_bucket_key_); if (rest_bucket_iter != sampling_exponents_.end()) { return rest_bucket_iter->second; } - return {}; } - return iter->second; + + // If we can't find a sampling exponent, we calculate it based on the total number of requests + // in this period. This should also handle the "warmup phase" where no top_k is available + const auto divisor = sampler_config_fetcher_->getSamplerConfig().getRootSpansPerMinute() / 2; + if (divisor == 0) { + return SamplingState{MAX_SAMPLING_EXPONENT}; + } + absl::ReaderMutexLock ss_lock{&stream_summary_mutex_}; + const uint32_t exp = stream_summary_->getN() / divisor; + return SamplingState{exp}; } std::string SamplingController::getSamplingKey(const absl::string_view path_query, @@ -126,7 +137,7 @@ void SamplingController::calculateSamplingExponents( // sampling exponent has to be a power of 2. Find the exponent to have multiplicity near to // wanted_multiplicity while (wanted_multiplicity > sampling_state->second.getMultiplicity() && - sampling_state->second.getExponent() < MAX_EXPONENT) { + sampling_state->second.getExponent() < MAX_SAMPLING_EXPONENT) { sampling_state->second.increaseExponent(); } if (wanted_multiplicity < sampling_state->second.getMultiplicity()) { diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h index e2ff01defc5f8..8a2294d23501d 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h @@ -25,10 +25,14 @@ class SamplingState { } } + explicit SamplingState(uint32_t exponent) : exponent_(exponent){}; + + SamplingState() = default; + bool shouldSample(const uint64_t random_nr) const { return (random_nr % getMultiplicity() == 0); } private: - uint32_t exponent_{}; + uint32_t exponent_{0}; }; using StreamSummaryT = StreamSummary; @@ -59,7 +63,7 @@ class SamplingController : public Logger::Loggable { SamplingExponentsT sampling_exponents_; mutable absl::Mutex sampling_exponents_mutex_{}; std::string rest_bucket_key_{}; - static constexpr uint32_t MAX_EXPONENT = (1 << 4) - 1; // 15 + static constexpr uint32_t MAX_SAMPLING_EXPONENT = (1 << 4) - 1; // 15 std::unique_ptr stream_summary_; uint64_t last_effective_count_{}; mutable absl::Mutex stream_summary_mutex_{}; diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc index e909785a1ad92..9d9d20b422831 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc @@ -82,6 +82,7 @@ TEST_P(DynatraceSamplerIntegrationTest, TestWithTraceparentAndTracestate) { .get(Http::LowerCaseString("tracestate"))[0] ->value() .getStringView(); + // use StartsWith because pathinfo (last element in trace state contains a random value) EXPECT_TRUE(absl::StartsWith(tracestate_value, "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;")) << "Received tracestate: " << tracestate_value; EXPECT_TRUE(absl::StrContains(tracestate_value, ",key=value")) diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc index d2fbadb3f7c20..b16b901b18954 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc @@ -47,18 +47,7 @@ class DynatraceSamplerTest : public testing::Test { )EOF"; public: -<<<<<<< HEAD - DynatraceSamplerTest() { - TestUtility::loadFromYaml(yaml_string_, config_); - NiceMock context; - - SamplerConfigFetcherPtr cf = std::make_unique(); - sampler_ = std::make_unique(config_, context, std::move(cf)); - EXPECT_STREQ(sampler_->getDescription().c_str(), "DynatraceSampler"); - } -======= - DynatraceSamplerTest() { TestUtility::loadFromYaml(yaml_string, config_); } ->>>>>>> 2705f51fef (extend test) + DynatraceSamplerTest() { TestUtility::loadFromYaml(yaml_string_, config_); } protected: NiceMock tracer_factory_context_; @@ -71,14 +60,19 @@ TEST_F(DynatraceSamplerTest, TestGetDescription) { EXPECT_STREQ(sampler.getDescription().c_str(), "DynatraceSampler"); } -// Verify sampler being invoked with no parent span context +// Verify sampler being invoked with an invalid/empty span context TEST_F(DynatraceSamplerTest, TestWithoutParentContext) { auto scf = std::make_unique(); + + SamplerConfig config; + // config should allow 200 root spans per minute + config.parse("{\n \"rootSpansPerMinute\" : 200 \n }"); + EXPECT_CALL(*scf, getSamplerConfig()).WillRepeatedly(testing::ReturnRef(config)); DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); auto sampling_result = - sampler_->shouldSample(absl::nullopt, trace_id, "operation_name", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + sampler.shouldSample(absl::nullopt, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); EXPECT_EQ(sampling_result.attributes->size(), 1); EXPECT_STREQ(sampling_result.tracestate.c_str(), "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;95"); @@ -92,9 +86,11 @@ TEST_F(DynatraceSamplerTest, TestWithParentContext) { SpanContext("00", "0af7651916cd43dd8448eb211c80319c", "b7ad6b7169203331", true, "ot=foo:bar,9712ad40-980df25c@dt=fw4;0;0;0;0;0;1;0"); + auto scf = std::make_unique(); + DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); SamplingResult sampling_result = - sampler_->shouldSample(parent_context, "operation_name", "parent_span", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + sampler.shouldSample(parent_context, "operation_name", "parent_span", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); EXPECT_EQ(sampling_result.attributes->size(), 1); @@ -104,6 +100,146 @@ TEST_F(DynatraceSamplerTest, TestWithParentContext) { EXPECT_TRUE(sampling_result.isSampled()); } +// Verify sampler being invoked with parent span context +TEST_F(DynatraceSamplerTest, TestWithUnknownParentContext) { + auto scf = std::make_unique(); + SamplerConfig config; + // config should allow 200 root spans per minute + config.parse("{\n \"rootSpansPerMinute\" : 200 \n }"); + EXPECT_CALL(*scf, getSamplerConfig()).WillRepeatedly(testing::ReturnRef(config)); + + DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); + + SpanContext parent_context("00", trace_id, parent_span_id, true, "some_vendor=some_value"); + + auto sampling_result = + sampler.shouldSample(parent_context, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); + EXPECT_EQ(sampling_result.attributes->size(), 1); + EXPECT_STREQ(sampling_result.tracestate.c_str(), + "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;95,some_vendor=some_value"); + EXPECT_TRUE(sampling_result.isRecording()); + EXPECT_TRUE(sampling_result.isSampled()); +} + +// Verify sampler being invoked with dynatrace trace parent +TEST_F(DynatraceSamplerTest, TestWithDynatraceParentContextSampled) { + auto scf = std::make_unique(); + DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); + + SpanContext parent_context("00", trace_id, parent_span_id, true, dt_tracestate_sampled); + + auto sampling_result = + sampler.shouldSample(parent_context, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); + EXPECT_EQ(sampling_result.attributes->size(), 1); + EXPECT_STREQ(sampling_result.tracestate.c_str(), dt_tracestate_sampled); + EXPECT_TRUE(sampling_result.isRecording()); + EXPECT_TRUE(sampling_result.isSampled()); +} + +// Verify sampler being invoked with dynatrace trace parent where ignored flag is set +TEST_F(DynatraceSamplerTest, TestWithDynatraceParentContextIgnored) { + auto scf = std::make_unique(); + DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); + SpanContext parent_context("00", trace_id, parent_span_id, true, dt_tracestate_ignored); + + auto sampling_result = + sampler.shouldSample(parent_context, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + EXPECT_EQ(sampling_result.decision, Decision::Drop); + EXPECT_EQ(sampling_result.attributes->size(), 1); + EXPECT_STREQ(sampling_result.tracestate.c_str(), dt_tracestate_ignored); + EXPECT_FALSE(sampling_result.isRecording()); + EXPECT_FALSE(sampling_result.isSampled()); +} + +// Verify sampler being invoked with dynatrace trace parent from a different tenant +TEST_F(DynatraceSamplerTest, TestWithDynatraceParentContextFromDifferentTenant) { + auto scf = std::make_unique(); + SamplerConfig config; + // config should allow 200 root spans per minute + config.parse("{\n \"rootSpansPerMinute\" : 200 \n }"); + + EXPECT_CALL(*scf, getSamplerConfig()).WillRepeatedly(testing::ReturnRef(config)); + + DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); + SpanContext parent_context("00", trace_id, parent_span_id, true, + dt_tracestate_ignored_different_tenant); + + auto sampling_result = + sampler.shouldSample(parent_context, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + // sampling decision on tracestate should be ignored because it is from a different tenant. + EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); + EXPECT_EQ(sampling_result.attributes->size(), 1); + const char* exptected = + "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;95,6666ad40-980df25c@dt=fw4;4;4af38366;0;0;1;2;123;" + "8eae;2h01;3h4af38366;4h00;5h01;6h67a9a23155e1741b5b35368e08e6ece5;7h9d83def9a4939b7b"; + EXPECT_STREQ(sampling_result.tracestate.c_str(), exptected); + EXPECT_TRUE(sampling_result.isRecording()); + EXPECT_TRUE(sampling_result.isSampled()); +} + +// Verify sampler being called during warmup phase (no recent top_k available) +TEST_F(DynatraceSamplerTest, TestWarmup) { + auto scf = std::make_unique(); + SamplerConfig config; + // config should allow 200 root spans per minute + config.parse("{\n \"rootSpansPerMinute\" : 200 \n }"); + EXPECT_CALL(*scf, getSamplerConfig()).WillRepeatedly(testing::ReturnRef(config)); + + Tracing::TestTraceContextImpl trace_context_1{}; + trace_context_1.context_method_ = "GET"; + trace_context_1.context_path_ = "/path"; + + DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); + + // timer is not invoked, because we want to test warm up phase. + // we use 200 as threshold. As long as number of requests is < (threshold/2), exponent should be 0 + uint32_t ignored = 0; + uint32_t sampled = 0; + for (int i = 0; i < 99; i++) { + auto result = sampler.shouldSample({}, std::to_string(1000 + i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); + result.isSampled() ? sampled++ : ignored++; + } + EXPECT_EQ(ignored, 0); + EXPECT_EQ(sampled, 99); + + // next (threshold/2) spans will get exponent 1, every second span will be sampled + for (int i = 0; i < 100; i++) { + auto result = sampler.shouldSample({}, std::to_string(1000 + i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); + result.isSampled() ? sampled++ : ignored++; + } + // should be 50, but the used "random" in shouldSample does not produce the same odd/even numbers. + EXPECT_EQ(ignored, 41); + EXPECT_EQ(sampled, 158); + + for (int i = 0; i < 100; i++) { + auto result = sampler.shouldSample({}, std::to_string(1000 + i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); + result.isSampled() ? sampled++ : ignored++; + } + EXPECT_EQ(ignored, 113); + EXPECT_EQ(sampled, 186); + + for (int i = 0; i < 700; i++) { + auto result = sampler.shouldSample({}, std::to_string(1000 + i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); + result.isSampled() ? sampled++ : ignored++; + } + EXPECT_EQ(ignored, 791); + EXPECT_EQ(sampled, 208); +} + TEST_F(DynatraceSamplerTest, TestSampling) { auto scf = std::make_unique(); diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate_test.cc deleted file mode 100644 index 380a734de5595..0000000000000 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate_test.cc +++ /dev/null @@ -1,81 +0,0 @@ -#include - -#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace Envoy { -namespace Extensions { -namespace Tracers { -namespace OpenTelemetry { - -TEST(DynatraceTracestateTest, TestKey) { - DtTracestateEntry tracestate("98812ad49", "980df25c"); - EXPECT_STREQ(tracestate.getKey().c_str(), "98812ad49-980df25c@dt"); -} - -// Test creating an invalid tag -TEST(FW4TagTest, TestInvalid) { - auto tag = FW4Tag::createInvalid(); - EXPECT_FALSE(tag.isValid()); -} - -// Test creating a tag via values -TEST(FW4TagTest, TestCreateFromValues) { - auto tag = FW4Tag::create(false, 2, 235); - EXPECT_TRUE(tag.isValid()); - EXPECT_FALSE(tag.isIgnored()); - EXPECT_EQ(tag.getSamplingExponent(), 2); - EXPECT_EQ(tag.getPathInfo(), 235); - EXPECT_STREQ(tag.asString().c_str(), "fw4;0;0;0;0;0;2;eb"); - - tag = FW4Tag::create(true, 8, 34566); - EXPECT_TRUE(tag.isValid()); - EXPECT_TRUE(tag.isIgnored()); - EXPECT_EQ(tag.getSamplingExponent(), 8); - EXPECT_EQ(tag.getPathInfo(), 34566); - EXPECT_STREQ(tag.asString().c_str(), "fw4;0;0;0;0;1;8;8706"); -} - -// Test creating a tag from a string -TEST(FW4TagTest, TestCreateFromString) { - auto tag = FW4Tag::create("fw4;0;0;0;0;0;4;1a"); - EXPECT_TRUE(tag.isValid()); - EXPECT_FALSE(tag.isIgnored()); - EXPECT_EQ(tag.getSamplingExponent(), 4); - EXPECT_EQ(tag.getPathInfo(), 26); - - tag = FW4Tag::create("fw4;0;0;0;0;1;2;1a2b"); - EXPECT_TRUE(tag.isValid()); - EXPECT_TRUE(tag.isIgnored()); - EXPECT_EQ(tag.getSamplingExponent(), 2); - EXPECT_EQ(tag.getPathInfo(), 6699); - - tag = FW4Tag::create("fw4;6;b3ed5cbd;0;0;0;0;28e;dd24;2h01;3hb3ed5cbd;4h00;5h01;" - "6he929c846864f0b96d59757ad34881a2b;7h032e812f5a9623aa"); - EXPECT_TRUE(tag.isValid()); - EXPECT_FALSE(tag.isIgnored()); - EXPECT_EQ(tag.getSamplingExponent(), 0); - EXPECT_EQ(tag.getPathInfo(), 654); - - tag = FW4Tag::create(""); - EXPECT_FALSE(tag.isValid()); - tag = FW4Tag::create(";"); - EXPECT_FALSE(tag.isValid()); - tag = FW4Tag::create("fw4;;;;;;;;"); - EXPECT_FALSE(tag.isValid()); - tag = FW4Tag::create("fw4;0;0;0;0;1;2"); - EXPECT_FALSE(tag.isValid()); - tag = FW4Tag::create("fw4;0;0;0;0;1;2;asdf"); // invalid path info (not a hex number) - EXPECT_FALSE(tag.isValid()); - tag = FW4Tag::create("fw4;0;0;0;0;1;X;a2"); // invalid exponent (not a hex number) - EXPECT_FALSE(tag.isValid()); - tag = FW4Tag::create("fw3;0;0;0;0;0;0;a2"); - EXPECT_FALSE(tag.isValid()); -} - -} // namespace OpenTelemetry -} // namespace Tracers -} // namespace Extensions -} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc index cc4614982e742..12668ee59a6ba 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc @@ -134,6 +134,44 @@ TEST_F(SamplingControllerTest, TestSimple) { EXPECT_EQ(sc.getSamplingState("GET_asdf").getMultiplicity(), 2); } +TEST_F(SamplingControllerTest, TestWarmup) { + auto scf = std::make_unique(); + SamplingController sc(std::move(scf)); + + // offer entries, but don't call update(); + // sampling exponents table will be empty + // exponent will be calculated based on count. + offerEntry(sc, "GET_0", 10); + EXPECT_EQ(sc.getSamplingState("GET_1").getExponent(), 0); + EXPECT_EQ(sc.getSamplingState("GET_2").getExponent(), 0); + EXPECT_EQ(sc.getSamplingState("GET_3").getExponent(), 0); + + offerEntry(sc, "GET_1", 540); + EXPECT_EQ(sc.getSamplingState("GET_1").getExponent(), 1); + EXPECT_EQ(sc.getSamplingState("GET_2").getExponent(), 1); + EXPECT_EQ(sc.getSamplingState("GET_3").getExponent(), 1); + + offerEntry(sc, "GET_0", 300); + EXPECT_EQ(sc.getSamplingState("GET_0").getExponent(), 1); + EXPECT_EQ(sc.getSamplingState("GET_1").getExponent(), 1); + EXPECT_EQ(sc.getSamplingState("GET_10").getExponent(), 1); + + offerEntry(sc, "GET_4", 550); + EXPECT_EQ(sc.getSamplingState("GET_1").getExponent(), 2); + EXPECT_EQ(sc.getSamplingState("GET_2").getExponent(), 2); + EXPECT_EQ(sc.getSamplingState("GET_3").getExponent(), 2); + + offerEntry(sc, "GET_5", 1000); + EXPECT_EQ(sc.getSamplingState("GET_1").getExponent(), 4); + EXPECT_EQ(sc.getSamplingState("GET_2").getExponent(), 4); + EXPECT_EQ(sc.getSamplingState("GET_3").getExponent(), 4); + + offerEntry(sc, "GET_7", 2000); + EXPECT_EQ(sc.getSamplingState("GET_1").getExponent(), 8); + EXPECT_EQ(sc.getSamplingState("GET_2").getExponent(), 8); + EXPECT_EQ(sc.getSamplingState("GET_3").getExponent(), 8); +} + TEST_F(SamplingControllerTest, TestEmpty) { auto scf = std::make_unique(); SamplingController sc(std::move(scf)); From 996aa444d874ff3da46ea5349b17fdac075c9c0f Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Fri, 2 Feb 2024 08:00:55 +0100 Subject: [PATCH 26/31] cleanup unit test Signed-off-by: thomas.ebner --- .../samplers/dynatrace/dynatrace_sampler.cc | 4 +- .../samplers/dynatrace/dynatrace_sampler.h | 2 - .../dynatrace/dynatrace_sampler_test.cc | 162 +++++++----------- 3 files changed, 64 insertions(+), 104 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index f32f5d9564585..0e39a017b870b 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -34,7 +34,7 @@ DynatraceSampler::DynatraceSampler( sampling_controller_(std::move(sampler_config_fetcher)) { timer_ = context.serverFactoryContext().mainThreadDispatcher().createTimer([this]() -> void { - updateSamplingInfo(); + sampling_controller_.update(); timer_->enableTimer(SAMPLING_UPDATE_TIMER_DURATION); }); timer_->enableTimer(SAMPLING_UPDATE_TIMER_DURATION); @@ -93,8 +93,6 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional return result; } -void DynatraceSampler::updateSamplingInfo() { sampling_controller_.update(); } - std::string DynatraceSampler::getDescription() const { return "DynatraceSampler"; } } // namespace OpenTelemetry diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index f4dfbf54bc3d4..077b84d59460f 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -93,8 +93,6 @@ class DynatraceSampler : public Sampler, Logger::Loggable { std::string dt_tracestate_key_; Event::TimerPtr timer_; SamplingController sampling_controller_; - - void updateSamplingInfo(); }; } // namespace OpenTelemetry diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc index b16b901b18954..1748d353d49f4 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc @@ -47,32 +47,37 @@ class DynatraceSamplerTest : public testing::Test { )EOF"; public: - DynatraceSamplerTest() { TestUtility::loadFromYaml(yaml_string_, config_); } + DynatraceSamplerTest() { + TestUtility::loadFromYaml(yaml_string_, proto_config_); + auto scf = std::make_unique>(); + ON_CALL(*scf, getSamplerConfig()).WillByDefault(testing::ReturnRef(sampler_config_)); + + timer_ = new NiceMock( + &tracer_factory_context_.server_factory_context_.dispatcher_); + ON_CALL(tracer_factory_context_.server_factory_context_.dispatcher_, createTimer_(_)) + .WillByDefault(Invoke([this](Event::TimerCb) { return timer_; })); + sampler_ = + std::make_unique(proto_config_, tracer_factory_context_, std::move(scf)); + } protected: NiceMock tracer_factory_context_; - envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig config_; + envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig proto_config_; + SamplerConfig sampler_config_; + NiceMock* timer_; + std::unique_ptr sampler_; }; +// Verify getDescription TEST_F(DynatraceSamplerTest, TestGetDescription) { - auto scf = std::make_unique(); - DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); - EXPECT_STREQ(sampler.getDescription().c_str(), "DynatraceSampler"); + EXPECT_STREQ(sampler_->getDescription().c_str(), "DynatraceSampler"); } // Verify sampler being invoked with an invalid/empty span context TEST_F(DynatraceSamplerTest, TestWithoutParentContext) { - auto scf = std::make_unique(); - - SamplerConfig config; - // config should allow 200 root spans per minute - config.parse("{\n \"rootSpansPerMinute\" : 200 \n }"); - EXPECT_CALL(*scf, getSamplerConfig()).WillRepeatedly(testing::ReturnRef(config)); - DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); - auto sampling_result = - sampler.shouldSample(absl::nullopt, trace_id, "operation_name", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + sampler_->shouldSample(absl::nullopt, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); EXPECT_EQ(sampling_result.attributes->size(), 1); EXPECT_STREQ(sampling_result.tracestate.c_str(), "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;95"); @@ -82,39 +87,27 @@ TEST_F(DynatraceSamplerTest, TestWithoutParentContext) { // Verify sampler being invoked with existing Dynatrace trace state tag set TEST_F(DynatraceSamplerTest, TestWithParentContext) { - SpanContext parent_context = - SpanContext("00", "0af7651916cd43dd8448eb211c80319c", "b7ad6b7169203331", true, - "ot=foo:bar,9712ad40-980df25c@dt=fw4;0;0;0;0;0;1;0"); + SpanContext parent_context = SpanContext("00", trace_id, "b7ad6b7169203331", true, + "ot=foo:bar,9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;ad"); - auto scf = std::make_unique(); - DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); SamplingResult sampling_result = - sampler.shouldSample(parent_context, "operation_name", "parent_span", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); - + sampler_->shouldSample(parent_context, trace_id, "parent_span", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); EXPECT_EQ(sampling_result.attributes->size(), 1); EXPECT_STREQ(sampling_result.tracestate.c_str(), - "ot=foo:bar,9712ad40-980df25c@dt=fw4;0;0;0;0;0;1;0"); + "ot=foo:bar,9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;ad"); EXPECT_TRUE(sampling_result.isRecording()); EXPECT_TRUE(sampling_result.isSampled()); } // Verify sampler being invoked with parent span context TEST_F(DynatraceSamplerTest, TestWithUnknownParentContext) { - auto scf = std::make_unique(); - SamplerConfig config; - // config should allow 200 root spans per minute - config.parse("{\n \"rootSpansPerMinute\" : 200 \n }"); - EXPECT_CALL(*scf, getSamplerConfig()).WillRepeatedly(testing::ReturnRef(config)); - - DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); - SpanContext parent_context("00", trace_id, parent_span_id, true, "some_vendor=some_value"); auto sampling_result = - sampler.shouldSample(parent_context, trace_id, "operation_name", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + sampler_->shouldSample(parent_context, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); EXPECT_EQ(sampling_result.attributes->size(), 1); EXPECT_STREQ(sampling_result.tracestate.c_str(), @@ -125,14 +118,11 @@ TEST_F(DynatraceSamplerTest, TestWithUnknownParentContext) { // Verify sampler being invoked with dynatrace trace parent TEST_F(DynatraceSamplerTest, TestWithDynatraceParentContextSampled) { - auto scf = std::make_unique(); - DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); - SpanContext parent_context("00", trace_id, parent_span_id, true, dt_tracestate_sampled); auto sampling_result = - sampler.shouldSample(parent_context, trace_id, "operation_name", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + sampler_->shouldSample(parent_context, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); EXPECT_EQ(sampling_result.attributes->size(), 1); EXPECT_STREQ(sampling_result.tracestate.c_str(), dt_tracestate_sampled); @@ -142,13 +132,11 @@ TEST_F(DynatraceSamplerTest, TestWithDynatraceParentContextSampled) { // Verify sampler being invoked with dynatrace trace parent where ignored flag is set TEST_F(DynatraceSamplerTest, TestWithDynatraceParentContextIgnored) { - auto scf = std::make_unique(); - DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); SpanContext parent_context("00", trace_id, parent_span_id, true, dt_tracestate_ignored); auto sampling_result = - sampler.shouldSample(parent_context, trace_id, "operation_name", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + sampler_->shouldSample(parent_context, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); EXPECT_EQ(sampling_result.decision, Decision::Drop); EXPECT_EQ(sampling_result.attributes->size(), 1); EXPECT_STREQ(sampling_result.tracestate.c_str(), dt_tracestate_ignored); @@ -158,20 +146,12 @@ TEST_F(DynatraceSamplerTest, TestWithDynatraceParentContextIgnored) { // Verify sampler being invoked with dynatrace trace parent from a different tenant TEST_F(DynatraceSamplerTest, TestWithDynatraceParentContextFromDifferentTenant) { - auto scf = std::make_unique(); - SamplerConfig config; - // config should allow 200 root spans per minute - config.parse("{\n \"rootSpansPerMinute\" : 200 \n }"); - - EXPECT_CALL(*scf, getSamplerConfig()).WillRepeatedly(testing::ReturnRef(config)); - - DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); SpanContext parent_context("00", trace_id, parent_span_id, true, dt_tracestate_ignored_different_tenant); auto sampling_result = - sampler.shouldSample(parent_context, trace_id, "operation_name", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + sampler_->shouldSample(parent_context, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); // sampling decision on tracestate should be ignored because it is from a different tenant. EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); EXPECT_EQ(sampling_result.attributes->size(), 1); @@ -185,26 +165,21 @@ TEST_F(DynatraceSamplerTest, TestWithDynatraceParentContextFromDifferentTenant) // Verify sampler being called during warmup phase (no recent top_k available) TEST_F(DynatraceSamplerTest, TestWarmup) { - auto scf = std::make_unique(); - SamplerConfig config; // config should allow 200 root spans per minute - config.parse("{\n \"rootSpansPerMinute\" : 200 \n }"); - EXPECT_CALL(*scf, getSamplerConfig()).WillRepeatedly(testing::ReturnRef(config)); + sampler_config_.parse("{\n \"rootSpansPerMinute\" : 200 \n }"); Tracing::TestTraceContextImpl trace_context_1{}; trace_context_1.context_method_ = "GET"; trace_context_1.context_path_ = "/path"; - DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); - // timer is not invoked, because we want to test warm up phase. // we use 200 as threshold. As long as number of requests is < (threshold/2), exponent should be 0 uint32_t ignored = 0; uint32_t sampled = 0; for (int i = 0; i < 99; i++) { - auto result = sampler.shouldSample({}, std::to_string(1000 + i), "operation_name", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, - trace_context_1, {}); + auto result = sampler_->shouldSample({}, std::to_string(1000 + i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); result.isSampled() ? sampled++ : ignored++; } EXPECT_EQ(ignored, 0); @@ -212,9 +187,9 @@ TEST_F(DynatraceSamplerTest, TestWarmup) { // next (threshold/2) spans will get exponent 1, every second span will be sampled for (int i = 0; i < 100; i++) { - auto result = sampler.shouldSample({}, std::to_string(1000 + i), "operation_name", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, - trace_context_1, {}); + auto result = sampler_->shouldSample({}, std::to_string(1000 + i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); result.isSampled() ? sampled++ : ignored++; } // should be 50, but the used "random" in shouldSample does not produce the same odd/even numbers. @@ -222,18 +197,18 @@ TEST_F(DynatraceSamplerTest, TestWarmup) { EXPECT_EQ(sampled, 158); for (int i = 0; i < 100; i++) { - auto result = sampler.shouldSample({}, std::to_string(1000 + i), "operation_name", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, - trace_context_1, {}); + auto result = sampler_->shouldSample({}, std::to_string(1000 + i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); result.isSampled() ? sampled++ : ignored++; } EXPECT_EQ(ignored, 113); EXPECT_EQ(sampled, 186); for (int i = 0; i < 700; i++) { - auto result = sampler.shouldSample({}, std::to_string(1000 + i), "operation_name", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, - trace_context_1, {}); + auto result = sampler_->shouldSample({}, std::to_string(1000 + i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); result.isSampled() ? sampled++ : ignored++; } EXPECT_EQ(ignored, 791); @@ -241,20 +216,9 @@ TEST_F(DynatraceSamplerTest, TestWarmup) { } TEST_F(DynatraceSamplerTest, TestSampling) { - auto scf = std::make_unique(); - - SamplerConfig config; // config should allow 200 root spans per minute - config.parse("{\n \"rootSpansPerMinute\" : 200 \n }"); - - EXPECT_CALL(*scf, getSamplerConfig()).WillRepeatedly(testing::ReturnRef(config)); + sampler_config_.parse("{\n \"rootSpansPerMinute\" : 200 \n }"); - auto timer = - new NiceMock(&tracer_factory_context_.server_factory_context_.dispatcher_); - ON_CALL(tracer_factory_context_.server_factory_context_.dispatcher_, createTimer_(_)) - .WillByDefault(Invoke([timer](Event::TimerCb) { return timer; })); - - DynatraceSampler sampler(config_, tracer_factory_context_, std::move(scf)); Tracing::TestTraceContextImpl trace_context_1{}; trace_context_1.context_method_ = "GET"; trace_context_1.context_path_ = "/path"; @@ -267,29 +231,29 @@ TEST_F(DynatraceSamplerTest, TestSampling) { // send requests for (int i = 0; i < 180; i++) { - sampler.shouldSample({}, trace_id, "operation_name", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, trace_context_1, - {}); - sampler.shouldSample({}, trace_id, "operation_name", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, trace_context_2, - {}); + sampler_->shouldSample({}, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); + sampler_->shouldSample({}, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_2, {}); } - sampler.shouldSample({}, trace_id, "operation_name", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, trace_context_3, - {}); + sampler_->shouldSample({}, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, trace_context_3, + {}); // sampler should read update sampling exponents - timer->invokeCallback(); + timer_->invokeCallback(); // the sampler should not sample every span for 'trace_context_1' // we call it again 10 times. This should be enough to get at least one ignored span // 'i' is used as 'random trace_id' bool ignored = false; for (int i = 0; i < 10; i++) { - auto result = sampler.shouldSample({}, std::to_string(i), "operation_name", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, - trace_context_1, {}); + auto result = sampler_->shouldSample({}, std::to_string(i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); if (!result.isSampled()) { ignored = true; break; @@ -299,9 +263,9 @@ TEST_F(DynatraceSamplerTest, TestSampling) { // trace_context_3 should be sampled for (int i = 0; i < 10; i++) { - auto result = sampler.shouldSample({}, std::to_string(i), "operation_name", - ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, - trace_context_2, {}); + auto result = sampler_->shouldSample({}, std::to_string(i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_2, {}); EXPECT_TRUE(result.isSampled()); } } From ecd833677f5bdec3716ca53257c43b792c0773a1 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Mon, 5 Feb 2024 10:23:08 +0100 Subject: [PATCH 27/31] remove tenant_id_ and cluster_id_ (unintentionally re-added during rebase) Signed-off-by: thomas.ebner --- .../opentelemetry/samplers/dynatrace/dynatrace_sampler.cc | 3 +-- .../opentelemetry/samplers/dynatrace/dynatrace_sampler.h | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index 0e39a017b870b..4776623ed1dc8 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -28,8 +28,7 @@ DynatraceSampler::DynatraceSampler( const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig& config, Server::Configuration::TracerFactoryContext& context, SamplerConfigFetcherPtr sampler_config_fetcher) - : tenant_id_(config.tenant_id()), cluster_id_(config.cluster_id()), - dt_tracestate_key_(absl::StrCat(absl::string_view(config.tenant_id()), "-", + : dt_tracestate_key_(absl::StrCat(absl::string_view(config.tenant_id()), "-", absl::string_view(config.cluster_id()), "@dt")), sampling_controller_(std::move(sampler_config_fetcher)) { diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index 077b84d59460f..c591bf1bf7e44 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -88,8 +88,6 @@ class DynatraceSampler : public Sampler, Logger::Loggable { std::string getDescription() const override; private: - std::string tenant_id_; - std::string cluster_id_; std::string dt_tracestate_key_; Event::TimerPtr timer_; SamplingController sampling_controller_; From 9f4fd46b456f0fb9d490ff29a3f9dbecbe5e55e7 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Mon, 5 Feb 2024 11:16:07 +0100 Subject: [PATCH 28/31] add calculateTenantId() to allow to set tenant instead of tenant_id in config file Signed-off-by: thomas.ebner --- .../samplers/dynatrace/dynatrace_sampler.cc | 27 ++++++++++++++++++- .../dynatrace_sampler_integration_test.cc | 8 +++--- .../dynatrace/dynatrace_sampler_test.cc | 16 +++++------ 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index 4776623ed1dc8..d74256b0ad388 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -11,6 +11,7 @@ #include "source/extensions/tracers/opentelemetry/trace_state.h" #include "absl/strings/str_cat.h" +#include "openssl/md5.h" namespace Envoy { namespace Extensions { @@ -22,13 +23,37 @@ namespace { constexpr std::chrono::minutes SAMPLING_UPDATE_TIMER_DURATION{1}; const char* SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME = "sampling_extrapolation_set_in_sampler"; +absl::Hex calculateTenantId(std::string tenant_uuid) { + if (tenant_uuid.empty()) { + return absl::Hex(0); + } + + for (char& c : tenant_uuid) { + if (c & 0x80) { + c = 0x3f; // '?' + } + } + + uint8_t digest[16]; + MD5(reinterpret_cast(tenant_uuid.data()), tenant_uuid.size(), digest); + + int32_t hash = 0; + for (int i = 0; i < 16; i++) { + const int shift_for_target_byte = (3 - (i % 4)) * 8; + // 24, 16, 8, 0 respectively + hash ^= + (static_cast(digest[i]) << shift_for_target_byte) & (0xff << shift_for_target_byte); + } + return absl::Hex(hash); +} + } // namespace DynatraceSampler::DynatraceSampler( const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig& config, Server::Configuration::TracerFactoryContext& context, SamplerConfigFetcherPtr sampler_config_fetcher) - : dt_tracestate_key_(absl::StrCat(absl::string_view(config.tenant_id()), "-", + : dt_tracestate_key_(absl::StrCat(calculateTenantId(config.tenant_id()), "-", absl::string_view(config.cluster_id()), "@dt")), sampling_controller_(std::move(sampler_config_fetcher)) { diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc index 9d9d20b422831..7664e50e1dbc7 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc @@ -37,7 +37,7 @@ class DynatraceSamplerIntegrationTest : public Envoy::HttpIntegrationTest, name: envoy.tracers.opentelemetry.samplers.dynatrace typed_config: "@type": type.googleapis.com/envoy.extensions.tracers.opentelemetry.samplers.v3.DynatraceSamplerConfig - tenant_id: "9712ad40" + tenant_id: "abc12345" cluster_id: "980df25c" )EOF"; @@ -83,7 +83,7 @@ TEST_P(DynatraceSamplerIntegrationTest, TestWithTraceparentAndTracestate) { ->value() .getStringView(); // use StartsWith because pathinfo (last element in trace state contains a random value) - EXPECT_TRUE(absl::StartsWith(tracestate_value, "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;")) + EXPECT_TRUE(absl::StartsWith(tracestate_value, "5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;")) << "Received tracestate: " << tracestate_value; EXPECT_TRUE(absl::StrContains(tracestate_value, ",key=value")) << "Received tracestate: " << tracestate_value; @@ -115,7 +115,7 @@ TEST_P(DynatraceSamplerIntegrationTest, TestWithTraceparentOnly) { ->value() .getStringView(); // use StartsWith because pathinfo (last element in trace state contains a random value) - EXPECT_TRUE(absl::StartsWith(tracestate_value, "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;")) + EXPECT_TRUE(absl::StartsWith(tracestate_value, "5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;")) << "Received tracestate: " << tracestate_value; } @@ -139,7 +139,7 @@ TEST_P(DynatraceSamplerIntegrationTest, TestWithoutTraceparentAndTracestate) { .get(Http::LowerCaseString("tracestate"))[0] ->value() .getStringView(); - EXPECT_TRUE(absl::StartsWith(tracestate_value, "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;")) + EXPECT_TRUE(absl::StartsWith(tracestate_value, "5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;")) << "Received tracestate: " << tracestate_value; } diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc index 1748d353d49f4..29982fc594b78 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc @@ -23,10 +23,10 @@ const char* trace_id = "67a9a23155e1741b5b35368e08e6ece5"; const char* parent_span_id = "9d83def9a4939b7b"; const char* dt_tracestate_ignored = - "9712ad40-980df25c@dt=fw4;4;4af38366;0;0;1;2;123;8eae;2h01;3h4af38366;4h00;5h01;" + "5b3f9fed-980df25c@dt=fw4;4;4af38366;0;0;1;2;123;8eae;2h01;3h4af38366;4h00;5h01;" "6h67a9a23155e1741b5b35368e08e6ece5;7h9d83def9a4939b7b"; const char* dt_tracestate_sampled = - "9712ad40-980df25c@dt=fw4;4;4af38366;0;0;0;0;123;8eae;2h01;3h4af38366;4h00;5h01;" + "5b3f9fed-980df25c@dt=fw4;4;4af38366;0;0;0;0;123;8eae;2h01;3h4af38366;4h00;5h01;" "6h67a9a23155e1741b5b35368e08e6ece5;7h9d83def9a4939b7b"; const char* dt_tracestate_ignored_different_tenant = "6666ad40-980df25c@dt=fw4;4;4af38366;0;0;1;2;123;8eae;2h01;3h4af38366;4h00;5h01;" @@ -42,7 +42,7 @@ class MockSamplerConfigFetcher : public SamplerConfigFetcher { class DynatraceSamplerTest : public testing::Test { const std::string yaml_string_ = R"EOF( - tenant_id: "9712ad40" + tenant_id: "abc12345" cluster_id: "980df25c" )EOF"; @@ -80,7 +80,7 @@ TEST_F(DynatraceSamplerTest, TestWithoutParentContext) { ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); EXPECT_EQ(sampling_result.attributes->size(), 1); - EXPECT_STREQ(sampling_result.tracestate.c_str(), "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;95"); + EXPECT_STREQ(sampling_result.tracestate.c_str(), "5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;95"); EXPECT_TRUE(sampling_result.isRecording()); EXPECT_TRUE(sampling_result.isSampled()); } @@ -88,7 +88,7 @@ TEST_F(DynatraceSamplerTest, TestWithoutParentContext) { // Verify sampler being invoked with existing Dynatrace trace state tag set TEST_F(DynatraceSamplerTest, TestWithParentContext) { SpanContext parent_context = SpanContext("00", trace_id, "b7ad6b7169203331", true, - "ot=foo:bar,9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;ad"); + "ot=foo:bar,5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;ad"); SamplingResult sampling_result = sampler_->shouldSample(parent_context, trace_id, "parent_span", @@ -96,7 +96,7 @@ TEST_F(DynatraceSamplerTest, TestWithParentContext) { EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); EXPECT_EQ(sampling_result.attributes->size(), 1); EXPECT_STREQ(sampling_result.tracestate.c_str(), - "ot=foo:bar,9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;ad"); + "ot=foo:bar,5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;ad"); EXPECT_TRUE(sampling_result.isRecording()); EXPECT_TRUE(sampling_result.isSampled()); } @@ -111,7 +111,7 @@ TEST_F(DynatraceSamplerTest, TestWithUnknownParentContext) { EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); EXPECT_EQ(sampling_result.attributes->size(), 1); EXPECT_STREQ(sampling_result.tracestate.c_str(), - "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;95,some_vendor=some_value"); + "5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;95,some_vendor=some_value"); EXPECT_TRUE(sampling_result.isRecording()); EXPECT_TRUE(sampling_result.isSampled()); } @@ -156,7 +156,7 @@ TEST_F(DynatraceSamplerTest, TestWithDynatraceParentContextFromDifferentTenant) EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); EXPECT_EQ(sampling_result.attributes->size(), 1); const char* exptected = - "9712ad40-980df25c@dt=fw4;0;0;0;0;0;0;95,6666ad40-980df25c@dt=fw4;4;4af38366;0;0;1;2;123;" + "5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;95,6666ad40-980df25c@dt=fw4;4;4af38366;0;0;1;2;123;" "8eae;2h01;3h4af38366;4h00;5h01;6h67a9a23155e1741b5b35368e08e6ece5;7h9d83def9a4939b7b"; EXPECT_STREQ(sampling_result.tracestate.c_str(), exptected); EXPECT_TRUE(sampling_result.isRecording()); From ca06322a85e2c9d5c153ff203bd62fc304427693 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Tue, 6 Feb 2024 07:05:34 +0100 Subject: [PATCH 29/31] rename tenant_id -> tenant Signed-off-by: thomas.ebner --- .../tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto | 2 +- .../opentelemetry/samplers/dynatrace/dynatrace_sampler.cc | 2 +- .../samplers/dynatrace/dynatrace_sampler_integration_test.cc | 2 +- .../opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto b/api/envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto index cf93ab04ed8fd..c86f82d3e4c6f 100644 --- a/api/envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto +++ b/api/envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto @@ -16,7 +16,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#extension: envoy.tracers.opentelemetry.samplers.dynatrace] message DynatraceSamplerConfig { - string tenant_id = 1; + string tenant = 1; string cluster_id = 2; diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index d74256b0ad388..3191f89c01802 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -53,7 +53,7 @@ DynatraceSampler::DynatraceSampler( const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig& config, Server::Configuration::TracerFactoryContext& context, SamplerConfigFetcherPtr sampler_config_fetcher) - : dt_tracestate_key_(absl::StrCat(calculateTenantId(config.tenant_id()), "-", + : dt_tracestate_key_(absl::StrCat(calculateTenantId(config.tenant()), "-", absl::string_view(config.cluster_id()), "@dt")), sampling_controller_(std::move(sampler_config_fetcher)) { diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc index 7664e50e1dbc7..163d95e3310bf 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc @@ -37,7 +37,7 @@ class DynatraceSamplerIntegrationTest : public Envoy::HttpIntegrationTest, name: envoy.tracers.opentelemetry.samplers.dynatrace typed_config: "@type": type.googleapis.com/envoy.extensions.tracers.opentelemetry.samplers.v3.DynatraceSamplerConfig - tenant_id: "abc12345" + tenant: "abc12345" cluster_id: "980df25c" )EOF"; diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc index 29982fc594b78..8d98000ab2072 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc @@ -42,7 +42,7 @@ class MockSamplerConfigFetcher : public SamplerConfigFetcher { class DynatraceSamplerTest : public testing::Test { const std::string yaml_string_ = R"EOF( - tenant_id: "abc12345" + tenant: "abc12345" cluster_id: "980df25c" )EOF"; From 1c33d1f98b37ea933cf8fb427914501dfcf8b456 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Tue, 6 Feb 2024 09:57:56 +0100 Subject: [PATCH 30/31] extract tenant id calculation to allow unit testing Signed-off-by: thomas.ebner --- .../opentelemetry/samplers/dynatrace/BUILD | 1 + .../samplers/dynatrace/dynatrace_sampler.cc | 26 +---------- .../samplers/dynatrace/tenant_id.h | 43 +++++++++++++++++++ .../opentelemetry/samplers/dynatrace/BUILD | 1 + .../samplers/dynatrace/tenant_id_test.cc | 30 +++++++++++++ 5 files changed, 76 insertions(+), 25 deletions(-) create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id.h create mode 100644 test/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id_test.cc diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD b/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD index 441f1dabdd239..6245dc6146199 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD @@ -35,6 +35,7 @@ envoy_cc_library( "sampler_config_fetcher.h", "sampling_controller.h", "stream_summary.h", + "tenant_id.h", ], deps = [ "//source/common/config:datasource_lib", diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index 3191f89c01802..f29ce0a01fa14 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -6,12 +6,12 @@ #include "source/common/common/hash.h" #include "source/common/config/datasource.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id.h" #include "source/extensions/tracers/opentelemetry/samplers/sampler.h" #include "source/extensions/tracers/opentelemetry/span_context.h" #include "source/extensions/tracers/opentelemetry/trace_state.h" #include "absl/strings/str_cat.h" -#include "openssl/md5.h" namespace Envoy { namespace Extensions { @@ -23,30 +23,6 @@ namespace { constexpr std::chrono::minutes SAMPLING_UPDATE_TIMER_DURATION{1}; const char* SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME = "sampling_extrapolation_set_in_sampler"; -absl::Hex calculateTenantId(std::string tenant_uuid) { - if (tenant_uuid.empty()) { - return absl::Hex(0); - } - - for (char& c : tenant_uuid) { - if (c & 0x80) { - c = 0x3f; // '?' - } - } - - uint8_t digest[16]; - MD5(reinterpret_cast(tenant_uuid.data()), tenant_uuid.size(), digest); - - int32_t hash = 0; - for (int i = 0; i < 16; i++) { - const int shift_for_target_byte = (3 - (i % 4)) * 8; - // 24, 16, 8, 0 respectively - hash ^= - (static_cast(digest[i]) << shift_for_target_byte) & (0xff << shift_for_target_byte); - } - return absl::Hex(hash); -} - } // namespace DynatraceSampler::DynatraceSampler( diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id.h new file mode 100644 index 0000000000000..28da1b3bbd5ea --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#include "absl/strings/str_cat.h" +#include "openssl/md5.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +namespace { + +absl::Hex calculateTenantId(std::string tenant_uuid) { + if (tenant_uuid.empty()) { + return absl::Hex(0); + } + + for (char& c : tenant_uuid) { + if (c & 0x80) { + c = 0x3f; // '?' + } + } + + uint8_t digest[16]; + MD5(reinterpret_cast(tenant_uuid.data()), tenant_uuid.size(), digest); + + int32_t hash = 0; + for (int i = 0; i < 16; i++) { + const int shift_for_target_byte = (3 - (i % 4)) * 8; + // 24, 16, 8, 0 respectively + hash ^= + (static_cast(digest[i]) << shift_for_target_byte) & (0xff << shift_for_target_byte); + } + return absl::Hex(hash); +} +} // namespace +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD b/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD index 831bba2a3944e..fbbdba66448f2 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD @@ -32,6 +32,7 @@ envoy_extension_cc_test( "sampler_config_test.cc", "sampling_controller_test.cc", "stream_summary_test.cc", + "tenant_id_test.cc", ], extension_names = ["envoy.tracers.opentelemetry.samplers.dynatrace"], deps = [ diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id_test.cc new file mode 100644 index 0000000000000..244e949b06ef5 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id_test.cc @@ -0,0 +1,30 @@ +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id.h" + +#include "absl/strings/str_cat.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +// Test calculation using an empty string +TEST(TenantIdTest, testEmpty) { EXPECT_EQ(absl::StrCat(calculateTenantId("")), "0"); } + +// Test calculation using strings with whitespace +TEST(TenantIdTest, testWhitespace) { + EXPECT_EQ(absl::StrCat(calculateTenantId("abc 1234")), "182ccac"); + EXPECT_EQ(absl::StrCat(calculateTenantId(" ")), "b173ef2e"); +} + +// Test calculation using some expected strings +TEST(TenantIdTest, testValues) { + EXPECT_EQ(absl::StrCat(calculateTenantId("jmw13303")), "4d10bede"); + EXPECT_EQ(absl::StrCat(calculateTenantId("abc12345")), "5b3f9fed"); + EXPECT_EQ(absl::StrCat(calculateTenantId("?pfel")), "7712d29d"); +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy From 2b883a07e2c28f96ebd41ae98d856eea9719acb4 Mon Sep 17 00:00:00 2001 From: "thomas.ebner" Date: Tue, 6 Feb 2024 13:04:48 +0100 Subject: [PATCH 31/31] implement review feedback, move class DynatraceTag from header to cc file Signed-off-by: thomas.ebner --- .../samplers/dynatrace/dynatrace_sampler.cc | 64 +++++++++++++++++-- .../samplers/dynatrace/dynatrace_sampler.h | 50 --------------- .../samplers/dynatrace/sampler_config.cc | 3 +- .../samplers/dynatrace/sampler_config.h | 2 +- .../dynatrace/sampler_config_fetcher.cc | 6 +- 5 files changed, 64 insertions(+), 61 deletions(-) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc index f29ce0a01fa14..ebf54c6dbd64e 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -23,6 +23,56 @@ namespace { constexpr std::chrono::minutes SAMPLING_UPDATE_TIMER_DURATION{1}; const char* SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME = "sampling_extrapolation_set_in_sampler"; +class DynatraceTag { +public: + static DynatraceTag createInvalid() { return {false, false, 0, 0}; } + + static DynatraceTag create(bool ignored, uint32_t sampling_exponent, uint32_t path_info) { + return {true, ignored, sampling_exponent, path_info}; + } + + static DynatraceTag create(const std::string& value) { + std::vector tracestate_components = + absl::StrSplit(value, ';', absl::AllowEmpty()); + if (tracestate_components.size() < 8) { + return createInvalid(); + } + + if (tracestate_components[0] != "fw4") { + return createInvalid(); + } + bool ignored = tracestate_components[5] == "1"; + uint32_t sampling_exponent; + uint32_t path_info; + if (absl::SimpleAtoi(tracestate_components[6], &sampling_exponent) && + absl::SimpleHexAtoi(tracestate_components[7], &path_info)) { + return {true, ignored, sampling_exponent, path_info}; + } + return createInvalid(); + } + + std::string asString() const { + std::string ret = absl::StrCat("fw4;0;0;0;0;", ignored_ ? "1" : "0", ";", sampling_exponent_, + ";", absl::Hex(path_info_)); + return ret; + } + + bool isValid() const { return valid_; }; + bool isIgnored() const { return ignored_; }; + int getSamplingExponent() const { return sampling_exponent_; }; + uint32_t getPathInfo() const { return path_info_; }; + +private: + DynatraceTag(bool valid, bool ignored, uint32_t sampling_exponent, uint32_t path_info) + : valid_(valid), ignored_(ignored), sampling_exponent_(sampling_exponent), + path_info_(path_info) {} + + bool valid_; + bool ignored_; + uint32_t sampling_exponent_; + uint32_t path_info_; +}; + } // namespace DynatraceSampler::DynatraceSampler( @@ -55,6 +105,7 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional ? sampling_controller_.getSamplingKey(trace_context->path(), trace_context->method()) : ""; + // add it to stream summary containing the number of requests sampling_controller_.offer(sampling_key); auto trace_state = @@ -64,10 +115,12 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional if (trace_state->get(dt_tracestate_key_, trace_state_value)) { // we found a DT trace decision in tracestate header - if (FW4Tag fw4_tag = FW4Tag::create(trace_state_value); fw4_tag.isValid()) { - result.decision = fw4_tag.isIgnored() ? Decision::Drop : Decision::RecordAndSample; + if (DynatraceTag dynatrace_tag = DynatraceTag::create(trace_state_value); + dynatrace_tag.isValid()) { + result.decision = dynatrace_tag.isIgnored() ? Decision::Drop : Decision::RecordAndSample; + // TODO: change attribute name and value in scope of OA-26680 att[SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME] = - std::to_string(fw4_tag.getSamplingExponent()); + std::to_string(dynatrace_tag.getSamplingExponent()); result.tracestate = parent_context->tracestate(); } } else { @@ -77,12 +130,13 @@ SamplingResult DynatraceSampler::shouldSample(const absl::optional const auto sampling_state = sampling_controller_.getSamplingState(sampling_key); const bool sample = sampling_state.shouldSample(hash); const auto sampling_exponent = sampling_state.getExponent(); - + // TODO: change attribute name and value in scope of OA-26680 att[SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME] = std::to_string(sampling_exponent); result.decision = sample ? Decision::RecordAndSample : Decision::Drop; // create new forward tag and add it to tracestate - FW4Tag new_tag = FW4Tag::create(!sample, sampling_exponent, static_cast(hash)); + DynatraceTag new_tag = + DynatraceTag::create(!sample, sampling_exponent, static_cast(hash)); trace_state = trace_state->set(dt_tracestate_key_, new_tag.asString()); result.tracestate = trace_state->toHeader(); } diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h index c591bf1bf7e44..4044fe65164d8 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -19,56 +19,6 @@ namespace Extensions { namespace Tracers { namespace OpenTelemetry { -class FW4Tag { -public: - static FW4Tag createInvalid() { return {false, false, 0, 0}; } - - static FW4Tag create(bool ignored, uint32_t sampling_exponent, uint32_t path_info) { - return {true, ignored, sampling_exponent, path_info}; - } - - static FW4Tag create(const std::string& value) { - std::vector tracestate_components = - absl::StrSplit(value, ';', absl::AllowEmpty()); - if (tracestate_components.size() < 8) { - return createInvalid(); - } - - if (tracestate_components[0] != "fw4") { - return createInvalid(); - } - bool ignored = tracestate_components[5] == "1"; - uint32_t sampling_exponent; - uint32_t path_info; - if (absl::SimpleAtoi(tracestate_components[6], &sampling_exponent) && - absl::SimpleHexAtoi(tracestate_components[7], &path_info)) { - return {true, ignored, sampling_exponent, path_info}; - } - return createInvalid(); - } - - std::string asString() const { - std::string ret = absl::StrCat("fw4;0;0;0;0;", ignored_ ? "1" : "0", ";", sampling_exponent_, - ";", absl::Hex(path_info_)); - return ret; - } - - bool isValid() const { return valid_; }; - bool isIgnored() const { return ignored_; }; - int getSamplingExponent() const { return sampling_exponent_; }; - uint32_t getPathInfo() const { return path_info_; }; - -private: - FW4Tag(bool valid, bool ignored, uint32_t sampling_exponent, uint32_t path_info) - : valid_(valid), ignored_(ignored), sampling_exponent_(sampling_exponent), - path_info_(path_info) {} - - bool valid_; - bool ignored_; - uint32_t sampling_exponent_; - uint32_t path_info_; -}; - /** * @brief A Dynatrace specific sampler */ diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.cc index ab6ca4b71f450..ccfb956bb6ced 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.cc @@ -11,13 +11,12 @@ namespace OpenTelemetry { void SamplerConfig::parse(const std::string& json) { const auto result = Envoy::Json::Factory::loadFromStringNoThrow(json); if (result.ok()) { - const auto obj = result.value(); + const auto& obj = result.value(); if (obj->hasObject("rootSpansPerMinute")) { const auto value = obj->getInteger("rootSpansPerMinute", ROOT_SPANS_PER_MINUTE_DEFAULT); root_spans_per_minute_.store(value); return; } - (void)obj; } // didn't get a value, reset to default root_spans_per_minute_.store(ROOT_SPANS_PER_MINUTE_DEFAULT); diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h index 888f0a2cd3bbb..af75cbc555a5f 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h @@ -18,7 +18,7 @@ class SamplerConfig { uint32_t getRootSpansPerMinute() const { return root_spans_per_minute_.load(); } private: - std::atomic root_spans_per_minute_ = ROOT_SPANS_PER_MINUTE_DEFAULT; + std::atomic root_spans_per_minute_{ROOT_SPANS_PER_MINUTE_DEFAULT}; }; } // namespace OpenTelemetry diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc index 818692bbdc0aa..67197f5ecc1ed 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_fetcher.cc @@ -51,11 +51,11 @@ void SamplerConfigFetcherImpl::onSuccess(const Http::AsyncClient::Request& /*req onRequestDone(); const auto response_code = Http::Utility::getResponseStatus(http_response->headers()); if (response_code == enumToInt(Http::Code::OK)) { - ENVOY_LOG(debug, "SamplerConfigFetcherImpl received success status code: {}", response_code); + ENVOY_LOG(debug, "Received sampling configuration from Dynatrace: {}", + http_response->bodyAsString()); sampler_config_.parse(http_response->bodyAsString()); } else { - ENVOY_LOG(warn, "SamplerConfigFetcherImpl received a non-success status code: {}", - response_code); + ENVOY_LOG(warn, "Failed to get sampling configuration from Dynatrace: {}", response_code); } }