Skip to content

Commit

Permalink
Feat/dynatrace sampler internal (#11)
Browse files Browse the repository at this point in the history
Signed-off-by: thomas.ebner <[email protected]>

Signed-off-by: Joao Grassi <[email protected]>
  • Loading branch information
samohte authored and joaopgrassi committed Feb 22, 2024
1 parent c82c083 commit f1fddbc
Show file tree
Hide file tree
Showing 18 changed files with 912 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
syntax = "proto3";

package envoy.extensions.tracers.opentelemetry.samplers.v3;

import "udpa/annotations/status.proto";

option java_package = "io.envoyproxy.envoy.extensions.tracers.opentelemetry.samplers.v3";
option java_outer_classname = "DynatraceSamplerProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/tracers/opentelemetry/samplers/v3;samplersv3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: Dynatrace Sampler config]
// [#extension: envoy.tracers.opentelemetry.samplers.dynatrace]

message DynatraceSamplerConfig {
string tenant_id = 1;

string cluster_id = 2;
}
1 change: 1 addition & 0 deletions source/extensions/extensions_build_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ EXTENSIONS = {
#

"envoy.tracers.opentelemetry.samplers.always_on": "//source/extensions/tracers/opentelemetry/samplers/always_on:config",
"envoy.tracers.opentelemetry.samplers.dynatrace": "//source/extensions/tracers/opentelemetry/samplers/dynatrace:config",

#
# Transport sockets
Expand Down
7 changes: 7 additions & 0 deletions source/extensions/extensions_metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1180,6 +1180,13 @@ envoy.tracers.opentelemetry.samplers.always_on:
status: wip
type_urls:
- envoy.extensions.tracers.opentelemetry.samplers.v3.AlwaysOnSamplerConfig
envoy.tracers.opentelemetry.samplers.dynatrace:
categories:
- envoy.tracers.opentelemetry.samplers
security_posture: unknown
status: wip
type_urls:
- envoy.extensions.tracers.opentelemetry.samplers.v3.DynatraceSamplerConfig
envoy.tracers.skywalking:
categories:
- envoy.tracers
Expand Down
42 changes: 42 additions & 0 deletions source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_extension",
"envoy_cc_library",
"envoy_extension_package",
)

licenses(["notice"]) # Apache 2

envoy_extension_package()

envoy_cc_extension(
name = "config",
srcs = ["config.cc"],
hdrs = ["config.h"],
deps = [
":dynatrace_sampler_lib",
"//envoy/registry",
"//source/common/config:utility_lib",
"@envoy_api//envoy/extensions/tracers/opentelemetry/samplers/v3:pkg_cc_proto",
],
)

envoy_cc_library(
name = "dynatrace_sampler_lib",
srcs = [
"dynatrace_sampler.cc",
"dynatrace_tracestate.cc",
"tracestate.cc",
],
hdrs = [
"dynatrace_sampler.h",
"dynatrace_tracestate.h",
"tracestate.h",
],
deps = [
"//source/common/config:datasource_lib",
"//source/extensions/tracers/opentelemetry:opentelemetry_tracer_lib",
"//source/extensions/tracers/opentelemetry/samplers:sampler_lib",
"@envoy_api//envoy/extensions/tracers/opentelemetry/samplers/v3:pkg_cc_proto",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include "config.h"

#include "envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.pb.validate.h"

#include "source/common/config/utility.h"
#include "source/common/protobuf/utility.h"
#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h"

namespace Envoy {
namespace Extensions {
namespace Tracers {
namespace OpenTelemetry {

SamplerSharedPtr
DynatraceSamplerFactory::createSampler(const Protobuf::Message& config,
Server::Configuration::TracerFactoryContext& context) {
auto mptr = Envoy::Config::Utility::translateAnyToFactoryConfig(
dynamic_cast<const ProtobufWkt::Any&>(config), context.messageValidationVisitor(), *this);
return std::make_shared<DynatraceSampler>(
MessageUtil::downcastAndValidate<
const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig&>(
*mptr, context.messageValidationVisitor()),
context);
}

/**
* Static registration for the Env sampler factory. @see RegisterFactory.
*/
REGISTER_FACTORY(DynatraceSamplerFactory, SamplerFactory);

} // namespace OpenTelemetry
} // namespace Tracers
} // namespace Extensions
} // namespace Envoy
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#include <string>

#include "envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.pb.h"
#include "envoy/registry/registry.h"

#include "source/extensions/tracers/opentelemetry/samplers/sampler.h"

namespace Envoy {
namespace Extensions {
namespace Tracers {
namespace OpenTelemetry {

/**
* Config registration for the DynatraceSampler. @see SamplerFactory.
*/
class DynatraceSamplerFactory : public SamplerFactory {
public:
/**
* @brief Create a Sampler which samples every span
*
* @param context
* @return SamplerSharedPtr
*/
SamplerSharedPtr createSampler(const Protobuf::Message& config,
Server::Configuration::TracerFactoryContext& context) override;

ProtobufTypes::MessagePtr createEmptyConfigProto() override {
return std::make_unique<
envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig>();
}
std::string name() const override { return "envoy.tracers.opentelemetry.samplers.dynatrace"; }
};

DECLARE_FACTORY(DynatraceSamplerFactory);

} // namespace OpenTelemetry
} // namespace Tracers
} // namespace Extensions
} // namespace Envoy
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h"

#include <memory>
#include <sstream>
#include <string>

#include "source/common/config/datasource.h"
#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h"
#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/tracestate.h"
#include "source/extensions/tracers/opentelemetry/samplers/sampler.h"
#include "source/extensions/tracers/opentelemetry/span_context.h"

namespace Envoy {
namespace Extensions {
namespace Tracers {
namespace OpenTelemetry {

static const char* SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME =
"sampling_extrapolation_set_in_sampler";

FW4Tag DynatraceSampler::getFW4Tag(const Tracestate& tracestate) {
for (auto const& entry : tracestate.entries()) {
if (dt_tracestate_entry_.keyMatches(
entry.key)) { // found a tracestate entry with key matching our tenant/cluster
return FW4Tag::create(entry.value);
}
}
return FW4Tag::createInvalid();
}

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_entry_(tenant_id_, cluster_id_), counter_(0) {}

SamplingResult DynatraceSampler::shouldSample(const absl::optional<SpanContext> parent_context,
const std::string& /*trace_id*/,
const std::string& /*name*/, OTelSpanKind /*kind*/,
OptRef<const Tracing::TraceContext> /*trace_context*/,
const std::vector<SpanContext>& /*links*/) {

SamplingResult result;
std::map<std::string, std::string> att;
// search for an existing forward tag in the tracestate
Tracestate tracestate;
tracestate.parse(parent_context.has_value() ? parent_context->tracestate() : "");

if (FW4Tag fw4_tag = getFW4Tag(tracestate);
fw4_tag.isValid()) { // we found a trace decision in tracestate header
result.decision = fw4_tag.isIgnored() ? Decision::DROP : Decision::RECORD_AND_SAMPLE;
att[SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME] = std::to_string(fw4_tag.getSamplingExponent());
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_;
bool sample;
int sampling_exponent;
if (current_counter % 2) {
sample = true;
sampling_exponent = 1;
} else {
sample = false;
sampling_exponent = 0;
}

att[SAMPLING_EXTRAPOLATION_SPAN_ATTRIBUTE_NAME] = std::to_string(sampling_exponent);

result.decision = sample ? Decision::RECORD_AND_SAMPLE : Decision::DROP;
// create new forward tag and add it to tracestate
FW4Tag new_tag = FW4Tag::create(!sample, sampling_exponent);
tracestate.add(dt_tracestate_entry_.getKey(), new_tag.asString());
result.tracestate = tracestate.asString();
}

if (!att.empty()) {
result.attributes = std::make_unique<const std::map<std::string, std::string>>(std::move(att));
}
return result;
}

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

} // namespace OpenTelemetry
} // namespace Tracers
} // namespace Extensions
} // namespace Envoy
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#pragma once

#include "envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.pb.h"
#include "envoy/server/factory_context.h"

#include "source/common/common/logger.h"
#include "source/common/config/datasource.h"
#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h"
#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/tracestate.h"
#include "source/extensions/tracers/opentelemetry/samplers/sampler.h"

namespace Envoy {
namespace Extensions {
namespace Tracers {
namespace OpenTelemetry {

/**
* @brief A Dynatrace specific sampler *
*/
class DynatraceSampler : public Sampler, Logger::Loggable<Logger::Id::tracing> {
public:
DynatraceSampler(
const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig& config,
Server::Configuration::TracerFactoryContext& context);

SamplingResult shouldSample(const absl::optional<SpanContext> parent_context,
const std::string& trace_id, const std::string& name,
OTelSpanKind spankind,
OptRef<const Tracing::TraceContext> trace_context,
const std::vector<SpanContext>& links) override;

std::string getDescription() const override;

private:
std::string tenant_id_;
std::string cluster_id_;
DtTracestateEntry dt_tracestate_entry_;
std::atomic<uint32_t> counter_; // request counter for dummy sampling
FW4Tag getFW4Tag(const Tracestate& tracestate);
};

} // namespace OpenTelemetry
} // namespace Tracers
} // namespace Extensions
} // namespace Envoy
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_tracestate.h"

#include <string>
#include <vector>

#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}; }

FW4Tag FW4Tag::create(bool ignored, int sampling_exponent) {
return {true, ignored, sampling_exponent};
}

FW4Tag FW4Tag::create(const std::string& value) {
std::vector<absl::string_view> tracestate_components =
absl::StrSplit(value, ';', absl::AllowEmpty());
if (tracestate_components.size() < 7) {
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]));
return {true, ignored, sampling_exponent};
}

std::string FW4Tag::asString() const {
std::string ret =
absl::StrCat("fw4;0;0;0;0;", ignored_ ? "1" : "0", ";", sampling_exponent_, ";0");
return ret;
}

} // namespace OpenTelemetry
} // namespace Tracers
} // namespace Extensions
} // namespace Envoy
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#pragma once

#include <string>

#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);

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)
: valid_(valid), ignored_(ignored), sampling_exponent_(sampling_exponent) {}

bool valid_;
bool ignored_;
int sampling_exponent_;
};

// <tenantID>-<clusterID>@dt=fw4;0;0;0;0;<isIgnored>;<sampling_exponent>;<rootPathRandom>
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
Loading

0 comments on commit f1fddbc

Please sign in to comment.