Skip to content
Open
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
692 changes: 608 additions & 84 deletions Cargo.lock

Large diffs are not rendered by default.

2,620 changes: 2,342 additions & 278 deletions LICENSE-3rdparty.yml

Large diffs are not rendered by default.

203 changes: 86 additions & 117 deletions examples/cxx/profiling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,141 +176,110 @@ int main() {
profile->add_endpoint_count("/api/products", 200);
std::cout << "✅ Added endpoint mappings and counts" << std::endl;

// Check if we should export to Datadog or save to file
// Create exporter based on environment variables
const char* agent_url = std::getenv("DD_AGENT_URL");
const char* api_key = std::getenv("DD_API_KEY");

if (agent_url || api_key) {
// Export to Datadog
std::cout << "\n=== Exporting to Datadog ===" << std::endl;

// Create a cancellation token for the export
// In a real application, you could clone this and cancel from another thread
// Example: auto token_clone = cancel_token->clone_token(); token_clone->cancel();
auto cancel_token = new_cancellation_token();

try {
// Example: Create an additional file to attach (e.g., application metadata)
std::string app_metadata = R"({
"app_version": "1.2.3",
"build_id": "abc123",
"profiling_mode": "continuous",
"sample_count": 100
})";
std::vector<uint8_t> metadata_bytes(app_metadata.begin(), app_metadata.end());

if (api_key) {
// Agentless mode - send directly to Datadog intake
const char* site = std::getenv("DD_SITE");
std::string dd_site = site ? site : "datadoghq.com";

std::cout << "Creating agentless exporter (site: " << dd_site << ")..." << std::endl;
auto exporter = ProfileExporter::create_agentless_exporter(
"dd-trace-cpp",
"1.0.0",
"native",
std::cout << "\n=== Creating Exporter ===" << std::endl;

// Create appropriate exporter based on configuration
std::unique_ptr<rust::Box<ProfileExporter>> exporter;
try {
if (api_key) {
// Agentless mode - send directly to Datadog intake
const char* site = std::getenv("DD_SITE");
std::string dd_site = site ? site : "datadoghq.com";
std::cout << "Creating agentless exporter (site: " << dd_site << ")..." << std::endl;
exporter = std::make_unique<rust::Box<ProfileExporter>>(
ProfileExporter::create_agentless_exporter(
"dd-trace-cpp", "1.0.0", "native",
{
Tag{.key = "service", .value = "profiling-example"},
Tag{.key = "env", .value = "dev"},
Tag{.key = "example", .value = "cxx"}
},
dd_site.c_str(),
api_key,
10000 // 10 second timeout (0 = use default)
);
std::cout << "✅ Exporter created" << std::endl;

std::cout << "Exporting profile to Datadog with additional metadata..." << std::endl;

exporter->send_profile_with_cancellation(
*profile,
// Files to compress and attach
{AttachmentFile{
.name = "app_metadata.json",
.data = {metadata_bytes.data(), metadata_bytes.size()}
}},
// Additional per-profile tags
{
Tag{.key = "export_id", .value = "12345"},
Tag{.key = "host", .value = "example-host"}
},
// Process-level tags (comma-separated)
"language:cpp,profiler_version:1.0,runtime:native",
// Internal metadata (JSON string)
R"({"profiler_version": "1.0", "custom_field": "demo"})",
// System info (JSON string)
R"({"os": "macos", "arch": "arm64", "cores": 8})",
*cancel_token
);
std::cout << "✅ Profile exported successfully!" << std::endl;
} else {
// Agent mode - send to local Datadog agent
std::cout << "Creating agent exporter (url: " << agent_url << ")..." << std::endl;
auto exporter = ProfileExporter::create_agent_exporter(
"dd-trace-cpp",
"1.0.0",
"native",
dd_site.c_str(), api_key, 10000
)
);
} else if (agent_url) {
// Agent mode - send to local Datadog agent
std::cout << "Creating agent exporter (url: " << agent_url << ")..." << std::endl;
exporter = std::make_unique<rust::Box<ProfileExporter>>(
ProfileExporter::create_agent_exporter(
"dd-trace-cpp", "1.0.0", "native",
{
Tag{.key = "service", .value = "profiling-example"},
Tag{.key = "env", .value = "dev"},
Tag{.key = "example", .value = "cxx"}
},
agent_url,
10000 // 10 second timeout (0 = use default)
);
std::cout << "✅ Exporter created" << std::endl;

std::cout << "Exporting profile to Datadog with additional metadata..." << std::endl;

exporter->send_profile_with_cancellation(
*profile,
// Files to compress and attach
{AttachmentFile{
.name = "app_metadata.json",
.data = {metadata_bytes.data(), metadata_bytes.size()}
}},
// Additional per-profile tags
agent_url, 10000
)
);
} else {
// File mode - dump HTTP request for debugging/testing
std::cout << "Creating file exporter (profile_dump.txt)..." << std::endl;
exporter = std::make_unique<rust::Box<ProfileExporter>>(
ProfileExporter::create_file_exporter(
"dd-trace-cpp", "1.0.0", "native",
{
Tag{.key = "export_id", .value = "12345"},
Tag{.key = "host", .value = "example-host"}
Tag{.key = "service", .value = "profiling-example"},
Tag{.key = "env", .value = "dev"},
Tag{.key = "example", .value = "cxx"}
},
// Process-level tags (comma-separated)
"language:cpp,profiler_version:1.0,runtime:native",
// Internal metadata (JSON string)
R"({"profiler_version": "1.0", "custom_field": "demo"})",
// System info (JSON string)
R"({"os": "macos", "arch": "arm64", "cores": 8})",
*cancel_token
);
std::cout << "✅ Profile exported successfully!" << std::endl;
}

} catch (const std::exception& e) {
std::cerr << "⚠️ Failed to export profile: " << e.what() << std::endl;
std::cerr << " Falling back to file export..." << std::endl;

// Fall back to file export on error
auto serialized = profile->serialize_to_vec();
std::ofstream out("profile.pprof", std::ios::binary);
out.write(reinterpret_cast<const char*>(serialized.data()), serialized.size());
out.close();
std::cout << "✅ Profile written to profile.pprof" << std::endl;
"profile_dump.txt"
)
);
}
} else {
// Save to file
std::cout << "\n=== Saving to File ===" << std::endl;
std::cout << "Serializing profile..." << std::endl;
auto serialized = profile->serialize_to_vec();
std::cout << "✅ Profile serialized to " << serialized.size() << " bytes" << std::endl;
std::cout << "✅ Exporter created" << std::endl;

std::ofstream out("profile.pprof", std::ios::binary);
out.write(reinterpret_cast<const char*>(serialized.data()), serialized.size());
out.close();
std::cout << "✅ Profile written to profile.pprof" << std::endl;
// Create a cancellation token for the export
// In a real application, you could clone this and cancel from another thread
// Example: auto token_clone = cancel_token->clone_token(); token_clone->cancel();
auto cancel_token = new_cancellation_token();

std::cout << "\nℹ️ To export to Datadog instead, set environment variables:" << std::endl;
std::cout << " Agent mode: DD_AGENT_URL=http://localhost:8126" << std::endl;
std::cout << " Agentless mode: DD_API_KEY=<your-api-key> [DD_SITE=datadoghq.com]" << std::endl;
// Prepare metadata (same for all export modes)
std::string app_metadata = R"({
"app_version": "1.2.3",
"build_id": "abc123",
"profiling_mode": "continuous",
"sample_count": 100
})";
std::vector<uint8_t> metadata_bytes(app_metadata.begin(), app_metadata.end());

// Export the profile (unified code path)
std::cout << "Exporting profile with additional metadata..." << std::endl;
(*exporter)->send_profile_with_cancellation(
*profile,
// Files to compress and attach
{AttachmentFile{
.name = "app_metadata.json",
.data = {metadata_bytes.data(), metadata_bytes.size()}
}},
// Additional per-profile tags
{
Tag{.key = "export_id", .value = "12345"},
Tag{.key = "host", .value = "example-host"}
},
// Process-level tags (comma-separated)
"language:cpp,profiler_version:1.0,runtime:native",
// Internal metadata (JSON string)
R"({"profiler_version": "1.0", "custom_field": "demo"})",
// System info (JSON string)
R"({"os": "macos", "arch": "arm64", "cores": 8})",
*cancel_token
);
std::cout << "✅ Profile exported successfully!" << std::endl;

// Print mode-specific info
if (!agent_url && !api_key) {
std::cout << "ℹ️ HTTP request written to profile_dump.txt" << std::endl;
std::cout << "ℹ️ Use the utils in libdd-profiling to parse the HTTP dump" << std::endl;
std::cout << "\nℹ️ To export to Datadog instead, set environment variables:" << std::endl;
std::cout << " Agent mode: DD_AGENT_URL=http://localhost:8126" << std::endl;
std::cout << " Agentless mode: DD_API_KEY=<your-api-key> [DD_SITE=datadoghq.com]" << std::endl;
}
} catch (const std::exception& e) {
std::cerr << "⚠️ Failed to export profile: " << e.what() << std::endl;
}

std::cout << "\n✅ Success!" << std::endl;
Expand Down
5 changes: 4 additions & 1 deletion libdd-profiling/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@ serde = {version = "1.0", features = ["derive"]}
serde_json = {version = "1.0"}
target-triple = "0.1.4"
thiserror = "2"
tokio = {version = "1.23", features = ["rt", "macros"]}
tokio = {version = "1.23", features = ["rt", "macros", "net", "io-util", "fs", "sync"]}
tokio-util = "0.7.1"
rand = "0.8"
httparse = "1.9"
multipart = "0.18"
zstd = { version = "0.13", default-features = false }
cxx = { version = "1.0", optional = true }

Expand Down
40 changes: 40 additions & 0 deletions libdd-profiling/src/cxx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,15 @@ pub mod ffi {
timeout_ms: u64,
) -> Result<Box<ProfileExporter>>;

#[Self = "ProfileExporter"]
fn create_file_exporter(
profiling_library_name: &str,
profiling_library_version: &str,
family: &str,
tags: Vec<Tag>,
output_path: &str,
) -> Result<Box<ProfileExporter>>;

// ProfileExporter methods
/// Sends a profile to Datadog.
///
Expand Down Expand Up @@ -518,6 +527,37 @@ impl ProfileExporter {
Ok(Box::new(ProfileExporter { inner }))
}

pub fn create_file_exporter(
profiling_library_name: &str,
profiling_library_version: &str,
family: &str,
tags: Vec<ffi::Tag>,
output_path: &str,
) -> anyhow::Result<Box<ProfileExporter>> {
let endpoint = exporter::config::file(output_path)?;

let tags_vec: Vec<exporter::Tag> = tags
.iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()?;

let tags_option = if tags_vec.is_empty() {
None
} else {
Some(tags_vec)
};

let inner = exporter::ProfileExporter::new(
profiling_library_name.to_string(),
profiling_library_version.to_string(),
family.to_string(),
tags_option,
endpoint,
)?;

Ok(Box::new(ProfileExporter { inner }))
}

/// Sends a profile to Datadog.
///
/// # Arguments
Expand Down
4 changes: 4 additions & 0 deletions libdd-profiling/src/exporter/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ pub fn agentless<AsStrRef: AsRef<str>, IntoCow: Into<Cow<'static, str>>>(
})
}

/// Creates an Endpoint for dumping HTTP requests to a file for testing/debugging.
///
/// # Arguments
/// * `path` - File system path where the HTTP request bytes should be written
pub fn file(path: impl AsRef<str>) -> anyhow::Result<Endpoint> {
let url: String = format!("file://{}", path.as_ref());
Ok(Endpoint::from_slice(&url))
Expand Down
Loading
Loading