diff --git a/doc/sampling-delegation.md b/doc/sampling-delegation.md deleted file mode 100644 index bafa7e29..00000000 --- a/doc/sampling-delegation.md +++ /dev/null @@ -1,189 +0,0 @@ -# Sampling Delegation -This document is a technical description of how sampling delegation works in -this library. The intended audience is maintainers of the library. - -Sampling delegation allows a tracer to use the trace sampling decision of a -service that it calls. The purpose of sampling delegation is to allow reverse -proxies at the ingress of a system (gateways) to use trace sampling decisions -that are decided by the actual services, as opposed to having to decide the -trace sampling decision at the proxy. The idea is that putting a reverse proxy -in front of your service(s) should not change how you configure sampling. - -See the `sampling-delegation` directory in Datadog's internal architecture -repository for the specification of sampling delegation. - -## Roles -In sampling delegation, a tracer plays one or both of two roles: - -- The _delegator_ is the tracer that is configured to delegate its trace - sampling decision. The delegator will request a sampling decision from one of - the services it calls. - - It will send the `X-Datadog-Delegate-Trace-Sampling` request header. - - If it is the root service, and if delegation succeeded, then it will set the - `_dd.is_sampling_decider:0` tag to indicate that some other service made the - sampling decision. -- The _delegatee_ is the tracer that has received a request whose headers - indicate that the client is delegating the sampling decision. The delegatee - will make a trace sampling decision using its own configuration, and then - convey that decision back to the client. - - It will send the `X-Datadog-Trace-Sampling-Decision` response header. - - If its sampling decision was made locally, as opposed to delegated to yet - another service, then it will set the `_dd.is_sampling_decider:1` tag to - indicate that it is the service that made the sampling decision. - -For a given trace, the tracer might act as the delegator, the delegatee, both, -or neither. - -## Tracer Configuration -Whether a tracer should act as a delegator is determined by its configuration. - -`bool TracerConfig::delegate_trace_sampling` is defined in [tracer_config.h][1] -and defaults to `false`. Its value is overridden by the -`DD_TRACE_DELEGATE_SAMPLING` environment variable. If `delegate_trace_sampling` -is `true`, then the tracer will act as delegator. - -## Runtime State -Whether a tracer should act as a delegatee is determined by whether the -extracted trace context includes the `X-Datadog-Delegate-Trace-Sampling` request -header. If trace context is extracted in the Datadog style, and if the -extracted context includes the `X-Datadog-Delegate-Trace-Sampling` header, then -the tracer will act as delegatee. - -All logic relevant to sampling delegation happens in `TraceSegment`, defined in -[trace_segment.h][2]. The `Tracer` that creates the `TraceSegment` passes -two booleans into `TraceSegment`'s constructor: - -- `bool sampling_delegation_enabled` indicates whether the `TraceSegment` will - act as delegator. -- `bool sampling_decision_was_delegated_to_me` indicates whether the - `TraceSegment` will act as delegatee. - -`TraceSegment` then keeps track of its sampling delegation relevant state in a -private data structure, `struct SamplingDelegation` (also defined in -[trace_segment.h][2]). `struct SamplingDelegation` contains the two booleans -passed into `TraceSegment`'s constructor, and additional booleans used -throughout the trace segment's lifetime. - -### `bool TraceSegment::SamplingDelegation::sent_request_header` -`send_request_header` indicates that, as delegator, the trace segment included -the `X-Datadog-Delegate-Trace-Sampling` request header as part of trace context -sent to another service. - -`sent_request_header` is used to prevent sampling delegation from being -requested of two or more services. Once a trace segment has requested sampling -delegation once, it will not request sampling delegation again, even if it never -receives the delegated decision in response. - -### `bool TraceSegment::SamplingDelegation::received_matching_response_header` -`received_matching_response_header` indicates that, as delegator, the trace -segment received a valid `X-Datadog-Trace-Sampling-Decision` response header -from a service to which the trace segment had previously sent the -`X-Datadog-Delegate-Trace-Sampling` request header. - -The `X-Datadog-Trace-Sampling-Decision` response header is valid if it is valid -JSON of the form `{"priority": int, "mechanism": int}`. See -`parse_sampling_delegation_response`, defined in [trace_segment.cpp][3]. - -`received_matching_response_header` is used as part of determining whether to -set the `_dd.is_sampling_decider:1` tag as delegatee. If a trace segment is -acting as delegatee, and if it made the sampling decision, then it sets the tag -`_dd.is_sampling_decider:1` on its local root span. However, the trace segment -might also be acting as delegator. `received_matching_response_header` allows -the trace segment to determine whether it delegated its decision to another -service, and thus is not the "sampling decider." - -An alternative way to determine whether a trace segment delegated its sampling -decision is to see whether its `SamplingDecision::origin` has the value -`SamplingDecision::Origin::DELEGATED` (see [sampling_decision.h][4]). However, -a trace segment's sampling decision might be overridden at any time by -`TraceSegment::override_sampling_priority(int)`. So, to answer the question -"did we delegate to another service?" it is better to keep track of whether the -trace segment received a valid and expected `X-Datadog-Trace-Sampling-Decision` -response header, which is what `received_matching_response_header` does. - -### `bool TraceSegment::SamplingDelegation::sent_response_header` -`sent_response_header` indicates that, as delegatee, the trace segment sent its trace sampling -decision back to the client in the `X-Datadog-Trace-Sampling-Decision` response -header. - -`sent_response_header` is used as part of determining whether to set the -`_dd.is_sampling_decider:1` tag as delegatee. The trace segment would not claim -to be the "sampling decider" if the service that delegated to it does not know -about the decision. If `sent_response_header` is true, then the trace segment -can be fairly confident that the client will receive the sampling decision. - -### `bool Span::expecting_delegated_sampling_decision_` -In addition to the state maintained in `TraceSegment`, `Span` also has a -sampling delegation related `bool`. See [span.h][5]. - -When sampling delegation is requested for an injected `Span`, that span -remembers that it injected the `X-Datadog-Delegate-Trace-Sampling` header. - -Later, when the corresponding response is examined, the `Span` knows whether to -expect the `X-Datadog-Trace-Sampling-Decision` response header to be present. - -`bool Span::expecting_delegated_sampling_decision_` prevents a `Span` from -interpreting an `X-Datadog-Trace-Sampling-Decision` response header when none -was requested. - -## Reading and Writing Responses -Distributed tracing typically does not involve RPC _responses_. When a service -X makes an HTTP/gRPC/etc. request to another service Y, X injects information -about the trace in request metadata (e.g. HTTP request headers). Y then -extracts that information from the request. - -Responses aren't involved. - -Now, with sampling delegation, responses _are_ involved. - -Trace context injection and extraction are about _requests_ (sending a receiving, -respectively). For _responses_ the tracing library needs a new notion. - -`TraceSegment` has two member functions for producing and consuming -response-related metadata (see [trace_segment.h][2]): - -- `void TraceSegment::write_sampling_delegation_response(DictWriter&)` writes - the `X-Datadog-Trace-Sampling-Decision` response header, if appropriate. This - is something that a _delegatee_ does. -- `void TraceSegment::read_sampling_delegation_response(const DictReader&)` - reads the `X-Datadog-Delegate-Trace-Sampling` response header, if present. - This is something that a _delegator_ does. - -`TraceSegment::read_sampling_delegation_response` is not called directly by an -instrumented application. -Instead, an instrumented application calls -`Span::read_sampling_delegation_response` on the `Span` that performed the -injection whose response is being examined. -`Span::read_sampling_delegation_response` then might call -`TraceSegment::read_sampling_delegation_response`. - -`TraceSegment::write_sampling_delegation_response` is called directly by an -instrumented application. - -Just as `Tracer::extract_span` and `Span::inject` must be called by an -instrumented application in order for trace context propagation to work, -`Span::read_sampling_delegation_response` and -`TraceSegment::write_sampling_delegation_response` must be called by an -instrumented application in order for sampling delegation to work. - -## Per-Trace Configuration -In addition to the `Tracer`-wide configuration option `bool -TracerConfig::delegate_trace_sampling`, there is also a per-injection option -`Optional InjectionOptions::delegate_sampling_decision`. - -`Span::inject` has an overload -`void inject(DictWriter&, const InjectionOptions&) const`. The -`InjectionOptions` can be used to specify sampling delegation (or its absence) -for this particular injection site. If -`InjectionOptions::delegate_sampling_decision` is null, which is the default, -then the tracer-wide configuration option is used instead. - -This granularity of control is useful in NGINX, where one `location` (i.e. -upstream or backend) might be configured for sampling delegation, while another -`location` might not. - -[1]: ../include/datadog/tracer_config.h -[2]: ../include/datadog/trace_segment.h -[3]: ../src/datadog/trace_segment.cpp -[4]: ../include/datadog/sampling_decision.h -[5]: ../include/datadog/sampling_decision.h diff --git a/examples/http-server/proxy/proxy.cpp b/examples/http-server/proxy/proxy.cpp index a6898d73..3941e869 100644 --- a/examples/http-server/proxy/proxy.cpp +++ b/examples/http-server/proxy/proxy.cpp @@ -74,12 +74,6 @@ int main() { span.set_error_message(httplib::to_string(er)); std::cerr << "Error occurred while proxying request: " << req.target << "\n"; - } else { - tracingutil::HeaderReader reader(res.headers); - auto status = span.read_sampling_delegation_response(reader); - if (auto error = status.if_error()) { - std::cerr << error << "\n"; - } } span.set_tag("http.status_code", std::to_string(res.status)); diff --git a/examples/http-server/server/server.cpp b/examples/http-server/server/server.cpp index cade2877..1b183ee2 100644 --- a/examples/http-server/server/server.cpp +++ b/examples/http-server/server/server.cpp @@ -159,10 +159,6 @@ int main() { // created. We finish it by popping it off of the span stack. server.set_post_routing_handler([](const httplib::Request& request, httplib::Response& response) { auto* context = static_cast(request.user_data.get()); - - tracingutil::HeaderWriter writer(response.headers); - context->spans.top().trace_segment().write_sampling_delegation_response(writer); - context->spans.pop(); return httplib::Server::HandlerResponse::Unhandled; }); diff --git a/include/datadog/environment.h b/include/datadog/environment.h index 8fd9a32e..7a1e1fa5 100644 --- a/include/datadog/environment.h +++ b/include/datadog/environment.h @@ -34,7 +34,6 @@ namespace environment { MACRO(DD_SERVICE) \ MACRO(DD_SPAN_SAMPLING_RULES) \ MACRO(DD_SPAN_SAMPLING_RULES_FILE) \ - MACRO(DD_TRACE_DELEGATE_SAMPLING) \ MACRO(DD_TRACE_PROPAGATION_STYLE_EXTRACT) \ MACRO(DD_TRACE_PROPAGATION_STYLE_INJECT) \ MACRO(DD_TRACE_PROPAGATION_STYLE) \ diff --git a/include/datadog/injection_options.h b/include/datadog/injection_options.h index ce1b83f0..f6f24b2d 100644 --- a/include/datadog/injection_options.h +++ b/include/datadog/injection_options.h @@ -4,20 +4,10 @@ // parameters to `Span::inject` that alter the behavior of trace context // propagation. -#include "optional.h" - namespace datadog { namespace tracing { -struct InjectionOptions { - // If this tracer is using the "Datadog" propagation injection style, then - // include a request header that indicates that whoever extracts this trace - // context "on the other side" may make their own trace sampling decision - // and convey it back to us in a response header. If - // `delegate_sampling_decision` is null, then its value depends on the tracer - // configuration (see `TracerConfig::delegate_trace_sampling`). - Optional delegate_sampling_decision; -}; +struct InjectionOptions {}; } // namespace tracing } // namespace datadog diff --git a/include/datadog/span.h b/include/datadog/span.h index 1e873096..d15348b3 100644 --- a/include/datadog/span.h +++ b/include/datadog/span.h @@ -66,7 +66,6 @@ class Span { std::function generate_span_id_; Clock clock_; Optional end_time_; - mutable bool expecting_delegated_sampling_decision_; public: // Create a span whose properties are stored in the specified `data`, that is @@ -169,13 +168,6 @@ class Span { void inject(DictWriter& writer) const; void inject(DictWriter& writer, const InjectionOptions& options) const; - // If this span is expecting a sampling decision that it previously delegated, - // then extract a sampling decision from the specified `reader`. Return an - // error if a sampling decision is present in `reader` but is invalid. Return - // success otherwise. The trace segment associated with this span might adopt - // the sampling decision from `reader`. - Expected read_sampling_delegation_response(const DictReader& reader); - // Return a reference to this span's trace segment. The trace segment has // member functions that affect the trace as a whole, such as // `TraceSegment::override_sampling_priority`. diff --git a/include/datadog/trace_segment.h b/include/datadog/trace_segment.h index 39eff57b..f2e28e79 100644 --- a/include/datadog/trace_segment.h +++ b/include/datadog/trace_segment.h @@ -79,25 +79,6 @@ class TraceSegment { std::shared_ptr config_manager_; - // See `doc/sampling-delegation.md` for more information about - // `struct SamplingDelegation`. - struct SamplingDelegation { - // This segment is configured to delegate its sampling decision. - bool enabled; - // The trace context from which the local root span was extracted delegated - // the sampling decision to this segment. - bool decision_was_delegated_to_me; - // This segment included a request for sampling delegation in outbound - // injected trace context (see `inject`). - bool sent_request_header; - // This segment received a (presumably delegated) sampling decision. See - // `read_sampling_delegation_response`. - bool received_matching_response_header; - // This segment conveyed a sampling decision back to a parent service that - // had previously requested a delegated sampling decision. - bool sent_response_header; - } sampling_delegation_ = {}; - public: TraceSegment(const std::shared_ptr& logger, const std::shared_ptr& collector, @@ -106,8 +87,7 @@ class TraceSegment { const std::shared_ptr& span_sampler, const std::shared_ptr& defaults, const std::shared_ptr& config_manager, - const RuntimeID& runtime_id, bool sampling_delegation_enabled, - bool sampling_decision_was_delegated_to_me, + const RuntimeID& runtime_id, const std::vector& injection_styles, const Optional& hostname, Optional origin, std::size_t tags_header_max_size, @@ -131,14 +111,6 @@ class TraceSegment { bool inject(DictWriter& writer, const SpanData& span, const InjectionOptions& options); - // Inject this segment's trace sampling decision into the specified `writer`, - // if appropriate. - void write_sampling_delegation_response(DictWriter& writer); - - // Extract a trace sampling decision from the specified `reader` if it has - // one, and use the resulting decision, if appropriate. - Expected read_sampling_delegation_response(const DictReader& reader); - // Take ownership of the specified `span`. void register_span(std::unique_ptr span); // Increment the number of finished spans. If that number is equal to the diff --git a/include/datadog/tracer.h b/include/datadog/tracer.h index a6e758df..d7d85286 100644 --- a/include/datadog/tracer.h +++ b/include/datadog/tracer.h @@ -49,7 +49,6 @@ class Tracer { std::vector extraction_styles_; Optional hostname_; std::size_t tags_header_max_size_; - bool sampling_delegation_enabled_; // Store the tracer configuration in an in-memory file, allowing it to be // read to determine if the process is instrumented with a tracer and to // retrieve relevant tracing information. diff --git a/include/datadog/tracer_config.h b/include/datadog/tracer_config.h index cac22d6f..db8376a5 100644 --- a/include/datadog/tracer_config.h +++ b/include/datadog/tracer_config.h @@ -80,11 +80,6 @@ struct TracerConfig { // `telemetry/configuration.h` By default, the telemetry module is enabled. telemetry::Configuration telemetry; - // `delegate_trace_sampling` indicates whether the tracer will consult a child - // service for a trace sampling decision, and prefer the resulting decision - // over its own, if appropriate. - Optional delegate_trace_sampling; - // `trace_sampler` configures trace sampling. Trace sampling determines which // traces are sent to Datadog. See `trace_sampler_config.h`. TraceSamplerConfig trace_sampler; @@ -199,7 +194,6 @@ class FinalizedTracerConfig final { Clock clock; std::string integration_name; std::string integration_version; - bool delegate_trace_sampling; bool report_traces; std::unordered_map metadata; Baggage::Options baggage_opts; diff --git a/src/datadog/extracted_data.h b/src/datadog/extracted_data.h index ef407f77..b2d17c30 100644 --- a/src/datadog/extracted_data.h +++ b/src/datadog/extracted_data.h @@ -20,7 +20,6 @@ struct ExtractedData { Optional parent_id; Optional origin; std::vector> trace_tags; - bool delegate_sampling_decision = false; Optional sampling_priority; // If this `ExtractedData` was created on account of `PropagationStyle::W3C`, // then `datadog_w3c_parent_id` contains the parts of the "tracestate" diff --git a/src/datadog/extraction_util.cpp b/src/datadog/extraction_util.cpp index cde533bc..0b4965fa 100644 --- a/src/datadog/extraction_util.cpp +++ b/src/datadog/extraction_util.cpp @@ -143,11 +143,6 @@ Expected extract_datadog( result.sampling_priority = *sampling_priority; } - if (auto sampling_delegation_header = - headers.lookup("x-datadog-delegate-trace-sampling")) { - result.delegate_sampling_decision = true; - } - auto origin = headers.lookup("x-datadog-origin"); if (origin) { result.origin = std::string(*origin); diff --git a/src/datadog/span.cpp b/src/datadog/span.cpp index 27222ba1..f7419841 100644 --- a/src/datadog/span.cpp +++ b/src/datadog/span.cpp @@ -20,8 +20,7 @@ Span::Span(SpanData* data, const std::shared_ptr& trace_segment, : trace_segment_(trace_segment), data_(data), generate_span_id_(generate_span_id), - clock_(clock), - expecting_delegated_sampling_decision_(false) { + clock_(clock) { assert(trace_segment_); assert(data_); assert(generate_span_id_); @@ -59,21 +58,11 @@ Span Span::create_child(const SpanConfig& config) const { Span Span::create_child() const { return create_child(SpanConfig{}); } void Span::inject(DictWriter& writer) const { - expecting_delegated_sampling_decision_ = - trace_segment_->inject(writer, *data_); + trace_segment_->inject(writer, *data_); } void Span::inject(DictWriter& writer, const InjectionOptions& options) const { - expecting_delegated_sampling_decision_ = - trace_segment_->inject(writer, *data_, options); -} - -Expected Span::read_sampling_delegation_response( - const DictReader& reader) { - if (!expecting_delegated_sampling_decision_) return {}; - - expecting_delegated_sampling_decision_ = false; - return trace_segment_->read_sampling_delegation_response(reader); + trace_segment_->inject(writer, *data_, options); } std::uint64_t Span::id() const { return data_->span_id; } diff --git a/src/datadog/trace_segment.cpp b/src/datadog/trace_segment.cpp index 3302102b..46444716 100644 --- a/src/datadog/trace_segment.cpp +++ b/src/datadog/trace_segment.cpp @@ -33,9 +33,6 @@ namespace datadog { namespace tracing { namespace { -constexpr StringView sampling_delegation_response_header = - "x-datadog-trace-sampling-decision"; - struct Cache { static int process_id; @@ -83,24 +80,6 @@ void inject_trace_tags( } } -Expected parse_sampling_delegation_response( - StringView response) { - try { - auto json = nlohmann::json::parse(response); - - SamplingDecision sampling_decision; - sampling_decision.origin = SamplingDecision::Origin::DELEGATED; - sampling_decision.priority = json.at("priority"); - sampling_decision.mechanism = json.at("mechanism"); - - return sampling_decision; - } catch (const std::exception& e) { - std::string msg{"Unable to parse sampling delegation response: "}; - msg += e.what(); - return Error{Error::Code::SAMPLING_DELEGATION_RESPONSE_INVALID_JSON, msg}; - } -} - } // namespace TraceSegment::TraceSegment( @@ -111,8 +90,7 @@ TraceSegment::TraceSegment( const std::shared_ptr& span_sampler, const std::shared_ptr& defaults, const std::shared_ptr& config_manager, - const RuntimeID& runtime_id, bool sampling_delegation_enabled, - bool sampling_decision_was_delegated_to_me, + const RuntimeID& runtime_id, const std::vector& injection_styles, const Optional& hostname, Optional origin, std::size_t tags_header_max_size, @@ -147,10 +125,6 @@ TraceSegment::TraceSegment( assert(defaults_); assert(config_manager_); - sampling_delegation_.enabled = sampling_delegation_enabled; - sampling_delegation_.decision_was_delegated_to_me = - sampling_decision_was_delegated_to_me; - register_span(std::move(local_root)); } @@ -251,16 +225,6 @@ void TraceSegment::span_finished() { // the sampling decision and so are not the "sampling decider." local_root.tags[tags::internal::sampling_decider] = "0"; } - if (local_root.parent_id != 0 && - sampling_delegation_.decision_was_delegated_to_me && - sampling_delegation_.sent_response_header && - !sampling_delegation_.received_matching_response_header) { - // Convey the fact that, while we are not the root service, somebody - // delegated the trace sampling decision to us, we did not then delegate it - // to someone else, and we ultimately conveyed our decision back to the - // parent service. So, we're the "sampling decider." - local_root.tags[tags::internal::sampling_decider] = "1"; - } // Some tags are repeated on all spans. for (const auto& span_ptr : spans_) { @@ -344,37 +308,13 @@ bool TraceSegment::inject(DictWriter& writer, const SpanData& span) { } bool TraceSegment::inject(DictWriter& writer, const SpanData& span, - const InjectionOptions& options) { + const InjectionOptions&) { // If the only injection style is `NONE`, then don't do anything. if (injection_styles_.size() == 1 && injection_styles_[0] == PropagationStyle::NONE) { return false; } - bool delegate_sampling; - // If `options.delegate_sampling_decision` is null, then pick a default based - // on our sampling delegation configuration and state. - // - // Also, even if the caller requested sampling delegation, do _not_ perform - // sampling delegation if we previously extracted a sampling decision for - // which delegation was not requested. - // That is, don't let our desire to delegate sampling result in overriding a - // sampling decision made earlier in the trace. - { - std::lock_guard lock{mutex_}; - if (sampling_decision_ && - sampling_decision_->origin == SamplingDecision::Origin::EXTRACTED && - !sampling_delegation_.decision_was_delegated_to_me) { - delegate_sampling = false; - } else { - delegate_sampling = options.delegate_sampling_decision.value_or( - sampling_delegation_.enabled && - !sampling_delegation_.sent_request_header); - } - } - - bool delegated_trace_sampling_decision = false; - // The sampling priority can change (it can be overridden on another thread), // and trace tags might change when that happens ("_dd.p.dm"). // So, we lock here, make a sampling decision if necessary, and then copy the @@ -399,14 +339,6 @@ bool TraceSegment::inject(DictWriter& writer, const SpanData& span, if (origin_) { writer.set("x-datadog-origin", *origin_); } - if (delegate_sampling) { - delegated_trace_sampling_decision = true; - { - std::lock_guard lock(mutex_); - sampling_delegation_.sent_request_header = true; - } - writer.set("x-datadog-delegate-trace-sampling", "delegate"); - } inject_trace_tags(writer, trace_tags, tags_header_max_size_, spans_.front()->tags, *logger_); break; @@ -439,49 +371,7 @@ bool TraceSegment::inject(DictWriter& writer, const SpanData& span, } } - return delegated_trace_sampling_decision; -} - -void TraceSegment::write_sampling_delegation_response(DictWriter& writer) { - nlohmann::json j; - { - std::lock_guard lock(mutex_); - if (!sampling_delegation_.decision_was_delegated_to_me) return; - make_sampling_decision_if_null(); - assert(sampling_decision_); - j["priority"] = sampling_decision_->priority; - assert(sampling_decision_->mechanism); - j["mechanism"] = *sampling_decision_->mechanism; - sampling_delegation_.sent_response_header = true; - } - - writer.set(sampling_delegation_response_header, j.dump()); -} - -Expected TraceSegment::read_sampling_delegation_response( - const DictReader& headers) { - auto header_value = headers.lookup(sampling_delegation_response_header); - if (!header_value) { - return {}; - } - - auto decision = parse_sampling_delegation_response(*header_value); - if (Error* error = decision.if_error()) { - return std::move(*error); - } - - std::lock_guard lock(mutex_); - sampling_delegation_.received_matching_response_header = true; - // Overwrite any existing sampling decision if and only if the existing - // decision is not a local manual override. - if (!(sampling_decision_ && - sampling_decision_->origin == SamplingDecision::Origin::LOCAL && - sampling_decision_->mechanism == int(SamplingMechanism::MANUAL))) { - sampling_decision_ = *decision; - update_decision_maker_trace_tag(); - } - - return {}; + return true; } } // namespace tracing diff --git a/src/datadog/tracer.cpp b/src/datadog/tracer.cpp index ffc298ac..d52814e8 100644 --- a/src/datadog/tracer.cpp +++ b/src/datadog/tracer.cpp @@ -59,7 +59,6 @@ Tracer::Tracer(const FinalizedTracerConfig& config, injection_styles_(config.injection_styles), extraction_styles_(config.extraction_styles), tags_header_max_size_(config.tags_header_size), - sampling_delegation_enabled_(config.delegate_trace_sampling), baggage_opts_(config.baggage_opts), baggage_injection_enabled_(false), baggage_extraction_enabled_(false) { @@ -191,9 +190,7 @@ Span Tracer::create_span(const SpanConfig& config) { tracer_telemetry_->metrics().tracer.trace_segments_created_new.inc(); const auto segment = std::make_shared( logger_, collector_, tracer_telemetry_, config_manager_->trace_sampler(), - span_sampler_, defaults, config_manager_, runtime_id_, - sampling_delegation_enabled_, - false /* sampling_decision_was_delegated_to_me */, injection_styles_, + span_sampler_, defaults, config_manager_, runtime_id_, injection_styles_, hostname_, nullopt /* origin */, tags_header_max_size_, std::move(trace_tags), nullopt /* sampling_decision */, nullopt /* additional_w3c_tracestate */, @@ -378,11 +375,8 @@ Expected Tracer::extract_span(const DictReader& reader, *merged_context.datadog_w3c_parent_id; } - const bool delegate_sampling_decision = - sampling_delegation_enabled_ && merged_context.delegate_sampling_decision; - Optional sampling_decision; - if (!delegate_sampling_decision && merged_context.sampling_priority) { + if (merged_context.sampling_priority) { SamplingDecision decision; decision.priority = *merged_context.sampling_priority; // `decision.mechanism` is null. We might be able to infer it once we @@ -397,10 +391,9 @@ Expected Tracer::extract_span(const DictReader& reader, const auto segment = std::make_shared( logger_, collector_, tracer_telemetry_, config_manager_->trace_sampler(), span_sampler_, config_manager_->span_defaults(), config_manager_, - runtime_id_, sampling_delegation_enabled_, delegate_sampling_decision, - injection_styles_, hostname_, std::move(merged_context.origin), - tags_header_max_size_, std::move(merged_context.trace_tags), - std::move(sampling_decision), + runtime_id_, injection_styles_, hostname_, + std::move(merged_context.origin), tags_header_max_size_, + std::move(merged_context.trace_tags), std::move(sampling_decision), std::move(merged_context.additional_w3c_tracestate), std::move(merged_context.additional_datadog_w3c_tracestate), std::move(span_data)); diff --git a/src/datadog/tracer_config.cpp b/src/datadog/tracer_config.cpp index 0118f102..06983ff9 100644 --- a/src/datadog/tracer_config.cpp +++ b/src/datadog/tracer_config.cpp @@ -122,10 +122,6 @@ Expected load_tracer_env_config(Logger &logger) { if (auto enabled_env = lookup(environment::DD_TRACE_ENABLED)) { env_cfg.report_traces = !falsy(*enabled_env); } - if (auto trace_delegate_sampling_env = - lookup(environment::DD_TRACE_DELEGATE_SAMPLING)) { - env_cfg.delegate_trace_sampling = !falsy(*trace_delegate_sampling_env); - } if (auto enabled_env = lookup(environment::DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED)) { env_cfg.generate_128bit_trace_ids = !falsy(*enabled_env); @@ -352,14 +348,6 @@ Expected finalize_config(const TracerConfig &user_config, final_config.report_hostname = value_or(env_config->report_hostname, user_config.report_hostname, false); - // Delegate Sampling - std::tie(origin, final_config.delegate_trace_sampling) = - pick(env_config->delegate_trace_sampling, - user_config.delegate_trace_sampling, false); - final_config.metadata[ConfigName::DELEGATE_SAMPLING] = - ConfigMetadata(ConfigName::DELEGATE_SAMPLING, - to_string(final_config.delegate_trace_sampling), origin); - // Tags Header Size final_config.tags_header_size = value_or( env_config->max_tags_header_size, user_config.max_tags_header_size, 512); @@ -370,7 +358,7 @@ Expected finalize_config(const TracerConfig &user_config, user_config.generate_128bit_trace_ids, true); final_config.metadata[ConfigName::GENEREATE_128BIT_TRACE_IDS] = ConfigMetadata(ConfigName::GENEREATE_128BIT_TRACE_IDS, - to_string(final_config.delegate_trace_sampling), origin); + to_string(final_config.generate_128bit_trace_ids), origin); // Integration name & version final_config.integration_name = value_or( diff --git a/test/test_span.cpp b/test/test_span.cpp index d91003d9..e12af2a4 100644 --- a/test/test_span.cpp +++ b/test/test_span.cpp @@ -860,110 +860,3 @@ TEST_CASE("128-bit trace ID injection") { REQUIRE(found != writer.items.end()); REQUIRE(found->second == "deadbeefdeadbeefcafebabecafebabe"); } - -TEST_CASE("sampling delegation injection") { - TracerConfig config; - config.service = "testsvc"; - config.logger = std::make_shared(); - config.collector = std::make_shared(); - - SECTION("configuration") { - config.delegate_trace_sampling = true; - const auto finalized = finalize_config(config); - REQUIRE(finalized); - - Tracer tracer{*finalized}; - - SECTION("delegate_trace_sampling inject header") { - auto span = tracer.create_span(); - MockDictWriter writer; - span.inject(writer); - - auto found = writer.items.find("x-datadog-delegate-trace-sampling"); - REQUIRE(found != writer.items.cend()); - REQUIRE(found->second == "delegate"); - } - - SECTION("injection option override sampling delegation configuration") { - const InjectionOptions options{/* delegate_sampling_decision=*/false}; - auto span = tracer.create_span(); - MockDictWriter writer; - span.inject(writer, options); - - REQUIRE(0 == writer.items.count("x-datadog-delegate-trace-sampling")); - } - } - - SECTION("injection options") { - const auto finalized = finalize_config(config); - REQUIRE(finalized); - - Tracer tracer{*finalized}; - MockDictWriter writer; - InjectionOptions options; - - options.delegate_sampling_decision = true; - auto span = tracer.create_span(); - span.inject(writer, options); - - auto found = writer.items.find("x-datadog-delegate-trace-sampling"); - REQUIRE(found != writer.items.cend()); - REQUIRE(found->second == "delegate"); - } - - SECTION("end-to-end") { - config.delegate_trace_sampling = true; - const auto finalized = finalize_config(config); - REQUIRE(finalized); - - Tracer tracer{*finalized}; - - auto root_span = tracer.create_span(); - - MockDictWriter writer; - root_span.inject(writer); - auto found = writer.items.find("x-datadog-delegate-trace-sampling"); - REQUIRE(found != writer.items.cend()); - REQUIRE(found->second == "delegate"); - - MockDictReader reader(writer.items); - auto sub_span = tracer.extract_span(reader); - REQUIRE(!sub_span->trace_segment().sampling_decision()); - - MockDictWriter response_writer; - sub_span->trace_segment().write_sampling_delegation_response( - response_writer); - REQUIRE(1 == - response_writer.items.count("x-datadog-trace-sampling-decision")); - - MockDictReader response_reader(response_writer.items); - SECTION("default") { - REQUIRE(root_span.read_sampling_delegation_response(response_reader)); - - // If no manual sampling override was made locally, then expect that the - // decision read above will be the one applied. - auto root_sampling_decision = - root_span.trace_segment().sampling_decision(); - REQUIRE(root_sampling_decision); - REQUIRE(root_sampling_decision->origin == - SamplingDecision::Origin::DELEGATED); - REQUIRE(root_sampling_decision->priority == - sub_span->trace_segment().sampling_decision()->priority); - } - - SECTION("manual sampling override") { - root_span.trace_segment().override_sampling_priority(-1); - REQUIRE(root_span.read_sampling_delegation_response(response_reader)); - - // If `override_sampling_priority` was called on this segment, then any - // decision read above will not replace the override. - auto root_sampling_decision = - root_span.trace_segment().sampling_decision(); - REQUIRE(root_sampling_decision); - REQUIRE(root_sampling_decision->origin == - SamplingDecision::Origin::LOCAL); - REQUIRE(root_sampling_decision->mechanism == - static_cast(SamplingMechanism::MANUAL)); - } - } -} diff --git a/test/test_tracer.cpp b/test/test_tracer.cpp index 8863505f..48dda9d1 100644 --- a/test/test_tracer.cpp +++ b/test/test_tracer.cpp @@ -1267,7 +1267,6 @@ TEST_CASE("span extraction") { CAPTURE(test_case.dd_parent_id); CAPTURE(test_case.dd_tags); - config.delegate_trace_sampling = false; std::vector extraction_styles{ PropagationStyle::DATADOG, PropagationStyle::B3, PropagationStyle::W3C}; config.extraction_styles = extraction_styles; @@ -1392,46 +1391,6 @@ TEST_CASE("span extraction") { } } } - - SECTION("inject an extracted span that delegated sampling") { - config.delegate_trace_sampling = GENERATE(true, false); - auto finalized_config = finalize_config(config); - REQUIRE(finalized_config); - Tracer tracer{*finalized_config}; - - std::unordered_map headers{ - {"x-datadog-trace-id", "123"}, - {"x-datadog-parent-id", "456"}, - {"x-datadog-sampling-priority", "2"}, - {"x-datadog-delegate-trace-sampling", "delegate"}}; - - MockDictReader reader{headers}; - auto span = tracer.extract_span(reader); - REQUIRE(span); - - if (*config.delegate_trace_sampling) { - REQUIRE(!span->trace_segment().sampling_decision()); - } else { - REQUIRE(span->trace_segment().sampling_decision()); - } - - MockDictWriter writer; - span->inject(writer); - - CAPTURE(writer.items); - if (*config.delegate_trace_sampling) { - // If sampling delegation is enabled, then expect the delegation header to - // have been injected. - auto found = writer.items.find("x-datadog-delegate-trace-sampling"); - REQUIRE(found != writer.items.end()); - REQUIRE(found->second == "delegate"); - } else { - // Even though `span` was extracted from context that requested sampling - // delegation, delegation is not enabled for this tracer, so expect that - // the delegation header was not injected. - REQUIRE(writer.items.count("x-datadog-delegate-trace-sampling") == 0); - } - } } TEST_CASE("baggage usage") { @@ -1659,406 +1618,6 @@ TEST_CASE( test_case.expected_error_prefix + test_case.tid_tag_value); } -TEST_CASE("sampling delegation extraction") { - const bool enable_sampling_delegation = GENERATE(true, false); - - CAPTURE(enable_sampling_delegation); - - const auto logger = std::make_shared(); - const auto collector = std::make_shared(); - - TracerConfig config; - config.service = "test-sampling-delegation"; - config.logger = logger; - config.collector = collector; - config.extraction_styles = {PropagationStyle::DATADOG}; - config.trace_sampler.sample_rate = 1.; - config.delegate_trace_sampling = enable_sampling_delegation; - - auto validated_config = finalize_config(config); - REQUIRE(validated_config); - - Tracer tracer(*validated_config); - - const std::unordered_map headers{ - {"x-datadog-trace-id", "17491188783264004180"}, - {"x-datadog-parent-id", "3390700340160032468"}, - {"x-datadog-sampling-priority", "-1"}, - {"x-datadog-tags", "_dd.p.tid=66718e8c00000000"}, - {"x-datadog-delegate-trace-sampling", "delegate"}, - }; - - MockDictReader propagation_reader{headers}; - const auto maybe_span = tracer.extract_span(propagation_reader); - REQUIRE(maybe_span); - - auto sampling_decision = maybe_span->trace_segment().sampling_decision(); - if (enable_sampling_delegation) { - CHECK(!sampling_decision.has_value()); - } else { - REQUIRE(sampling_decision.has_value()); - CHECK(sampling_decision->origin == SamplingDecision::Origin::EXTRACTED); - CHECK(sampling_decision->priority == int(SamplingPriority::USER_DROP)); - } -} - -TEST_CASE("_dd.is_sampling_decider") { - // This test involves three tracers: "service1", "service2", and "service3". - // Each calls the next, and each produces two spans: "local_root" and "child". - // - // [service1] -> [service2] -> [service3] - // delegate delegate either - // - // Sampling delegation is enabled for service1 and for service2. - // Regardless of whether sampling delegation is enabled for service3, the - // following are expected: - // - // - service1's local root span will contain the tag - // "_dd.is_sampling_decider:0", because while it is the root span, it did - // not make the sampling decision. - // - service2's local root span will not contain the "dd_.is_sampling_decider" - // tag, because it did not make the sampling decision and was not the root - // span. - // - service3's local root span will contain the tag "_dd.sampling_decider:1", - // because regardless of whether sampling delegation was enabled for it, it - // made the sampling decision, and it is not the root span. - // - any span that is not a local root span will not contain the tag - // "_dd.is_sampling_decider", because that tag is only ever set on the local - // root span if it is set at all. - // - // Further, if we configure service3 to keep all of its traces, then the - // sampling decision conveyed by all of service1, service2, and service3 will - // be "keep" due to "rule". - bool service3_delegation_enabled = GENERATE(true, false); - - const auto collector = std::make_shared(); - const auto logger = std::make_shared(); - - TracerConfig config1; - config1.collector = collector; - config1.logger = logger; - config1.service = "service1"; - config1.delegate_trace_sampling = true; - - TracerConfig config2; - config2.collector = collector; - config2.logger = logger; - config2.service = "service2"; - config2.trace_sampler.sample_rate = 1; // keep all traces - config2.delegate_trace_sampling = true; - - TracerConfig config3; - config3.collector = collector; - config3.logger = logger; - config3.service = "service3"; - config3.delegate_trace_sampling = service3_delegation_enabled; - config3.trace_sampler.sample_rate = 1; // keep all traces - CAPTURE(config3.delegate_trace_sampling); - - auto valid_config = finalize_config(config1); - REQUIRE(valid_config); - Tracer tracer1{*valid_config}; - - valid_config = finalize_config(config2); - REQUIRE(valid_config); - Tracer tracer2{*valid_config}; - - valid_config = finalize_config(config3); - REQUIRE(valid_config); - Tracer tracer3{*valid_config}; - - // The spans will communicate forwards using the propagation writer and - // reader (trace context propagation). - MockDictWriter propagation_writer; - MockDictReader propagation_reader{propagation_writer.items}; - - // The spans will communicate backwards using the delegation writer and reader - // (delegation responses). - MockDictWriter delegation_writer; - MockDictReader delegation_reader{delegation_writer.items}; - - // The following nested blocks provide scopes for each of the services. - // service1.local_root: - { - SpanConfig span_config; - span_config.name = "local_root"; - Span global_root = tracer1.create_span(span_config); - - { // service1.child - span_config.name = "child"; - Span service1_child = global_root.create_child(span_config); - - service1_child.inject(propagation_writer); - - { // service2.local_root: - span_config.name = "local_root"; - Expected service2_local_root = - tracer2.extract_span(propagation_reader, span_config); - REQUIRE(service2_local_root); - { // service2.child: - span_config.name = "child"; - Span service2_child = service2_local_root->create_child(span_config); - - propagation_writer.items.clear(); - service2_child.inject(propagation_writer); - - { // service3.local_root: - span_config.name = "local_root"; - Expected service3_local_root = - tracer3.extract_span(propagation_reader, span_config); - REQUIRE(service3_local_root); - - { // service3.child: - span_config.name = "child"; - Span service3_child = - service3_local_root->create_child(span_config); - } - service3_local_root->trace_segment() - .write_sampling_delegation_response(delegation_writer); - } - - service2_child.read_sampling_delegation_response(delegation_reader); - } - delegation_writer.items.clear(); - service2_local_root->trace_segment().write_sampling_delegation_response( - delegation_writer); - } - service1_child.read_sampling_delegation_response(delegation_reader); - } - delegation_writer.items.clear(); - global_root.trace_segment().write_sampling_delegation_response( - delegation_writer); - } - - // service1 (the root service) was the most recent thing to - // `write_sampling_delegation_response`, and service1 has no delegation - // response to deliver, so expect that there are no corresponding response - // headers. - { - CAPTURE(delegation_writer.items); - REQUIRE(delegation_writer.items.empty()); - } - - // three segments, each having two spans - REQUIRE(collector->span_count() == 3 * 2); - - const double expected_sampling_priority = double(SamplingPriority::USER_KEEP); - // "dm" as in the "_dd.p.dm" tag - const std::string expected_dm = - "-" + std::to_string(int(SamplingMechanism::RULE)); - - // Check everything described in the comment at the top of this `TEST_CASE`. - for (const auto& chunk : collector->chunks) { - for (const auto& span_ptr : chunk) { - REQUIRE(span_ptr); - const SpanData& span = *span_ptr; - CAPTURE(span.service); - CAPTURE(span.name); - CAPTURE(span.tags); - CAPTURE(span.numeric_tags); - - if (span.service == "service1" && span.name == "local_root") { - REQUIRE(span.tags.count(tags::internal::sampling_decider) == 1); - REQUIRE(span.tags.at(tags::internal::sampling_decider) == "0"); - REQUIRE(span.numeric_tags.count(tags::internal::sampling_priority) == - 1); - REQUIRE(span.numeric_tags.at(tags::internal::sampling_priority) == - expected_sampling_priority); - REQUIRE(span.tags.count(tags::internal::decision_maker) == 1); - REQUIRE(span.tags.at(tags::internal::decision_maker) == expected_dm); - continue; - } - if (span.service == "service1" && span.name == "child") { - REQUIRE(span.tags.count(tags::internal::sampling_decider) == 0); - continue; - } - REQUIRE(span.service != "service1"); - if (span.service == "service2" && span.name == "local_root") { - const size_t made_the_decision = service3_delegation_enabled ? 0 : 1; - REQUIRE(span.tags.count(tags::internal::sampling_decider) == - made_the_decision); - REQUIRE(span.numeric_tags.count(tags::internal::sampling_priority) == - 1); - REQUIRE(span.numeric_tags.at(tags::internal::sampling_priority) == - expected_sampling_priority); - REQUIRE(span.tags.count(tags::internal::decision_maker) == 1); - REQUIRE(span.tags.at(tags::internal::decision_maker) == expected_dm); - continue; - } - if (span.service == "service2" && span.name == "child") { - REQUIRE(span.tags.count(tags::internal::sampling_decider) == 0); - continue; - } - REQUIRE(span.service != "service2"); - if (span.service == "service3" && span.name == "local_root") { - const size_t made_the_decision = service3_delegation_enabled ? 1 : 0; - REQUIRE(span.tags.count(tags::internal::sampling_decider) == - made_the_decision); - REQUIRE(span.numeric_tags.count(tags::internal::sampling_priority) == - 1); - REQUIRE(span.numeric_tags.at(tags::internal::sampling_priority) == - expected_sampling_priority); - REQUIRE(span.tags.count(tags::internal::decision_maker) == 1); - REQUIRE(span.tags.at(tags::internal::decision_maker) == expected_dm); - continue; - } - if (span.service == "service3" && span.name == "child") { - REQUIRE(span.tags.count(tags::internal::sampling_decider) == 0); - continue; - } - REQUIRE(span.service != "service3"); - } - } -} - -TEST_CASE("sampling delegation is not an override") { - // Verify that sampling delegation does not occur, even if so configured, - // when a sampling decision is extracted from an incoming request _and_ - // sampling delegation was not indicated in that request. - // We want to make sure that a mid-trace tracer configured to delegate - // sampling does not "break the trace," i.e. change the sampling decision - // mid-trace. - // - // This test involves three tracers: "service1", "service2", and "service3". - // Each calls the next, and each produces one span: "local_root". - // - // [service1] -> [service2] -> [service3] - // keep/drop/neither keep/drop - // delegate? delegate - // - - // There are three variables: - // - // 1. the injected sampling decision from service1, if any, - // 2. the configured sampling decision for service3, - // 3. whether service1 is configured to delegate. - // - // When service1 is configured to delegate, the sampling decision of all - // three services should be consistent with that made by service3. - // - // When service1 is configured _not_ to delegate, and when it injects a - // sampling decision, then the sampling decision of all three services should - // be consistent with that made by service1. - // - // When service1 is configured _not_ to delegate, and when it does _not_ - // inject a sampling decision, then the sampling decision of all three - // services should be consistent with that made by service3. - // - // The idea is that service2 does not perform delegation when service1 already - // made a decision and did not request delegation. - auto service1_delegate = GENERATE(true, false); - auto service3_sample_rate = GENERATE(0.0, 1.0); - - const int service3_sampling_priority = service3_sample_rate == 0.0 ? -1 : 2; - - CAPTURE(service1_delegate); - CAPTURE(service3_sample_rate); - - const auto collector = std::make_shared(); - const auto logger = std::make_shared(); - const std::vector styles = {PropagationStyle::DATADOG}; - - TracerConfig config1; - config1.collector = collector; - config1.logger = logger; - config1.extraction_styles = config1.injection_styles = styles; - config1.service = "service1"; - config1.delegate_trace_sampling = service1_delegate; - config1.trace_sampler.sample_rate = 1.0; // as a default - // `service1_sampling_priority` will be dealt with when service1 injects trace - // context. - - TracerConfig config2; - config2.collector = collector; - config2.logger = logger; - config2.extraction_styles = config1.injection_styles = styles; - config2.service = "service2"; - config2.delegate_trace_sampling = true; - - TracerConfig config3; - config3.collector = collector; - config3.logger = logger; - config3.extraction_styles = config1.injection_styles = styles; - config3.service = "service3"; - config3.delegate_trace_sampling = true; - config3.trace_sampler.sample_rate = service3_sample_rate; - - auto valid_config = finalize_config(config1); - REQUIRE(valid_config); - Tracer tracer1{*valid_config}; - - valid_config = finalize_config(config2); - REQUIRE(valid_config); - Tracer tracer2{*valid_config}; - - valid_config = finalize_config(config3); - Tracer tracer3{*valid_config}; - - // The spans will communicate forwards using the propagation writer and - // reader (trace context propagation). - MockDictWriter propagation_writer; - MockDictReader propagation_reader{propagation_writer.items}; - - // The spans will communicate backwards using the delegation writer and reader - // (delegation responses). - MockDictWriter delegation_writer; - MockDictReader delegation_reader{delegation_writer.items}; - - { - SpanConfig span_config; - span_config.name = "local_root"; - Span span1 = tracer1.create_span(span_config); - span1.inject(propagation_writer); - - { - auto span2 = tracer2.extract_span(propagation_reader, span_config); - REQUIRE(span2); - propagation_writer.items.clear(); - span2->inject(propagation_writer); - const size_t expected_delegate_header = service1_delegate ? 1 : 0; - CHECK( - propagation_writer.items.count("x-datadog-delegate-trace-sampling") == - expected_delegate_header); - - { - auto span3 = tracer3.extract_span(propagation_reader, span_config); - REQUIRE(span3); - span3->trace_segment().write_sampling_delegation_response( - delegation_writer); - } - - span2->trace_segment().read_sampling_delegation_response( - delegation_reader); - delegation_writer.items.clear(); - span2->trace_segment().write_sampling_delegation_response( - delegation_writer); - } - - span1.trace_segment().read_sampling_delegation_response(delegation_reader); - } - - // Verify that we received three spans, and that they have the expected - // sampling priorities. - REQUIRE(collector->span_count() == 3); - for (const auto& chunk : collector->chunks) { - for (const auto& span_ptr : chunk) { - REQUIRE(span_ptr); - const SpanData& span = *span_ptr; - CAPTURE(span.service); - REQUIRE(span.numeric_tags.count(tags::internal::sampling_priority) == 1); - // If `service1_delegate` is false, then service1's sampling decision was - // made by the service1's sampler, which will result in priority 2. - // Otherwise, it's the same priority expected for the other spans. - if (!service1_delegate) { - REQUIRE(span.numeric_tags.at(tags::internal::sampling_priority) == 2); - } else { - REQUIRE(span.numeric_tags.at(tags::internal::sampling_priority) == - service3_sampling_priority); - } - } - } -} - TEST_CASE("heterogeneous extraction") { // These test cases verify that when W3C is among the configured extraction // styles, then non-Datadog and unexpected Datadog fields in an incoming diff --git a/test/test_tracer_config.cpp b/test/test_tracer_config.cpp index c8056e74..197ab9df 100644 --- a/test/test_tracer_config.cpp +++ b/test/test_tracer_config.cpp @@ -144,25 +144,6 @@ TRACER_CONFIG_TEST("TracerConfig::defaults") { REQUIRE(finalized->defaults.version == "v2"); } - SECTION("DD_TRACE_DELEGATE_SAMPLING") { - SECTION("is disabled by default") { - config.version = "v1"; - config.service = "required"; - auto finalized = finalize_config(config); - REQUIRE(finalized); - REQUIRE(finalized->delegate_trace_sampling == false); - } - - SECTION("setting is overridden by environment variable") { - const EnvGuard guard{"DD_TRACE_DELEGATE_SAMPLING", "1"}; - config.version = "v1"; - config.service = "required"; - auto finalized = finalize_config(config); - REQUIRE(finalized); - REQUIRE(finalized->delegate_trace_sampling == true); - } - } - SECTION("DD_TAGS") { struct TestCase { std::string name;