1
#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h"
2

            
3
#include <chrono>
4
#include <memory>
5
#include <string>
6

            
7
#include "source/common/common/hash.h"
8
#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tag.h"
9
#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id.h"
10
#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/trace_capture_reason.h"
11
#include "source/extensions/tracers/opentelemetry/samplers/sampler.h"
12
#include "source/extensions/tracers/opentelemetry/span_context.h"
13

            
14
#include "absl/strings/str_cat.h"
15
#include "opentelemetry/trace/trace_state.h"
16

            
17
namespace Envoy {
18
namespace Extensions {
19
namespace Tracers {
20
namespace OpenTelemetry {
21

            
22
namespace {
23

            
24
constexpr std::chrono::minutes SAMPLING_UPDATE_TIMER_DURATION{1};
25

            
26
// add Dynatrace specific span attributes
27
1390
void addSamplingAttributes(OtelAttributes& attributes, const DynatraceTag& dynatrace_tag) {
28

            
29
1390
  const auto multiplicity = SamplingState::toMultiplicity(dynatrace_tag.getSamplingExponent());
30
  // The denominator of the sampling ratio. If, for example, the Dynatrace OneAgent samples with a
31
  // probability of 1/16, the value of supportability sampling ratio would be 16.
32
  // Note: Ratio is also known as multiplicity.
33
1390
  attributes["supportability.atm_sampling_ratio"] = multiplicity;
34

            
35
1390
  if (multiplicity > 1) {
36
1166
    static constexpr uint64_t two_pow_56 = 1llu << 56; // 2^56
37
    // The sampling probability can be interpreted as the number of spans
38
    // that are discarded out of 2^56. The attribute is only available if the sampling.threshold is
39
    // not 0 and therefore sampling happened.
40
1166
    const uint64_t sampling_threshold = two_pow_56 - two_pow_56 / multiplicity;
41
1166
    attributes["sampling.threshold"] = sampling_threshold;
42
1166
  }
43

            
44
1390
  auto tcr = dynatrace_tag.getTcrExtension();
45

            
46
1390
  if (tcr && tcr->isValid()) {
47
1386
    auto span_attribute_value = tcr->toSpanAttributeValue();
48
1386
    attributes["trace.capture.reasons"] = span_attribute_value;
49
1386
  }
50
1390
}
51

            
52
} // namespace
53

            
54
DynatraceSampler::DynatraceSampler(
55
    const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig& config,
56
    Server::Configuration::TracerFactoryContext& context,
57
    SamplerConfigProviderPtr sampler_config_provider)
58
22
    : dt_tracestate_key_(absl::StrCat(calculateTenantId(config.tenant()), "-",
59
22
                                      absl::Hex(config.cluster_id()), "@dt")),
60
22
      sampling_controller_(std::move(sampler_config_provider)) {
61

            
62
  // start a timer to periodically recalculate the sampling exponents
63
22
  timer_ = context.serverFactoryContext().mainThreadDispatcher().createTimer([this]() -> void {
64
1
    sampling_controller_.update();
65
1
    timer_->enableTimer(SAMPLING_UPDATE_TIMER_DURATION);
66
1
  });
67
22
  timer_->enableTimer(SAMPLING_UPDATE_TIMER_DURATION);
68
22
}
69

            
70
SamplingResult DynatraceSampler::shouldSample(const StreamInfo::StreamInfo&,
71
                                              const absl::optional<SpanContext> parent_context,
72
                                              const std::string& trace_id,
73
                                              const std::string& /*name*/, OTelSpanKind /*kind*/,
74
                                              OptRef<const Tracing::TraceContext> trace_context,
75
1390
                                              const std::vector<SpanContext>& /*links*/) {
76

            
77
1390
  SamplingResult result;
78
1390
  OtelAttributes att;
79

            
80
  // trace_context->path() returns path and query. query part is removed in getSamplingKey()
81
1390
  const std::string sampling_key =
82
1390
      trace_context.has_value()
83
1390
          ? sampling_controller_.getSamplingKey(trace_context->path(), trace_context->method())
84
1390
          : "";
85

            
86
1390
  sampling_controller_.offer(sampling_key);
87

            
88
1390
  auto trace_state = opentelemetry::trace::TraceState::FromHeader(
89
1390
      parent_context.has_value() ? parent_context->tracestate() : "");
90

            
91
1390
  std::string trace_state_value;
92
1390
  bool is_root_span = true;
93

            
94
1390
  if (trace_state->Get(dt_tracestate_key_, trace_state_value)) {
95
    // we found a Dynatrace tag in the tracestate header. Respect the sampling decision in the tag.
96
11
    if (DynatraceTag dynatrace_tag = DynatraceTag::create(trace_state_value);
97
11
        dynatrace_tag.isValid()) {
98
7
      result.decision = dynatrace_tag.isIgnored() ? Decision::Drop : Decision::RecordAndSample;
99
7
      addSamplingAttributes(att, dynatrace_tag);
100
7
      result.tracestate = parent_context->tracestate();
101
7
      is_root_span = false;
102
7
    }
103
11
  }
104

            
105
1390
  if (is_root_span) {
106
    // do a decision based on the calculated exponent
107
    // we use a hash of the trace_id as random number
108
1383
    const auto hash = MurmurHash::murmurHash2(trace_id);
109
1383
    const auto sampling_state = sampling_controller_.getSamplingState(sampling_key);
110
1383
    const bool sample = sampling_state.shouldSample(hash);
111
1383
    const auto sampling_exponent = sampling_state.getExponent();
112

            
113
1383
    result.decision = sample ? Decision::RecordAndSample : Decision::Drop;
114

            
115
    // create a new Dynatrace tag and add it to tracestate
116
1383
    DynatraceTag new_tag =
117
1383
        DynatraceTag::create(!sample, sampling_exponent, static_cast<uint8_t>(hash),
118
1383
                             TraceCaptureReason::create(TraceCaptureReason::Reason::Atm));
119

            
120
1383
    trace_state = trace_state->Set(dt_tracestate_key_, new_tag.asString());
121
1383
    result.tracestate = trace_state->ToHeader();
122

            
123
1383
    addSamplingAttributes(att, new_tag);
124
1383
  }
125

            
126
1390
  if (!att.empty()) {
127
1390
    result.attributes = std::make_unique<const OtelAttributes>(std::move(att));
128
1390
  }
129

            
130
1390
  return result;
131
1390
}
132

            
133
1
std::string DynatraceSampler::getDescription() const { return "DynatraceSampler"; }
134

            
135
} // namespace OpenTelemetry
136
} // namespace Tracers
137
} // namespace Extensions
138
} // namespace Envoy