From c4fe01c44cb75bceb0342a79e20375674557563c Mon Sep 17 00:00:00 2001 From: Thomas Ebner <96168670+samohte@users.noreply.github.com> Date: Thu, 21 Mar 2024 06:48:32 +0100 Subject: [PATCH] opentelemetrytracer: Dynatrace sampler: Enable adaptative sampling (#32848) * opentelemetrytracer: Dynatrace sampler to fetch configuration from an API (#21) * Dynatrace sampler to fetch configuration from an API Signed-off-by: Thomas Ebner <96168670+samohte@users.noreply.github.com> Co-authored-by: Joao Grassi <5938087+joaopgrassi@users.noreply.github.com> * review feedback: add log msg, remove timeout adjustment Signed-off-by: thomas.ebner --------- Signed-off-by: Thomas Ebner <96168670+samohte@users.noreply.github.com> Signed-off-by: thomas.ebner Co-authored-by: Joao Grassi <5938087+joaopgrassi@users.noreply.github.com> --- .../samplers/v3/dynatrace_sampler.proto | 2 +- changelogs/current.yaml | 3 + .../samplers/dynatrace/sampler_config.cc | 5 +- .../samplers/dynatrace/sampler_config.h | 8 +- .../dynatrace/sampler_config_provider.cc | 88 ++++++++- .../dynatrace/sampler_config_provider.h | 19 +- .../dynatrace/sampler_config_provider_test.cc | 181 +++++++++++++++++- .../samplers/dynatrace/sampler_config_test.cc | 20 +- 8 files changed, 304 insertions(+), 22 deletions(-) diff --git a/api/envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto b/api/envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto index b74e96a6a416..e4d64e24eb07 100644 --- a/api/envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto +++ b/api/envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto @@ -33,7 +33,7 @@ message DynatraceSamplerConfig { // .. code-block:: yaml // // http_uri: - // uri: .dev.dynatracelabs.com/api/v2/otlp/v1/traces + // uri: .dev.dynatracelabs.com/api/v2/samplingConfiguration // cluster: dynatrace // timeout: 10s // diff --git a/changelogs/current.yaml b/changelogs/current.yaml index c36030eaa1c2..a19ed1acdae7 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -365,6 +365,9 @@ new_features: change: | Added :ref:`rules_stat_prefix ` to allow adding custom prefix to the stats emitted by rules. +- area: tracing + change: | + Dynatrace sampler fetches configuration from Dynatrace API. deprecated: - area: listener diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.cc index b3a6f2b91c45..eee907c002f4 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.cc @@ -8,18 +8,19 @@ namespace Extensions { namespace Tracers { namespace OpenTelemetry { -void SamplerConfig::parse(const std::string& json) { +bool SamplerConfig::parse(const std::string& json) { const auto result = Envoy::Json::Factory::loadFromStringNoThrow(json); if (result.ok()) { const auto& obj = result.value(); if (obj->hasObject("rootSpansPerMinute")) { const auto value = obj->getInteger("rootSpansPerMinute", default_root_spans_per_minute_); root_spans_per_minute_.store(value); - return; + return true; } } // Didn't get a value, reset to default root_spans_per_minute_.store(default_root_spans_per_minute_); + return false; } } // namespace OpenTelemetry diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h index 6f576b2d5afb..3763de3f7a25 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h @@ -22,12 +22,18 @@ class SamplerConfig { ? default_root_spans_per_minute : ROOT_SPANS_PER_MINUTE_DEFAULT), root_spans_per_minute_(default_root_spans_per_minute_) {} + + SamplerConfig(const SamplerConfig&) = delete; + SamplerConfig& operator=(const SamplerConfig&) = delete; + /** * @brief Parses a json string containing the expected root spans per minute. * * @param json A string containing the configuration. + * + * @return true if parsing was successful, false otherwise */ - void parse(const std::string& json); + bool parse(const std::string& json); /** * @brief Returns wanted root spans per minute diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.cc index 465cb9e1bd5f..badc4458c131 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.cc +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.cc @@ -1,5 +1,7 @@ #include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.h" +#include + #include "source/common/common/enum_to_int.h" #include "source/common/http/utility.h" @@ -8,10 +10,92 @@ namespace Extensions { namespace Tracers { namespace OpenTelemetry { +static constexpr std::chrono::seconds INITIAL_TIMER_DURATION{10}; +static constexpr std::chrono::minutes TIMER_INTERVAL{5}; + +namespace { + +bool reEnableTimer(Http::Code response_code) { + switch (response_code) { + case Http::Code::OK: + case Http::Code::TooManyRequests: + case Http::Code::InternalServerError: + case Http::Code::BadGateway: + case Http::Code::ServiceUnavailable: + case Http::Code::GatewayTimeout: + return true; + default: + return false; + } +} + +} // namespace + SamplerConfigProviderImpl::SamplerConfigProviderImpl( - Server::Configuration::TracerFactoryContext& /*context*/, + Server::Configuration::TracerFactoryContext& context, const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig& config) - : sampler_config_(config.root_spans_per_minute()) {} + : cluster_manager_(context.serverFactoryContext().clusterManager()), + http_uri_(config.http_uri()), + authorization_header_value_(absl::StrCat("Api-Token ", config.token())), + sampler_config_(config.root_spans_per_minute()) { + + timer_ = context.serverFactoryContext().mainThreadDispatcher().createTimer([this]() -> void { + const auto thread_local_cluster = cluster_manager_.getThreadLocalCluster(http_uri_.cluster()); + if (thread_local_cluster == nullptr) { + ENVOY_LOG(error, "SamplerConfigProviderImpl failed: [cluster = {}] is not configured", + http_uri_.cluster()); + } else { + Http::RequestMessagePtr message = Http::Utility::prepareHeaders(http_uri_); + message->headers().setReferenceMethod(Http::Headers::get().MethodValues.Get); + message->headers().setReference(Http::CustomHeaders::get().Authorization, + authorization_header_value_); + active_request_ = thread_local_cluster->httpAsyncClient().send( + std::move(message), *this, + Http::AsyncClient::RequestOptions().setTimeout(std::chrono::milliseconds( + DurationUtil::durationToMilliseconds(http_uri_.timeout())))); + } + }); + + timer_->enableTimer(std::chrono::seconds(INITIAL_TIMER_DURATION)); +} + +SamplerConfigProviderImpl::~SamplerConfigProviderImpl() { + if (active_request_) { + active_request_->cancel(); + } +} + +void SamplerConfigProviderImpl::onSuccess(const Http::AsyncClient::Request& /*request*/, + Http::ResponseMessagePtr&& http_response) { + active_request_ = nullptr; + const auto response_code = Http::Utility::getResponseStatus(http_response->headers()); + bool json_valid = false; + if (response_code == enumToInt(Http::Code::OK)) { + ENVOY_LOG(debug, "Received sampling configuration from Dynatrace: {}", + http_response->bodyAsString()); + json_valid = sampler_config_.parse(http_response->bodyAsString()); + if (!json_valid) { + ENVOY_LOG(warn, "Failed to parse sampling configuration received from Dynatrace: {}", + http_response->bodyAsString()); + } + } else { + ENVOY_LOG(warn, "Failed to get sampling configuration from Dynatrace: {}", response_code); + } + + if (json_valid || reEnableTimer(static_cast(response_code))) { + timer_->enableTimer(std::chrono::seconds(TIMER_INTERVAL)); + } else { + ENVOY_LOG(error, "Stopped to query sampling configuration from Dynatrace."); + } +} + +void SamplerConfigProviderImpl::onFailure(const Http::AsyncClient::Request& /*request*/, + Http::AsyncClient::FailureReason reason) { + active_request_ = nullptr; + timer_->enableTimer(std::chrono::seconds(TIMER_INTERVAL)); + ENVOY_LOG(warn, "Failed to get sampling configuration from Dynatrace. Reason {}", + enumToInt(reason)); +} const SamplerConfig& SamplerConfigProviderImpl::getSamplerConfig() const { return sampler_config_; } diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.h index 6dd464d4f6c7..ddb42ac5fb6a 100644 --- a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.h +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.h @@ -40,18 +40,33 @@ class SamplerConfigProvider { }; class SamplerConfigProviderImpl : public SamplerConfigProvider, - public Logger::Loggable { + public Logger::Loggable, + public Http::AsyncClient::Callbacks { public: SamplerConfigProviderImpl( Server::Configuration::TracerFactoryContext& context, const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig& config); + void onSuccess(const Http::AsyncClient::Request& request, + Http::ResponseMessagePtr&& response) override; + + void onFailure(const Http::AsyncClient::Request& request, + Http::AsyncClient::FailureReason reason) override; + + void onBeforeFinalizeUpstreamSpan(Envoy::Tracing::Span& /*span*/, + const Http::ResponseHeaderMap* /*response_headers*/) override{}; + const SamplerConfig& getSamplerConfig() const override; - ~SamplerConfigProviderImpl() override = default; + ~SamplerConfigProviderImpl() override; private: + Event::TimerPtr timer_; + Upstream::ClusterManager& cluster_manager_; + envoy::config::core::v3::HttpUri http_uri_; + const std::string authorization_header_value_; + Http::AsyncClient::Request* active_request_{}; SamplerConfig sampler_config_; }; diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider_test.cc index d5a8c68d71b8..bdc0add55955 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider_test.cc @@ -18,17 +18,190 @@ namespace Envoy { namespace Extensions { namespace Tracers { namespace OpenTelemetry { - using testing::NiceMock; using testing::Return; using testing::ReturnRef; class SamplerConfigProviderTest : public testing::Test { public: + SamplerConfigProviderTest() + : request_(&tracer_factory_context_.server_factory_context_.cluster_manager_ + .thread_local_cluster_.async_client_) { + + const std::string yaml_string = R"EOF( + tenant: "abc12345" + cluster_id: -1743916452 + token: "tokenval" + http_uri: + cluster: "cluster_name" + uri: "https://testhost.com/api/v2/samplingConfiguration" + timeout: 0.250s + root_spans_per_minute: 1000 + )EOF"; + TestUtility::loadFromYaml(yaml_string, proto_config_); + + ON_CALL(tracer_factory_context_.server_factory_context_.cluster_manager_, + getThreadLocalCluster(_)) + .WillByDefault(Return(&tracer_factory_context_.server_factory_context_.cluster_manager_ + .thread_local_cluster_)); + timer_ = new NiceMock( + &tracer_factory_context_.server_factory_context_.dispatcher_); + ON_CALL(tracer_factory_context_.server_factory_context_.dispatcher_, createTimer_(_)) + .WillByDefault(Invoke([this](Event::TimerCb) { return timer_; })); + } + protected: NiceMock tracer_factory_context_; + envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig proto_config_; + NiceMock* timer_; + Http::MockAsyncClientRequest request_; }; +MATCHER_P(MessageMatcher, unusedArg, "") { + // prefix 'Api-Token' should be added to 'tokenval' set via SamplerConfigProvider constructor + return (arg->headers() + .get(Http::CustomHeaders::get().Authorization)[0] + ->value() + .getStringView() == "Api-Token tokenval") && + (arg->headers().get(Http::Headers::get().Path)[0]->value().getStringView() == + "/api/v2/samplingConfiguration") && + (arg->headers().get(Http::Headers::get().Host)[0]->value().getStringView() == + "testhost.com") && + (arg->headers().get(Http::Headers::get().Method)[0]->value().getStringView() == "GET"); +} + +// Test that a request is sent if timer fires +TEST_F(SamplerConfigProviderTest, TestRequestIsSent) { + EXPECT_CALL(tracer_factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_ + .async_client_, + send_(MessageMatcher("unused-arg"), _, _)); + SamplerConfigProviderImpl config_provider(tracer_factory_context_, proto_config_); + timer_->invokeCallback(); +} + +// Test that a pending request is canceled +TEST_F(SamplerConfigProviderTest, TestPendingRequestIsCanceled) { + class TestRequest : public Http::AsyncClient::Request { + public: + MOCK_METHOD(void, cancel, ()); + }; + + NiceMock test_request; + EXPECT_CALL(test_request, cancel()); + ON_CALL(tracer_factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_ + .async_client_, + send_(_, _, _)) + .WillByDefault(Return(&test_request)); + SamplerConfigProviderImpl config_provider(tracer_factory_context_, proto_config_); + timer_->invokeCallback(); +} + +// Test receiving http response code 200 and valid json +TEST_F(SamplerConfigProviderTest, TestResponseOkValidJson) { + SamplerConfigProviderImpl config_provider(tracer_factory_context_, proto_config_); + timer_->invokeCallback(); + + Http::ResponseMessagePtr message(new Http::ResponseMessageImpl( + Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); + message->body().add("{\n \"rootSpansPerMinute\" : 4356 \n }"); + config_provider.onSuccess(request_, std::move(message)); + EXPECT_EQ(config_provider.getSamplerConfig().getRootSpansPerMinute(), 4356); + EXPECT_TRUE(timer_->enabled()); +} + +// Test receiving http response code 200 and invalid json +TEST_F(SamplerConfigProviderTest, TestResponseOkInvalidJson) { + SamplerConfigProviderImpl config_provider(tracer_factory_context_, proto_config_); + timer_->invokeCallback(); + + Http::ResponseMessagePtr message(new Http::ResponseMessageImpl( + Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); + message->body().add("{\n "); + config_provider.onSuccess(request_, std::move(message)); + EXPECT_EQ(config_provider.getSamplerConfig().getRootSpansPerMinute(), + SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + EXPECT_TRUE(timer_->enabled()); +} + +void doTestResponseCode(Http::Code response_code, bool timer_enabled, + SamplerConfigProviderImpl& config_provider, + Http::MockAsyncClientRequest& request, NiceMock* timer, + int line_num) { + SCOPED_TRACE(absl::StrCat(__FUNCTION__, " called from line ", line_num)); + Http::ResponseMessagePtr message(new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ + new Http::TestResponseHeaderMapImpl{{":status", std::to_string(enumToInt(response_code))}}})); + message->body().add("{\n \"rootSpansPerMinute\" : 1000 \n }"); + config_provider.onSuccess(request, std::move(message)); + EXPECT_EQ(timer->enabled(), timer_enabled); +} + +// Test that timer is re-enabled depending on the response code +TEST_F(SamplerConfigProviderTest, TestReenableTimer) { + SamplerConfigProviderImpl config_provider(tracer_factory_context_, proto_config_); + timer_->invokeCallback(); + doTestResponseCode(Http::Code::Forbidden, false, config_provider, request_, timer_, __LINE__); + doTestResponseCode(Http::Code::NotFound, false, config_provider, request_, timer_, __LINE__); + doTestResponseCode(Http::Code::OK, true, config_provider, request_, timer_, __LINE__); + timer_->invokeCallback(); + doTestResponseCode(Http::Code::TooManyRequests, true, config_provider, request_, timer_, + __LINE__); + timer_->invokeCallback(); + doTestResponseCode(Http::Code::InternalServerError, true, config_provider, request_, timer_, + __LINE__); + timer_->invokeCallback(); + doTestResponseCode(Http::Code::BadGateway, true, config_provider, request_, timer_, __LINE__); + timer_->invokeCallback(); + doTestResponseCode(Http::Code::ServiceUnavailable, true, config_provider, request_, timer_, + __LINE__); + timer_->invokeCallback(); + doTestResponseCode(Http::Code::GatewayTimeout, true, config_provider, request_, timer_, __LINE__); + timer_->invokeCallback(); +} + +// Test receiving http response code != 200 +TEST_F(SamplerConfigProviderTest, TestResponseErrorCode) { + SamplerConfigProviderImpl config_provider(tracer_factory_context_, proto_config_); + timer_->invokeCallback(); + + Http::ResponseMessagePtr message(new Http::ResponseMessageImpl( + Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "401"}}})); + message->body().add("{\n \"rootSpansPerMinute\" : 4356 \n }"); + config_provider.onSuccess(request_, std::move(message)); + EXPECT_EQ(config_provider.getSamplerConfig().getRootSpansPerMinute(), + SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + EXPECT_FALSE(timer_->enabled()); +} + +// Test sending failed +TEST_F(SamplerConfigProviderTest, TestOnFailure) { + SamplerConfigProviderImpl config_provider(tracer_factory_context_, proto_config_); + timer_->invokeCallback(); + config_provider.onFailure(request_, Http::AsyncClient::FailureReason::Reset); + EXPECT_EQ(config_provider.getSamplerConfig().getRootSpansPerMinute(), + SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + EXPECT_TRUE(timer_->enabled()); +} + +// Test calling onBeforeFinalizeUpstreamSpan +TEST_F(SamplerConfigProviderTest, TestOnBeforeFinalizeUpstreamSpan) { + Tracing::MockSpan child_span_; + SamplerConfigProviderImpl config_provider(tracer_factory_context_, proto_config_); + // onBeforeFinalizeUpstreamSpan() is an empty method, nothing to ASSERT, nothing should happen + config_provider.onBeforeFinalizeUpstreamSpan(child_span_, nullptr); +} + +// Test invoking the timer if no cluster can be found +TEST_F(SamplerConfigProviderTest, TestNoCluster) { + // simulate no configured cluster, return nullptr. + ON_CALL(tracer_factory_context_.server_factory_context_.cluster_manager_, + getThreadLocalCluster(_)) + .WillByDefault(Return(nullptr)); + SamplerConfigProviderImpl config_provider(tracer_factory_context_, proto_config_); + timer_->invokeCallback(); + // nothing to assert, should not crash or throw. +} + +// Test that configured value is used TEST_F(SamplerConfigProviderTest, TestValueConfigured) { const std::string yaml_string = R"EOF( tenant: "abc12345" @@ -39,7 +212,6 @@ TEST_F(SamplerConfigProviderTest, TestValueConfigured) { uri: "https://testhost.com/otlp/v1/traces" timeout: 0.250s root_spans_per_minute: 3456 - )EOF"; envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig proto_config; @@ -49,6 +221,7 @@ TEST_F(SamplerConfigProviderTest, TestValueConfigured) { EXPECT_EQ(config_provider.getSamplerConfig().getRootSpansPerMinute(), 3456); } +// Test using a config without a setting for configured root spans TEST_F(SamplerConfigProviderTest, TestNoValueConfigured) { const std::string yaml_string = R"EOF( tenant: "abc12345" @@ -57,8 +230,7 @@ TEST_F(SamplerConfigProviderTest, TestNoValueConfigured) { http_uri: cluster: "cluster_name" uri: "https://testhost.com/otlp/v1/traces" - timeout: 0.250s - + timeout: 500s )EOF"; envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig proto_config; @@ -69,6 +241,7 @@ TEST_F(SamplerConfigProviderTest, TestNoValueConfigured) { SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); } +// Test using a config with 0 configured root spans TEST_F(SamplerConfigProviderTest, TestValueZeroConfigured) { const std::string yaml_string = R"EOF( tenant: "abc12345" diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_test.cc index 8cb45a8ca3e6..f849b582c5f1 100644 --- a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_test.cc @@ -14,25 +14,25 @@ namespace OpenTelemetry { TEST(SamplerConfigTest, TestParsing) { // default_root_spans_per_minute not set, ROOT_SPANS_PER_MINUTE_DEFAULT should be used SamplerConfig config(0); - config.parse("{\n \"rootSpansPerMinute\" : 2000 \n }"); + EXPECT_TRUE(config.parse("{\n \"rootSpansPerMinute\" : 2000 \n }")); EXPECT_EQ(config.getRootSpansPerMinute(), 2000u); - config.parse("{\n \"rootSpansPerMinute\" : 10000 \n }"); + EXPECT_TRUE(config.parse("{\n \"rootSpansPerMinute\" : 10000 \n }")); EXPECT_EQ(config.getRootSpansPerMinute(), 10000u); // unexpected json, default value should be used - config.parse("{}"); + EXPECT_FALSE(config.parse("{}")); EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); - config.parse(""); + EXPECT_FALSE(config.parse("")); EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); - config.parse("\\"); + EXPECT_FALSE(config.parse("\\")); EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); - config.parse(" { "); + EXPECT_FALSE(config.parse(" { ")); EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); - config.parse("{\n \"rootSpansPerMinute\" : 10000 "); // closing } is missing + EXPECT_FALSE(config.parse("{\n \"rootSpansPerMinute\" : 10000 ")); // closing } is missing EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); } @@ -41,19 +41,19 @@ TEST(SamplerConfigTest, TestDefaultConfig) { { SamplerConfig config(0); EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); - config.parse(" { "); // parse invalid json, default value should still be used + EXPECT_FALSE(config.parse(" { ")); // parse invalid json, default value should still be used EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); } { SamplerConfig config(900); EXPECT_EQ(config.getRootSpansPerMinute(), 900); - config.parse(" { "); + EXPECT_FALSE(config.parse(" { ")); EXPECT_EQ(config.getRootSpansPerMinute(), 900); } { SamplerConfig config(SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); - config.parse(" { "); + EXPECT_FALSE(config.parse(" { ")); EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); } }