diff --git a/src/datadog/config_manager.cpp b/src/datadog/config_manager.cpp index d2badc83..d8c78128 100644 --- a/src/datadog/config_manager.cpp +++ b/src/datadog/config_manager.cpp @@ -6,18 +6,81 @@ namespace datadog { namespace tracing { +namespace { + +using Rules = + std::unordered_map; + +Expected parse_trace_sampling_rules(const nlohmann::json& json_rules) { + Rules parsed_rules; + + std::string type = json_rules.type_name(); + if (type != "array") { + std::string message; + return Error{Error::TRACE_SAMPLING_RULES_WRONG_TYPE, std::move(message)}; + } + + for (const auto& json_rule : json_rules) { + auto matcher = SpanMatcher::from_json(json_rule); + if (auto* error = matcher.if_error()) { + std::string prefix; + return error->with_prefix(prefix); + } + + TraceSamplerRate rate; + if (auto sample_rate = json_rule.find("sample_rate"); + sample_rate != json_rule.end()) { + type = sample_rate->type_name(); + if (type != "number") { + std::string message; + return Error{Error::TRACE_SAMPLING_RULES_SAMPLE_RATE_WRONG_TYPE, + std::move(message)}; + } + + auto maybe_rate = Rate::from(*sample_rate); + if (auto error = maybe_rate.if_error()) { + return *error; + } + + rate.value = *maybe_rate; + } + + if (auto provenance_it = json_rule.find("provenance"); + provenance_it != json_rule.cend()) { + if (!provenance_it->is_string()) { + std::string message; + return Error{Error::TRACE_SAMPLING_RULES_SAMPLE_RATE_WRONG_TYPE, + std::move(message)}; + } + + auto provenance = provenance_it->get(); + if (provenance == "customer") { + rate.mechanism = SamplingMechanism::REMOTE_RULE; + } else if (provenance == "dynamic") { + rate.mechanism = SamplingMechanism::REMOTE_ADAPTIVE_RULE; + } + } + + parsed_rules.emplace(std::move(*matcher), std::move(rate)); + } + + return parsed_rules; +} + +} // namespace ConfigManager::ConfigManager(const FinalizedTracerConfig& config) : clock_(config.clock), default_metadata_(config.metadata), trace_sampler_( std::make_shared(config.trace_sampler, clock_)), + rules_(config.trace_sampler.rules), span_defaults_(std::make_shared(config.defaults)), report_traces_(config.report_traces) {} std::shared_ptr ConfigManager::trace_sampler() { std::lock_guard lock(mutex_); - return trace_sampler_.value(); + return trace_sampler_; } std::shared_ptr ConfigManager::span_defaults() { @@ -35,32 +98,48 @@ std::vector ConfigManager::update(const ConfigUpdate& conf) { std::lock_guard lock(mutex_); + decltype(rules_) rules; + if (!conf.trace_sampling_rate) { - reset_config(ConfigName::TRACE_SAMPLING_RATE, trace_sampler_, metadata); + auto found = default_metadata_.find(ConfigName::TRACE_SAMPLING_RATE); + if (found != default_metadata_.cend()) { + metadata.push_back(found->second); + } } else { ConfigMetadata trace_sampling_metadata( ConfigName::TRACE_SAMPLING_RATE, to_string(*conf.trace_sampling_rate, 1), ConfigMetadata::Origin::REMOTE_CONFIG); - TraceSamplerConfig trace_sampler_cfg; - trace_sampler_cfg.sample_rate = *conf.trace_sampling_rate; + auto rate = Rate::from(*conf.trace_sampling_rate); + rules[catch_all] = TraceSamplerRate{*rate, SamplingMechanism::RULE}; + + metadata.emplace_back(std::move(trace_sampling_metadata)); + } - auto finalized_trace_sampler_cfg = finalize_config(trace_sampler_cfg); - if (auto error = finalized_trace_sampler_cfg.if_error()) { - trace_sampling_metadata.error = *error; + if (!conf.trace_sampling_rules) { + auto found = default_metadata_.find(ConfigName::TRACE_SAMPLING_RULES); + if (found != default_metadata_.cend()) { + metadata.emplace_back(found->second); } + } else { + ConfigMetadata trace_sampling_rules_metadata( + ConfigName::TRACE_SAMPLING_RULES, conf.trace_sampling_rules->dump(), + ConfigMetadata::Origin::REMOTE_CONFIG); - auto trace_sampler = - std::make_shared(*finalized_trace_sampler_cfg, clock_); + auto maybe_rules = parse_trace_sampling_rules(*conf.trace_sampling_rules); + if (auto error = maybe_rules.if_error()) { + trace_sampling_rules_metadata.error = std::move(*error); + } else { + rules.merge(*maybe_rules); + } - // This reset rate limiting and `TraceSampler` has no `operator==`. - // TODO: Instead of creating another `TraceSampler`, we should - // update the default sampling rate. - trace_sampler_ = std::move(trace_sampler); - metadata.emplace_back(std::move(trace_sampling_metadata)); + metadata.emplace_back(std::move(trace_sampling_rules_metadata)); } + rules.insert(rules_.cbegin(), rules_.cend()); + trace_sampler_->set_rules(rules); + if (!conf.tags) { reset_config(ConfigName::TAGS, span_defaults_, metadata); } else { @@ -109,10 +188,9 @@ std::vector ConfigManager::reset() { return update({}); } nlohmann::json ConfigManager::config_json() const { std::lock_guard lock(mutex_); - return nlohmann::json{ - {"defaults", to_json(*span_defaults_.value())}, - {"trace_sampler", trace_sampler_.value()->config_json()}, - {"report_traces", report_traces_.value()}}; + return nlohmann::json{{"defaults", to_json(*span_defaults_.value())}, + {"trace_sampler", trace_sampler_->config_json()}, + {"report_traces", report_traces_.value()}}; } } // namespace tracing diff --git a/src/datadog/config_manager.h b/src/datadog/config_manager.h index 88698616..5c0536bc 100644 --- a/src/datadog/config_manager.h +++ b/src/datadog/config_manager.h @@ -54,7 +54,9 @@ class ConfigManager { Clock clock_; std::unordered_map default_metadata_; - DynamicConfig> trace_sampler_; + std::shared_ptr trace_sampler_; + std::unordered_map rules_; + DynamicConfig> span_defaults_; DynamicConfig report_traces_; diff --git a/src/datadog/config_update.h b/src/datadog/config_update.h index cd7e07f4..d16fe92b 100644 --- a/src/datadog/config_update.h +++ b/src/datadog/config_update.h @@ -17,6 +17,7 @@ struct ConfigUpdate { Optional report_traces; Optional trace_sampling_rate; Optional> tags; + const nlohmann::json* trace_sampling_rules = nullptr; }; } // namespace tracing diff --git a/src/datadog/datadog_agent_config.cpp b/src/datadog/datadog_agent_config.cpp index aef6074c..6049a823 100644 --- a/src/datadog/datadog_agent_config.cpp +++ b/src/datadog/datadog_agent_config.cpp @@ -21,7 +21,7 @@ Expected load_datadog_agent_env_config() { if (auto raw_rc_poll_interval_value = lookup(environment::DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS)) { - auto res = parse_int(*raw_rc_poll_interval_value, 10); + auto res = parse_double(*raw_rc_poll_interval_value); if (auto error = res.if_error()) { return error->with_prefix( "DatadogAgent: Remote Configuration poll interval error "); @@ -114,12 +114,13 @@ Expected finalize_config( "milliseconds."}; } - if (int rc_poll_interval_seconds = + if (double rc_poll_interval_seconds = value_or(env_config->remote_configuration_poll_interval_seconds, - user_config.remote_configuration_poll_interval_seconds, 5); - rc_poll_interval_seconds > 0) { + user_config.remote_configuration_poll_interval_seconds, 5.0); + rc_poll_interval_seconds >= 0.0) { result.remote_configuration_poll_interval = - std::chrono::seconds(rc_poll_interval_seconds); + std::chrono::duration_cast( + std::chrono::duration(rc_poll_interval_seconds)); } else { return Error{Error::DATADOG_AGENT_INVALID_REMOTE_CONFIG_POLL_INTERVAL, "DatadogAgent: Remote Configuration poll interval must be a " diff --git a/src/datadog/datadog_agent_config.h b/src/datadog/datadog_agent_config.h index 432b11b7..67559aee 100644 --- a/src/datadog/datadog_agent_config.h +++ b/src/datadog/datadog_agent_config.h @@ -61,7 +61,7 @@ struct DatadogAgentConfig { Optional remote_configuration_enabled; // How often, in seconds, to query the Datadog Agent for remote configuration // updates. - Optional remote_configuration_poll_interval_seconds; + Optional remote_configuration_poll_interval_seconds; static Expected parse(StringView); }; diff --git a/src/datadog/remote_config.cpp b/src/datadog/remote_config.cpp index a7be6d82..a2a4489f 100644 --- a/src/datadog/remote_config.cpp +++ b/src/datadog/remote_config.cpp @@ -30,7 +30,8 @@ namespace { enum CapabilitiesFlag : uint64_t { APM_TRACING_SAMPLE_RATE = 1 << 12, APM_TRACING_TAGS = 1 << 15, - APM_TRACING_ENABLED = 1 << 19 + APM_TRACING_ENABLED = 1 << 19, + APM_TRACING_SAMPLE_RULES = 1 << 29, }; constexpr std::array capabilities_byte_array( @@ -46,7 +47,7 @@ constexpr std::array capabilities_byte_array( constexpr std::array k_apm_capabilities = capabilities_byte_array(APM_TRACING_SAMPLE_RATE | APM_TRACING_TAGS | - APM_TRACING_ENABLED); + APM_TRACING_ENABLED | APM_TRACING_SAMPLE_RULES); constexpr StringView k_apm_product = "APM_TRACING"; constexpr StringView k_apm_product_path_substring = "/APM_TRACING/"; @@ -69,6 +70,12 @@ ConfigUpdate parse_dynamic_config(const nlohmann::json& j) { config_update.report_traces = tracing_enabled_it->get(); } + if (auto tracing_sampling_rules_it = j.find("tracing_sampling_rules"); + tracing_sampling_rules_it != j.cend() && + tracing_sampling_rules_it->is_array()) { + config_update.trace_sampling_rules = &(*tracing_sampling_rules_it); + } + return config_update; } diff --git a/src/datadog/sampling_mechanism.h b/src/datadog/sampling_mechanism.h index 26a9ecef..71c99a99 100644 --- a/src/datadog/sampling_mechanism.h +++ b/src/datadog/sampling_mechanism.h @@ -58,6 +58,13 @@ enum class SamplingMechanism { // Individual span kept by a matching span sampling rule when the enclosing // trace was dropped. SPAN_RULE = 8, + // Reserved for future use. + OTLP_RULE = 9, + // Sampling rule configured by user via remote configuration. + REMOTE_RULE = 11, + // Adaptive sampling rule automatically computed by Datadog backend and sent + // via remote configuration. + REMOTE_ADAPTIVE_RULE = 12, }; } // namespace tracing diff --git a/src/datadog/span_matcher.h b/src/datadog/span_matcher.h index 0de2fbf2..2ff0f4c8 100644 --- a/src/datadog/span_matcher.h +++ b/src/datadog/span_matcher.h @@ -32,7 +32,23 @@ struct SpanMatcher { nlohmann::json to_json() const; static Expected from_json(const nlohmann::json&); + + bool operator==(const SpanMatcher& other) const { + return (service == other.service && name == other.name && + resource == other.resource && tags == other.tags); + } + + // TODO: add tags + struct Hash { + size_t operator()(const SpanMatcher& rule) const { + return std::hash()(rule.service) ^ + (std::hash()(rule.name) << 1) ^ + (std::hash()(rule.resource) << 2); + } + }; }; +static const SpanMatcher catch_all; + } // namespace tracing } // namespace datadog diff --git a/src/datadog/span_sampler_config.cpp b/src/datadog/span_sampler_config.cpp index bde23eb8..38a33c11 100644 --- a/src/datadog/span_sampler_config.cpp +++ b/src/datadog/span_sampler_config.cpp @@ -16,7 +16,12 @@ namespace { std::string to_string(const std::vector &rules) { nlohmann::json res; for (const auto &r : rules) { - res.emplace_back(r.to_json()); + auto j = r.to_json(); + j["sample_rate"] = r.sample_rate; + if (r.max_per_second) { + j["max_per_second"] = *r.max_per_second; + } + res.emplace_back(std::move(j)); } return res.dump(); diff --git a/src/datadog/trace_sampler.cpp b/src/datadog/trace_sampler.cpp index ae74865b..356381a5 100644 --- a/src/datadog/trace_sampler.cpp +++ b/src/datadog/trace_sampler.cpp @@ -21,25 +21,32 @@ TraceSampler::TraceSampler(const FinalizedTraceSamplerConfig& config, limiter_(clock, config.max_per_second), limiter_max_per_second_(config.max_per_second) {} +void TraceSampler::set_rules( + std::unordered_map + rules) { + std::lock_guard lock(mutex_); + rules_ = std::move(rules); +} + SamplingDecision TraceSampler::decide(const SpanData& span) { SamplingDecision decision; decision.origin = SamplingDecision::Origin::LOCAL; // First check sampling rules. - auto found_rule = - std::find_if(rules_.begin(), rules_.end(), - [&](const auto& rule) { return rule.match(span); }); + const auto found_rule = + std::find_if(rules_.cbegin(), rules_.cend(), + [&](const auto& it) { return it.first.match(span); }); // `mutex_` protects `limiter_`, `collector_sample_rates_`, and // `collector_default_sample_rate_`, so let's lock it here. std::lock_guard lock(mutex_); if (found_rule != rules_.end()) { - const auto& rule = *found_rule; - decision.mechanism = int(SamplingMechanism::RULE); + const auto& [rule, rate] = *found_rule; + decision.mechanism = int(rate.mechanism); decision.limiter_max_per_second = limiter_max_per_second_; - decision.configured_rate = rule.sample_rate; - const std::uint64_t threshold = max_id_from_rate(rule.sample_rate); + decision.configured_rate = rate.value; + const std::uint64_t threshold = max_id_from_rate(rate.value); if (knuth_hash(span.trace_id.low) < threshold) { const auto result = limiter_.allow(); if (result.allowed) { @@ -99,8 +106,10 @@ void TraceSampler::handle_collector_response( nlohmann::json TraceSampler::config_json() const { std::vector rules; - for (const auto& rule : rules_) { - rules.push_back(to_json(rule)); + for (const auto& [rule, rate] : rules_) { + nlohmann::json j = rule.to_json(); + j["sampling_rate"] = rate.value.value(); + rules.push_back(std::move(j)); } return nlohmann::json::object({ diff --git a/src/datadog/trace_sampler.h b/src/datadog/trace_sampler.h index ddaec47d..03d19fa5 100644 --- a/src/datadog/trace_sampler.h +++ b/src/datadog/trace_sampler.h @@ -102,18 +102,22 @@ struct SamplingDecision; struct SpanData; class TraceSampler { + private: std::mutex mutex_; Optional collector_default_sample_rate_; std::unordered_map collector_sample_rates_; - - std::vector rules_; + std::unordered_map rules_; Limiter limiter_; double limiter_max_per_second_; public: TraceSampler(const FinalizedTraceSamplerConfig& config, const Clock& clock); + void set_rules( + std::unordered_map + rules); + // Return a sampling decision for the specified root span. SamplingDecision decide(const SpanData&); diff --git a/src/datadog/trace_sampler_config.cpp b/src/datadog/trace_sampler_config.cpp index 4a2cc229..4e39914e 100644 --- a/src/datadog/trace_sampler_config.cpp +++ b/src/datadog/trace_sampler_config.cpp @@ -134,7 +134,9 @@ Expected load_trace_sampler_env_config() { std::string to_string(const std::vector &rules) { nlohmann::json res; for (const auto &r : rules) { - res.emplace_back(r.to_json()); + auto j = r.to_json(); + j["sample_rate"] = r.sample_rate; + res.emplace_back(std::move(j)); } return res.dump(); @@ -179,10 +181,9 @@ Expected finalize_config( return error->with_prefix(prefix); } - FinalizedTraceSamplerConfig::Rule finalized; - static_cast(finalized) = rule; - finalized.sample_rate = *maybe_rate; - result.rules.push_back(std::move(finalized)); + SpanMatcher matcher = rule; + result.rules.emplace( + matcher, TraceSamplerRate{*maybe_rate, SamplingMechanism::RULE}); } Optional sample_rate; @@ -196,6 +197,10 @@ Expected finalize_config( result.metadata[ConfigName::TRACE_SAMPLING_RATE] = ConfigMetadata( ConfigName::TRACE_SAMPLING_RATE, to_string(*sample_rate, 1), ConfigMetadata::Origin::CODE); + } else { + result.metadata[ConfigName::TRACE_SAMPLING_RATE] = + ConfigMetadata(ConfigName::TRACE_SAMPLING_RATE, "1.0", + ConfigMetadata::Origin::DEFAULT); } // If `sample_rate` was specified, then it translates to a "catch-all" rule @@ -208,9 +213,8 @@ Expected finalize_config( "Unable to parse overall sample_rate for trace sampling: "); } - FinalizedTraceSamplerConfig::Rule catch_all; - catch_all.sample_rate = *maybe_rate; - result.rules.push_back(std::move(catch_all)); + result.rules.emplace( + catch_all, TraceSamplerRate{*maybe_rate, SamplingMechanism::RULE}); } const auto [origin, max_per_second] = @@ -234,12 +238,5 @@ Expected finalize_config( return result; } -nlohmann::json to_json(const FinalizedTraceSamplerConfig::Rule &rule) { - // Get the base class's fields, then add our own. - auto result = static_cast(rule).to_json(); - result["sample_rate"] = double(rule.sample_rate); - return result; -} - } // namespace tracing } // namespace datadog diff --git a/src/datadog/trace_sampler_config.h b/src/datadog/trace_sampler_config.h index bfe93a50..2e0f65b7 100644 --- a/src/datadog/trace_sampler_config.h +++ b/src/datadog/trace_sampler_config.h @@ -15,11 +15,17 @@ #include "json_fwd.hpp" #include "optional.h" #include "rate.h" +#include "sampling_mechanism.h" #include "span_matcher.h" namespace datadog { namespace tracing { +struct TraceSamplerRate final { + Rate value; + SamplingMechanism mechanism; +}; + struct TraceSamplerConfig { struct Rule : public SpanMatcher { double sample_rate = 1.0; @@ -41,20 +47,13 @@ class FinalizedTraceSamplerConfig { FinalizedTraceSamplerConfig() = default; public: - struct Rule : public SpanMatcher { - Rate sample_rate; - }; - - std::vector rules; double max_per_second; - std::unordered_map metadata; + std::unordered_map rules; }; Expected finalize_config( const TraceSamplerConfig& config); -nlohmann::json to_json(const FinalizedTraceSamplerConfig::Rule&); - } // namespace tracing } // namespace datadog diff --git a/src/datadog/trace_segment.cpp b/src/datadog/trace_segment.cpp index e2506f7d..a91c4da2 100644 --- a/src/datadog/trace_segment.cpp +++ b/src/datadog/trace_segment.cpp @@ -230,7 +230,10 @@ void TraceSegment::span_finished() { decision.mechanism == int(SamplingMechanism::DEFAULT)) { local_root.numeric_tags[tags::internal::agent_sample_rate] = *decision.configured_rate; - } else if (decision.mechanism == int(SamplingMechanism::RULE)) { + } else if (decision.mechanism == int(SamplingMechanism::RULE) || + decision.mechanism == int(SamplingMechanism::REMOTE_RULE) || + decision.mechanism == + int(SamplingMechanism::REMOTE_ADAPTIVE_RULE)) { local_root.numeric_tags[tags::internal::rule_sample_rate] = *decision.configured_rate; if (decision.limiter_effective_rate) { diff --git a/test/test_remote_config.cpp b/test/test_remote_config.cpp index 2800a1c7..e3bd9d98 100644 --- a/test/test_remote_config.cpp +++ b/test/test_remote_config.cpp @@ -3,6 +3,7 @@ #include "catch.hpp" #include "datadog/json_fwd.hpp" #include "datadog/remote_config.h" +#include "datadog/trace_sampler.h" #include "mocks/loggers.h" #include "test.h" @@ -199,16 +200,18 @@ REMOTE_CONFIG_TEST("response processing") { REQUIRE(!response_json.is_discarded()); - const auto old_trace_sampler = config_manager->trace_sampler(); + const auto old_trace_sampler_config = + config_manager->trace_sampler()->config_json(); const auto old_span_defaults = config_manager->span_defaults(); const auto old_report_traces = config_manager->report_traces(); const auto config_updated = rc.process_response(response_json); REQUIRE(config_updated.size() == 3); - const auto new_trace_sampler = config_manager->trace_sampler(); + const auto new_trace_sampler_config = + config_manager->trace_sampler()->config_json(); const auto new_span_defaults = config_manager->span_defaults(); const auto new_report_traces = config_manager->report_traces(); - CHECK(new_trace_sampler != old_trace_sampler); + CHECK(new_trace_sampler_config != old_trace_sampler_config); CHECK(new_span_defaults != old_span_defaults); CHECK(new_report_traces != old_report_traces); @@ -245,11 +248,12 @@ REMOTE_CONFIG_TEST("response processing") { const auto config_updated = rc.process_response(response_json); REQUIRE(config_updated.size() == 3); - const auto current_trace_sampler = config_manager->trace_sampler(); + const auto current_trace_sampler_config = + config_manager->trace_sampler()->config_json(); const auto current_span_defaults = config_manager->span_defaults(); const auto current_report_traces = config_manager->report_traces(); - CHECK(old_trace_sampler == current_trace_sampler); + CHECK(old_trace_sampler_config == current_trace_sampler_config); CHECK(old_span_defaults == current_span_defaults); CHECK(old_report_traces == current_report_traces); } @@ -279,8 +283,9 @@ REMOTE_CONFIG_TEST("response processing") { const auto config_updated = rc.process_response(response_json); REQUIRE(config_updated.size() == 1); - const auto current_trace_sampler = config_manager->trace_sampler(); - CHECK(old_trace_sampler == current_trace_sampler); + const auto current_trace_sampler_config = + config_manager->trace_sampler()->config_json(); + CHECK(old_trace_sampler_config == current_trace_sampler_config); } } } diff --git a/test/test_tracer_config.cpp b/test/test_tracer_config.cpp index 42c93bc1..ed5a2df1 100644 --- a/test/test_tracer_config.cpp +++ b/test/test_tracer_config.cpp @@ -446,14 +446,6 @@ TEST_CASE("TracerConfig::agent") { } SECTION("remote configuration poll interval") { - SECTION("cannot be zero") { - config.agent.remote_configuration_poll_interval_seconds = 0; - auto finalized = finalize_config(config); - REQUIRE(!finalized); - REQUIRE(finalized.error().code == - Error::DATADOG_AGENT_INVALID_REMOTE_CONFIG_POLL_INTERVAL); - } - SECTION("cannot be negative") { config.agent.remote_configuration_poll_interval_seconds = -1337; auto finalized = finalize_config(config); @@ -491,7 +483,7 @@ TEST_CASE("TracerConfig::agent") { "ddog"}; auto finalized = finalize_config(config); REQUIRE(!finalized); - REQUIRE(finalized.error().code == Error::INVALID_INTEGER); + REQUIRE(finalized.error().code == Error::INVALID_DOUBLE); } } } @@ -614,9 +606,11 @@ TEST_CASE("TracerConfig::trace_sampler") { SECTION("yields one sampling rule") { auto finalized = finalize_config(config); REQUIRE(finalized); - REQUIRE(finalized->trace_sampler.rules.size() == 1); + REQUIRE(finalized->trace_sampler.rules.count(catch_all)); // and the default sample_rate is 100% - REQUIRE(finalized->trace_sampler.rules.front().sample_rate == 1.0); + const auto& rate = finalized->trace_sampler.rules[catch_all]; + CHECK(rate.value == 1.0); + CHECK(rate.mechanism == SamplingMechanism::RULE); } SECTION("has to have a valid sample_rate") { @@ -637,22 +631,21 @@ TEST_CASE("TracerConfig::trace_sampler") { rules[1].sample_rate = 0.6; auto finalized = finalize_config(config); REQUIRE(finalized); - REQUIRE(finalized->trace_sampler.rules.size() == 2); - REQUIRE(finalized->trace_sampler.rules[0].sample_rate == 0.5); - REQUIRE(finalized->trace_sampler.rules[1].sample_rate == 0.6); + REQUIRE(finalized->trace_sampler.rules.count(catch_all)); + + const auto& rate = finalized->trace_sampler.rules[catch_all]; + CHECK(rate.value == 0.5); + CHECK(rate.mechanism == SamplingMechanism::RULE); } SECTION("global sample_rate creates a catch-all rule") { config.trace_sampler.sample_rate = 0.25; auto finalized = finalize_config(config); REQUIRE(finalized); - REQUIRE(finalized->trace_sampler.rules.size() == 1); - const auto& rule = finalized->trace_sampler.rules.front(); - REQUIRE(rule.sample_rate == 0.25); - REQUIRE(rule.service == "*"); - REQUIRE(rule.name == "*"); - REQUIRE(rule.resource == "*"); - REQUIRE(rule.tags.empty()); + REQUIRE(finalized->trace_sampler.rules.count(catch_all)); + const auto& rate = finalized->trace_sampler.rules[catch_all]; + CHECK(rate.value == 0.25); + CHECK(rate.mechanism == SamplingMechanism::RULE); } SECTION("DD_TRACE_SAMPLE_RATE") { @@ -660,8 +653,10 @@ TEST_CASE("TracerConfig::trace_sampler") { const EnvGuard guard{"DD_TRACE_SAMPLE_RATE", "0.5"}; auto finalized = finalize_config(config); REQUIRE(finalized); - REQUIRE(finalized->trace_sampler.rules.size() == 1); - REQUIRE(finalized->trace_sampler.rules.front().sample_rate == 0.5); + REQUIRE(finalized->trace_sampler.rules.count(catch_all)); + const auto& rate = finalized->trace_sampler.rules[catch_all]; + CHECK(rate.value == 0.5); + CHECK(rate.mechanism == SamplingMechanism::RULE); } SECTION("overrides TraceSamplerConfig::sample_rate") { @@ -669,8 +664,10 @@ TEST_CASE("TracerConfig::trace_sampler") { const EnvGuard guard{"DD_TRACE_SAMPLE_RATE", "0.5"}; auto finalized = finalize_config(config); REQUIRE(finalized); - REQUIRE(finalized->trace_sampler.rules.size() == 1); - REQUIRE(finalized->trace_sampler.rules.front().sample_rate == 0.5); + REQUIRE(finalized->trace_sampler.rules.count(catch_all)); + const auto& rate = finalized->trace_sampler.rules[catch_all]; + CHECK(rate.value == 0.5); + CHECK(rate.mechanism == SamplingMechanism::RULE); } SECTION("has to have a valid value") { @@ -802,16 +799,27 @@ TEST_CASE("TracerConfig::trace_sampler") { CAPTURE(rules_json); CAPTURE(rules); REQUIRE(rules.size() == 2); - REQUIRE(rules[0].service == "poohbear"); - REQUIRE(rules[0].name == "get.honey"); - REQUIRE(rules[0].sample_rate == 0); - REQUIRE(rules[0].tags.size() == 0); - REQUIRE(rules[1].service == "*"); - REQUIRE(rules[1].name == "*"); - REQUIRE(rules[1].sample_rate == 1); - REQUIRE(rules[1].tags.size() == 1); - REQUIRE(rules[1].tags.at("error") == "*"); - REQUIRE(rules[1].resource == "/admin/*"); + + SpanMatcher matcher; + matcher.service = "poohbear"; + matcher.name = "get.honey"; + + auto found = rules.find(matcher); + REQUIRE(found != rules.cend()); + + CHECK(found->second.value == 0); + CHECK(found->second.mechanism == SamplingMechanism::RULE); + + SpanMatcher matcher2; + matcher2.service = "*"; + matcher2.name = "*"; + matcher2.tags.emplace("error", "*"); + matcher2.resource = "/admin/*"; + + found = rules.find(matcher2); + REQUIRE(found != rules.cend()); + CHECK(found->second.value == 1); + CHECK(found->second.mechanism == SamplingMechanism::RULE); } SECTION("must be valid") {