Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@
"cacheVariables": {
"CMAKE_POLICY_VERSION_MINIMUM": "3.5"
}
},
{
"name": "dev",
Copy link
Contributor

@dubloom dubloom May 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will be annoying but that would be cool to add documentation somewhere on how to use that (if it is supposed to be used by devs).
It does not need to be in that PR.

"displayName": "Development",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"DD_TRACE_ENABLE_SANITIZE": "ON",
"DD_TRACE_BUILD_TESTING": "ON",
"DD_TRACE_BUILD_EXAMPLES": "ON"
}
}
]
}
2 changes: 1 addition & 1 deletion examples/http-server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ from ubuntu:22.04
WORKDIR /dd-trace-cpp

ARG DEBIAN_FRONTEND=noninteractive
ARG BRANCH=v0.2.1
ARG BRANCH=v1.0.0

run apt update -y \
&& apt install -y g++ make git wget sed \
Expand Down
1 change: 1 addition & 0 deletions examples/http-server/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ services:
- DD_APM_ENABLED=true
- DD_LOG_LEVEL=ERROR
- DOCKER_HOST
- DD_SITE
3 changes: 3 additions & 0 deletions include/datadog/datadog_agent_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ class FinalizedDatadogAgentConfig {
std::chrono::steady_clock::duration shutdown_timeout;
std::chrono::steady_clock::duration remote_configuration_poll_interval;
std::unordered_map<ConfigName, ConfigMetadata> metadata;

// Origin detection
Optional<std::string> admission_controller_uid;
};

Expected<FinalizedDatadogAgentConfig> finalize_config(
Expand Down
3 changes: 2 additions & 1 deletion include/datadog/environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ namespace environment {
MACRO(DD_TELEMETRY_LOG_COLLECTION_ENABLED) \
MACRO(DD_INSTRUMENTATION_INSTALL_ID) \
MACRO(DD_INSTRUMENTATION_INSTALL_TYPE) \
MACRO(DD_INSTRUMENTATION_INSTALL_TIME)
MACRO(DD_INSTRUMENTATION_INSTALL_TIME) \
MACRO(DD_EXTERNAL_ENV)

#define WITH_COMMA(ARG) ARG,

Expand Down
44 changes: 34 additions & 10 deletions src/datadog/datadog_agent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "collector_response.h"
#include "json.hpp"
#include "msgpack.h"
#include "platform_util.h"
#include "span_data.h"
#include "telemetry_metrics.h"
#include "trace_sampler.h"
Expand Down Expand Up @@ -156,10 +157,36 @@ DatadogAgent::DatadogAgent(
flush_interval_(config.flush_interval),
request_timeout_(config.request_timeout),
shutdown_timeout_(config.shutdown_timeout),
remote_config_(tracer_signature, rc_listeners, logger),
tracer_signature_(tracer_signature) {
remote_config_(tracer_signature, rc_listeners, logger) {
assert(logger_);

// Set HTTP headers
headers_.emplace("Content-Type", "application/msgpack");
headers_.emplace("Datadog-Meta-Lang", "cpp");
headers_.emplace("Datadog-Meta-Lang-Version",
tracer_signature.library_language_version);
headers_.emplace("Datadog-Meta-Tracer-Version",
tracer_signature.library_version);

// Origin Detection headers are not necessary when Unix Domain Socket (UDS)
// is used to communicate with the Datadog Agent.
if (!contains(config.url.scheme, "unix")) {
if (auto container_id = container::get_id()) {
if (container_id->type == container::ContainerID::Type::container_id) {
headers_.emplace("Datadog-Container-ID", container_id->value);
headers_.emplace("Datadog-Entity-Id", "ci-" + container_id->value);
} else if (container_id->type ==
container::ContainerID::Type::cgroup_inode) {
headers_.emplace("Datadog-Entity-Id", "in-" + container_id->value);
}
}

if (config.admission_controller_uid) {
headers_.emplace("Datadog-External-Env",
*config.admission_controller_uid);
}
}

tasks_.emplace_back(event_scheduler_->schedule_recurring_event(
config.flush_interval, [this]() { flush(); }));

Expand Down Expand Up @@ -252,14 +279,11 @@ void DatadogAgent::flush() {

// This is the callback for setting request headers.
// It's invoked synchronously (before `post` returns).
auto set_request_headers = [&](DictWriter& headers) {
headers.set("Content-Type", "application/msgpack");
headers.set("Datadog-Meta-Lang", "cpp");
headers.set("Datadog-Meta-Lang-Version",
tracer_signature_.library_language_version);
headers.set("Datadog-Meta-Tracer-Version",
tracer_signature_.library_version);
headers.set("X-Datadog-Trace-Count", std::to_string(trace_chunks.size()));
auto set_request_headers = [&](DictWriter& writer) {
writer.set("X-Datadog-Trace-Count", std::to_string(trace_chunks.size()));
for (const auto& [key, value] : headers_) {
writer.set(key, value);
}
};

// This is the callback for the HTTP response. It's invoked
Expand Down
3 changes: 2 additions & 1 deletion src/datadog/datadog_agent.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ class DatadogAgent : public Collector {
std::chrono::steady_clock::duration shutdown_timeout_;

remote_config::Manager remote_config_;
TracerSignature tracer_signature_;

std::unordered_map<std::string, std::string> headers_;

void flush();

Expand Down
7 changes: 7 additions & 0 deletions src/datadog/datadog_agent_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "default_http_client.h"
#include "parse_util.h"
#include "platform_util.h"
#include "threaded_event_scheduler.h"

namespace datadog {
Expand Down Expand Up @@ -144,6 +145,12 @@ Expected<FinalizedDatadogAgentConfig> finalize_config(
result.metadata[ConfigName::AGENT_URL] =
ConfigMetadata(ConfigName::AGENT_URL, url, origin);

/// Starting Agent X, the admission controller inject a unique identifier
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • find the datadog agent version

/// through `DD_EXTERNAL_ENV`. This uid is used for origin detection.
if (auto external_env = lookup(environment::DD_EXTERNAL_ENV)) {
result.admission_controller_uid = std::string(*external_env);
}

return result;
}

Expand Down
130 changes: 128 additions & 2 deletions src/datadog/platform_util.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include "platform_util.h"

#include <cstdint>
#include <fstream>

// clang-format off
#if defined(__x86_64__) || defined(_M_X64)
# define DD_SDK_CPU_ARCH "x86_64"
Expand All @@ -24,11 +27,13 @@
# define DD_SDK_OS "GNU/Linux"
# define DD_SDK_KERNEL "Linux"
# include "string_util.h"
# include <errno.h>
# include <fstream>
# include <fcntl.h>
# include <sys/types.h>
# include <sys/mman.h>
# include <fcntl.h>
# include <errno.h>
# include <sys/stat.h>
# include <sys/statfs.h>
# endif
#elif defined(_MSC_VER)
# include <windows.h>
Expand Down Expand Up @@ -281,5 +286,126 @@ Expected<InMemoryFile> InMemoryFile::make(StringView) {
}
#endif

namespace container {
namespace {
#if defined(__linux__) || defined(__unix__)
/// Magic numbers from linux/magic.h:
/// <https://github.com/torvalds/linux/blob/ca91b9500108d4cf083a635c2e11c884d5dd20ea/include/uapi/linux/magic.h#L71>
constexpr uint64_t CGROUP_SUPER_MAGIC = 0x27e0eb;
constexpr uint64_t CGROUP2_SUPER_MAGIC = 0x63677270;

/// Magic number from linux/proc_ns.h:
/// <https://github.com/torvalds/linux/blob/5859a2b1991101d6b978f3feb5325dad39421f29/include/linux/proc_ns.h#L41-L49>
constexpr ino_t HOST_CGROUP_NAMESPACE_INODE = 0xeffffffb;

/// Represents the cgroup version of the current process.
enum class Cgroup : char { v1, v2 };

Optional<ino_t> get_inode(std::string_view path) {
struct stat buf;
if (stat(path.data(), &buf) != 0) {
return nullopt;
}

return buf.st_ino;
}

// Host namespace inode number are hardcoded, which allows for dectection of
// whether the binary is running in host or not. However, it does not work when
// running in a Docker in Docker environment.
bool is_running_in_host_namespace() {
// linux procfs file that represents the cgroup namespace of the current
// process.
if (auto inode = get_inode("/proc/self/ns/cgroup")) {
return *inode == HOST_CGROUP_NAMESPACE_INODE;
}

return false;
}

Optional<Cgroup> get_cgroup_version() {
struct statfs buf;

if (statfs("/sys/fs/cgroup", &buf) != 0) {
return nullopt;
}

if (buf.f_type == CGROUP_SUPER_MAGIC)
return Cgroup::v1;
else if (buf.f_type == CGROUP2_SUPER_MAGIC)
return Cgroup::v2;

return nullopt;
}

Optional<std::string> find_docker_container_id_from_cgroup() {
auto cgroup_fd = std::ifstream("/proc/self/cgroup", std::ios::in);
if (!cgroup_fd.is_open()) return nullopt;

return find_docker_container_id(cgroup_fd);
}
#endif
} // namespace

Optional<std::string> find_docker_container_id(std::istream& source) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Optional<std::string> find_docker_container_id(std::istream& source) {
// this function only exists for testing purposes
Optional<std::string> find_docker_container_id(std::istream& source) {

Copy link
Collaborator Author

@dmehala dmehala May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's litterally mentionned in the header file:

/// This function is exposed mainly for testing purposes.

constexpr std::string_view docker_str = "docker-";

std::string line;
while (std::getline(source, line)) {
// Example:
// `0::/system.slice/docker-abcdef0123456789abcdef0123456789.scope`
if (auto beg = line.find(docker_str); beg != std::string::npos) {
beg += docker_str.size();
auto end = line.find(".scope", beg);
if (end == std::string::npos || end - beg <= 0) {
continue;
}

auto container_id = line.substr(beg, end - beg);
return container_id;
}
}

return nullopt;
}

Optional<ContainerID> get_id() {
#if defined(__linux__) || defined(__unix__)
if (is_running_in_host_namespace()) {
// Not in a container, no need to continue.
return nullopt;
}

auto maybe_cgroup = get_cgroup_version();
if (!maybe_cgroup) return nullopt;

ContainerID id;
switch (*maybe_cgroup) {
case Cgroup::v1: {
if (auto maybe_id = find_docker_container_id_from_cgroup()) {
id.value = *maybe_id;
id.type = ContainerID::Type::container_id;
break;
}
}
// NOTE(@dmehala): failed to find the container ID, try getting the cgroup
// inode.
[[fallthrough]];
case Cgroup::v2: {
if (auto maybe_inode = get_inode("/sys/fs/cgroup")) {
id.type = ContainerID::Type::cgroup_inode;
id.value = std::to_string(*maybe_inode);
}
}; break;
}

return id;
#else
return nullopt;
#endif
}

} // namespace container

} // namespace tracing
} // namespace datadog
28 changes: 28 additions & 0 deletions src/datadog/platform_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,33 @@ std::string get_process_name();

int at_fork_in_child(void (*on_fork)());

namespace container {

struct ContainerID final {
/// Type of unique ID.
enum class Type : char { container_id, cgroup_inode } type;
/// Identifier of the container. It _mostly_ depends on the
/// cgroup version:
/// - For cgroup v1, it contains the container ID.
/// - For cgroup v2, it contains the "container" inode.
std::string value;
};

/// Find the docker container ID from a given source.
/// This function is exposed mainly for testing purposes.
///
/// @param source The input from which to read the Docker container ID.
/// @return An Optional containing the Docker container ID if found, otherwise
/// nothing.
Optional<std::string> find_docker_container_id(std::istream& source);

/// Function to retrieve the container metadata.
///
/// @return A `ContainerID` object containing id of the container in
/// which the current process is running.
Optional<ContainerID> get_id();

} // namespace container

} // namespace tracing
} // namespace datadog
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ add_executable(tests
test_glob.cpp
test_limiter.cpp
test_msgpack.cpp
test_platform_util.cpp
test_parse_util.cpp
test_smoke.cpp
test_span.cpp
Expand Down
6 changes: 4 additions & 2 deletions test/test_datadog_agent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ using namespace datadog;
using namespace datadog::tracing;
using namespace std::chrono_literals;

TEST_CASE("CollectorResponse", "[datadog_agent]") {
#define DATADOG_AGENT_TEST(x) TEST_CASE(x, "[datadog_agent]")

DATADOG_AGENT_TEST("CollectorResponse") {
TracerConfig config;
config.service = "testsvc";
const auto logger =
Expand Down Expand Up @@ -180,7 +182,7 @@ TEST_CASE("CollectorResponse", "[datadog_agent]") {
// - telemetry is disabled, no event scheduled.
// - telemetry is enabled, after x sec generate metrics is called.
// - send_app_started?
TEST_CASE("Remote Configuration", "[datadog_agent]") {
DATADOG_AGENT_TEST("Remote Configuration") {
const auto logger =
std::make_shared<MockLogger>(std::cerr, MockLogger::ERRORS_ONLY);
logger->echo = nullptr;
Expand Down
Loading