diff --git a/BUILD.bazel b/BUILD.bazel index 89e2ac21..0a854130 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -17,6 +17,7 @@ cc_library( "src/datadog/datadog_agent.cpp", "src/datadog/datadog_agent.h", "src/datadog/datadog_agent_config.cpp", + "src/datadog/datadog_agent_config_internal.h", "src/datadog/default_http_client.h", "src/datadog/default_http_client_null.cpp", "src/datadog/endpoint_inferral.cpp", diff --git a/benchmark/hasher.h b/benchmark/hasher.h index 9ad3e6b1..849b2519 100644 --- a/benchmark/hasher.h +++ b/benchmark/hasher.h @@ -1,11 +1,10 @@ #include -namespace datadog { -namespace tracing { +namespace datadog::tracing { class Tracer; -} // namespace tracing -} // namespace datadog +} // namespace datadog::tracing // Use the specified `tracer` to create a trace whose structure resembles the // file system tree rooted at the specified `path`. -void sha256_traced(const std::filesystem::path &path, datadog::tracing::Tracer &tracer); +void sha256_traced(const std::filesystem::path &path, + datadog::tracing::Tracer &tracer); diff --git a/benchmark/trace_id_bench.cpp b/benchmark/trace_id_bench.cpp index 89d4d45d..6931043e 100644 --- a/benchmark/trace_id_bench.cpp +++ b/benchmark/trace_id_bench.cpp @@ -6,10 +6,14 @@ namespace { namespace dd = datadog::tracing; +constexpr int batch_size = 1000; + void BM_TraceID_HexPadded(benchmark::State& state, dd::TraceID id) { for (auto _ : state) { - auto result = id.hex_padded(); - benchmark::DoNotOptimize(result); + for (int i = 0; i < batch_size; ++i) { + auto result = id.hex_padded(); + benchmark::DoNotOptimize(result); + } } } BENCHMARK_CAPTURE(BM_TraceID_HexPadded, NoPadding, @@ -18,8 +22,10 @@ BENCHMARK_CAPTURE(BM_TraceID_HexPadded, WithPadding, dd::TraceID{1, 0}); void BM_TraceID_ParseHex(benchmark::State& state, std::string input) { for (auto _ : state) { - auto result = dd::TraceID::parse_hex(input); - benchmark::DoNotOptimize(result); + for (int i = 0; i < batch_size; ++i) { + auto result = dd::TraceID::parse_hex(input); + benchmark::DoNotOptimize(result); + } } } BENCHMARK_CAPTURE(BM_TraceID_ParseHex, 64bit, std::string{"deadbeefcafebabe"}); @@ -28,8 +34,10 @@ BENCHMARK_CAPTURE(BM_TraceID_ParseHex, 128bit, void BM_HexPadded_uint64(benchmark::State& state, std::uint64_t value) { for (auto _ : state) { - auto result = dd::hex_padded(value); - benchmark::DoNotOptimize(result); + for (int i = 0; i < batch_size; ++i) { + auto result = dd::hex_padded(value); + benchmark::DoNotOptimize(result); + } } } BENCHMARK_CAPTURE(BM_HexPadded_uint64, NoPadding, 0xDEADBEEFCAFEBABEULL); @@ -38,8 +46,10 @@ BENCHMARK_CAPTURE(BM_HexPadded_uint64, WorstCasePadding, 0x1ULL); void BM_Hex_uint64(benchmark::State& state) { const std::uint64_t value = 0xDEADBEEFCAFEBABEULL; for (auto _ : state) { - auto result = dd::hex(value); - benchmark::DoNotOptimize(result); + for (int i = 0; i < batch_size; ++i) { + auto result = dd::hex(value); + benchmark::DoNotOptimize(result); + } } } BENCHMARK(BM_Hex_uint64); diff --git a/include/datadog/environment.h b/include/datadog/environment.h index 93046601..4b4e8dbb 100644 --- a/include/datadog/environment.h +++ b/include/datadog/environment.h @@ -63,7 +63,8 @@ namespace environment { MACRO(DD_TRACE_AGENT_URL, STRING, \ ENV_DEFAULT_RESOLVED_IN_CODE( \ "If unset, built from DD_AGENT_HOST and DD_TRACE_AGENT_PORT, " \ - "then defaults to http://localhost:8126.")) \ + "then uses /var/run/datadog/apm.socket when present, otherwise " \ + "defaults to http://localhost:8126.")) \ MACRO(DD_TRACE_DEBUG, BOOLEAN, false) \ MACRO(DD_TRACE_ENABLED, BOOLEAN, true) \ MACRO(DD_TRACE_RATE_LIMIT, INT, 100) \ diff --git a/src/datadog/datadog_agent_config.cpp b/src/datadog/datadog_agent_config.cpp index d5335fd1..4e3abd59 100644 --- a/src/datadog/datadog_agent_config.cpp +++ b/src/datadog/datadog_agent_config.cpp @@ -1,6 +1,9 @@ #include #include +#include + +#include "datadog_agent_config_internal.h" #include "default_http_client.h" #include "parse_util.h" #include "threaded_event_scheduler.h" @@ -9,6 +12,30 @@ namespace datadog::tracing { namespace { +constexpr char default_agent_url[] = "http://localhost:8126"; + +std::string detect_default_agent_url( + [[maybe_unused]] const std::filesystem::path& socket_path) { +#ifndef _WIN32 + std::error_code error; + if (std::filesystem::exists(socket_path, error)) { + return "unix://" + socket_path.string(); + } +#endif + + return std::string{default_agent_url}; +} + +} // namespace + +std::pair select_agent_url( + const Optional& environment_url, + const Optional& programmatic_url, + const std::filesystem::path& default_socket_path) { + return pick(environment_url, programmatic_url, + detect_default_agent_url(default_socket_path)); +} + Optional build_agent_url_from_environment_variables() { Optional url_env = lookup(environment::DD_TRACE_AGENT_URL); if (url_env && !url_env->empty()) { @@ -39,8 +66,6 @@ Optional build_agent_url_from_environment_variables() { return nullopt; } -} // namespace - Expected load_datadog_agent_env_config() { DatadogAgentConfig env_config; @@ -155,7 +180,8 @@ Expected finalize_config( user_config.remote_configuration_enabled, true); const auto [origin, url] = - pick(env_config->url, user_config.url, "http://localhost:8126"); + select_agent_url(env_config->url, user_config.url, + std::filesystem::path{default_agent_socket_path}); auto parsed_url = HTTPClient::URL::parse(url); if (auto* error = parsed_url.if_error()) { return std::move(*error); diff --git a/src/datadog/datadog_agent_config_internal.h b/src/datadog/datadog_agent_config_internal.h new file mode 100644 index 00000000..7d024650 --- /dev/null +++ b/src/datadog/datadog_agent_config_internal.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include +#include + +namespace datadog::tracing { + +// The well-known path where the Datadog Agent listens on a Unix domain socket. +// Single-step instrumentation mounts the socket here. +inline constexpr char default_agent_socket_path[] = + "/var/run/datadog/apm.socket"; + +// Return the Agent URL configured by the environment, or null if the +// environment does not configure one. `DD_TRACE_AGENT_URL` wins over +// `DD_AGENT_HOST` and `DD_TRACE_AGENT_PORT`. Empty values count as unset. +Optional build_agent_url_from_environment_variables(); + +// Return the origin and value of the Agent URL, given the URL from the +// environment, the URL from programmatic configuration, and the path where the +// Agent might listen on a Unix domain socket. +std::pair select_agent_url( + const Optional& environment_url, + const Optional& programmatic_url, + const std::filesystem::path& default_socket_path); + +} // namespace datadog::tracing diff --git a/test/common/environment.h b/test/common/environment.h index 089904e5..923ec5ac 100644 --- a/test/common/environment.h +++ b/test/common/environment.h @@ -15,12 +15,17 @@ class EnvGuard { tracing::Optional former_value_; public: - EnvGuard(std::string name, std::string value) : name_(std::move(name)) { + EnvGuard(std::string name, std::string value) : EnvGuard(std::move(name)) { + set_value(value); + } + + // Unset the variable until a value is set or this object is destroyed. + explicit EnvGuard(std::string name) : name_(std::move(name)) { const char* current = std::getenv(name_.c_str()); if (current) { former_value_ = current; } - set_value(value); + unset(); } ~EnvGuard() { diff --git a/test/test_tracer_config.cpp b/test/test_tracer_config.cpp index f23f8893..e757c643 100644 --- a/test/test_tracer_config.cpp +++ b/test/test_tracer_config.cpp @@ -15,6 +15,7 @@ #include #include "common/environment.h" +#include "datadog_agent_config_internal.h" #include "mocks/collectors.h" #include "mocks/event_schedulers.h" #include "mocks/loggers.h" @@ -450,6 +451,75 @@ TRACER_CONFIG_TEST("TracerConfig::agent") { } SECTION("url") { + SECTION("the default socket path is the well-known Agent socket") { + REQUIRE(std::string{default_agent_socket_path} == + "/var/run/datadog/apm.socket"); + } + + SECTION("all defaults use the detected default URL") { + // Whether the well-known socket exists depends on the machine running + // this test, so compare against what detection decides, not against a + // hard-coded URL. This checks that `finalize_config` probes the + // well-known socket path. + EnvGuard host_guard{"DD_AGENT_HOST"}; + EnvGuard port_guard{"DD_TRACE_AGENT_PORT"}; + EnvGuard url_guard{"DD_TRACE_AGENT_URL"}; + + const auto [origin, expected_url] = + select_agent_url(nullopt, nullopt, default_agent_socket_path); + REQUIRE(origin == ConfigMetadata::Origin::DEFAULT); + const auto expected = HTTPClient::URL::parse(expected_url); + REQUIRE(expected); + + auto finalized = finalize_config(config); + REQUIRE(finalized); + const auto* const agent = + std::get_if(&finalized->collector); + REQUIRE(agent); + REQUIRE(agent->url.scheme == expected->scheme); + REQUIRE(agent->url.authority == expected->authority); + } + + SECTION("default socket detection") { + SomewhatSecureTemporaryFile socket; + REQUIRE(socket.is_open()); + + SECTION("uses an existing socket") { + const auto [origin, url] = + select_agent_url(nullopt, nullopt, socket.path()); + REQUIRE(origin == ConfigMetadata::Origin::DEFAULT); +#ifdef _WIN32 + REQUIRE(url == "http://localhost:8126"); +#else + REQUIRE(url == "unix://" + socket.path().string()); +#endif + } + + SECTION("falls back when the socket does not exist") { + const std::filesystem::path missing_socket = + socket.path().string() + ".missing"; + const auto [origin, url] = + select_agent_url(nullopt, nullopt, missing_socket); + REQUIRE(origin == ConfigMetadata::Origin::DEFAULT); + REQUIRE(url == "http://localhost:8126"); + } + + SECTION("programmatic URL takes precedence") { + const auto [origin, url] = select_agent_url( + nullopt, "http://configured-agent:8126", socket.path()); + REQUIRE(origin == ConfigMetadata::Origin::CODE); + REQUIRE(url == "http://configured-agent:8126"); + } + + SECTION("environment configuration takes precedence") { + const auto [origin, url] = + select_agent_url("http://environment-agent:8126", + "http://configured-agent:8126", socket.path()); + REQUIRE(origin == ConfigMetadata::Origin::ENVIRONMENT_VARIABLE); + REQUIRE(url == "http://environment-agent:8126"); + } + } + SECTION("parsing") { struct TestCase { std::string url; @@ -493,6 +563,39 @@ TRACER_CONFIG_TEST("TracerConfig::agent") { } } + SECTION("no environment configuration") { + struct TestCase { + std::string name; + Optional env_host; + Optional env_port; + Optional env_url; + }; + + auto test_case = GENERATE(values({ + {"all unset", nullopt, nullopt, nullopt}, + {"empty host", "", nullopt, nullopt}, + {"empty port", nullopt, "", nullopt}, + {"empty URL", nullopt, nullopt, ""}, + {"all empty", "", "", ""}, + })); + + CAPTURE(test_case.name); + EnvGuard host_guard{"DD_AGENT_HOST"}; + EnvGuard port_guard{"DD_TRACE_AGENT_PORT"}; + EnvGuard url_guard{"DD_TRACE_AGENT_URL"}; + if (test_case.env_host) { + host_guard.set_value(*test_case.env_host); + } + if (test_case.env_port) { + port_guard.set_value(*test_case.env_port); + } + if (test_case.env_url) { + url_guard.set_value(*test_case.env_url); + } + + REQUIRE(build_agent_url_from_environment_variables() == nullopt); + } + SECTION("environment variables override") { struct TestCase { std::string name; @@ -504,15 +607,12 @@ TRACER_CONFIG_TEST("TracerConfig::agent") { }; auto test_case = GENERATE(values({ - {"all defaults", nullopt, nullopt, nullopt, "http", "localhost:8126"}, {"override host", "dd-agent", nullopt, nullopt, "http", "dd-agent:8126"}, {"override port", nullopt, "8080", nullopt, "http", "localhost:8080"}, {"override host and port", "dd-agent", "8080", nullopt, "http", "dd-agent:8080"}, {"empty URL", "dd-agent", "8080", "", "http", "dd-agent:8080"}, - {"empty host", "", nullopt, nullopt, "http", "localhost:8126"}, - {"empty port", nullopt, "", nullopt, "http", "localhost:8126"}, {"IPv6 host", "::1", nullopt, nullopt, "http", "[::1]:8126"}, {"IPv6 host with brackets", "[::1]", nullopt, nullopt, "http", "[::1]:8126"},