Skip to content

Commit e99f32f

Browse files
authored
feat(profiling): File endpoint for exporter (#1421)
feat(profiling): File endpoint for exporter Explicitly flush the file support in c++ clippy error on windows windows test failures use crates RAII test cleanup Use constants instead of strings merge main Co-authored-by: daniel.schwartznarbonne <[email protected]>
1 parent 5c4e535 commit e99f32f

File tree

10 files changed

+4069
-481
lines changed

10 files changed

+4069
-481
lines changed

Cargo.lock

Lines changed: 608 additions & 84 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

LICENSE-3rdparty.yml

Lines changed: 2342 additions & 278 deletions
Large diffs are not rendered by default.

examples/cxx/profiling.cpp

Lines changed: 86 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -176,141 +176,110 @@ int main() {
176176
profile->add_endpoint_count("/api/products", 200);
177177
std::cout << "✅ Added endpoint mappings and counts" << std::endl;
178178

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

183-
if (agent_url || api_key) {
184-
// Export to Datadog
185-
std::cout << "\n=== Exporting to Datadog ===" << std::endl;
186-
187-
// Create a cancellation token for the export
188-
// In a real application, you could clone this and cancel from another thread
189-
// Example: auto token_clone = cancel_token->clone_token(); token_clone->cancel();
190-
auto cancel_token = new_cancellation_token();
191-
192-
try {
193-
// Example: Create an additional file to attach (e.g., application metadata)
194-
std::string app_metadata = R"({
195-
"app_version": "1.2.3",
196-
"build_id": "abc123",
197-
"profiling_mode": "continuous",
198-
"sample_count": 100
199-
})";
200-
std::vector<uint8_t> metadata_bytes(app_metadata.begin(), app_metadata.end());
201-
202-
if (api_key) {
203-
// Agentless mode - send directly to Datadog intake
204-
const char* site = std::getenv("DD_SITE");
205-
std::string dd_site = site ? site : "datadoghq.com";
206-
207-
std::cout << "Creating agentless exporter (site: " << dd_site << ")..." << std::endl;
208-
auto exporter = ProfileExporter::create_agentless_exporter(
209-
"dd-trace-cpp",
210-
"1.0.0",
211-
"native",
183+
std::cout << "\n=== Creating Exporter ===" << std::endl;
184+
185+
// Create appropriate exporter based on configuration
186+
std::unique_ptr<rust::Box<ProfileExporter>> exporter;
187+
try {
188+
if (api_key) {
189+
// Agentless mode - send directly to Datadog intake
190+
const char* site = std::getenv("DD_SITE");
191+
std::string dd_site = site ? site : "datadoghq.com";
192+
std::cout << "Creating agentless exporter (site: " << dd_site << ")..." << std::endl;
193+
exporter = std::make_unique<rust::Box<ProfileExporter>>(
194+
ProfileExporter::create_agentless_exporter(
195+
"dd-trace-cpp", "1.0.0", "native",
212196
{
213197
Tag{.key = "service", .value = "profiling-example"},
214198
Tag{.key = "env", .value = "dev"},
215199
Tag{.key = "example", .value = "cxx"}
216200
},
217-
dd_site.c_str(),
218-
api_key,
219-
10000 // 10 second timeout (0 = use default)
220-
);
221-
std::cout << "✅ Exporter created" << std::endl;
222-
223-
std::cout << "Exporting profile to Datadog with additional metadata..." << std::endl;
224-
225-
exporter->send_profile_with_cancellation(
226-
*profile,
227-
// Files to compress and attach
228-
{AttachmentFile{
229-
.name = "app_metadata.json",
230-
.data = {metadata_bytes.data(), metadata_bytes.size()}
231-
}},
232-
// Additional per-profile tags
233-
{
234-
Tag{.key = "export_id", .value = "12345"},
235-
Tag{.key = "host", .value = "example-host"}
236-
},
237-
// Process-level tags (comma-separated)
238-
"language:cpp,profiler_version:1.0,runtime:native",
239-
// Internal metadata (JSON string)
240-
R"({"profiler_version": "1.0", "custom_field": "demo"})",
241-
// System info (JSON string)
242-
R"({"os": "macos", "arch": "arm64", "cores": 8})",
243-
*cancel_token
244-
);
245-
std::cout << "✅ Profile exported successfully!" << std::endl;
246-
} else {
247-
// Agent mode - send to local Datadog agent
248-
std::cout << "Creating agent exporter (url: " << agent_url << ")..." << std::endl;
249-
auto exporter = ProfileExporter::create_agent_exporter(
250-
"dd-trace-cpp",
251-
"1.0.0",
252-
"native",
201+
dd_site.c_str(), api_key, 10000
202+
)
203+
);
204+
} else if (agent_url) {
205+
// Agent mode - send to local Datadog agent
206+
std::cout << "Creating agent exporter (url: " << agent_url << ")..." << std::endl;
207+
exporter = std::make_unique<rust::Box<ProfileExporter>>(
208+
ProfileExporter::create_agent_exporter(
209+
"dd-trace-cpp", "1.0.0", "native",
253210
{
254211
Tag{.key = "service", .value = "profiling-example"},
255212
Tag{.key = "env", .value = "dev"},
256213
Tag{.key = "example", .value = "cxx"}
257214
},
258-
agent_url,
259-
10000 // 10 second timeout (0 = use default)
260-
);
261-
std::cout << "✅ Exporter created" << std::endl;
262-
263-
std::cout << "Exporting profile to Datadog with additional metadata..." << std::endl;
264-
265-
exporter->send_profile_with_cancellation(
266-
*profile,
267-
// Files to compress and attach
268-
{AttachmentFile{
269-
.name = "app_metadata.json",
270-
.data = {metadata_bytes.data(), metadata_bytes.size()}
271-
}},
272-
// Additional per-profile tags
215+
agent_url, 10000
216+
)
217+
);
218+
} else {
219+
// File mode - dump HTTP request for debugging/testing
220+
std::cout << "Creating file exporter (profile_dump.txt)..." << std::endl;
221+
exporter = std::make_unique<rust::Box<ProfileExporter>>(
222+
ProfileExporter::create_file_exporter(
223+
"dd-trace-cpp", "1.0.0", "native",
273224
{
274-
Tag{.key = "export_id", .value = "12345"},
275-
Tag{.key = "host", .value = "example-host"}
225+
Tag{.key = "service", .value = "profiling-example"},
226+
Tag{.key = "env", .value = "dev"},
227+
Tag{.key = "example", .value = "cxx"}
276228
},
277-
// Process-level tags (comma-separated)
278-
"language:cpp,profiler_version:1.0,runtime:native",
279-
// Internal metadata (JSON string)
280-
R"({"profiler_version": "1.0", "custom_field": "demo"})",
281-
// System info (JSON string)
282-
R"({"os": "macos", "arch": "arm64", "cores": 8})",
283-
*cancel_token
284-
);
285-
std::cout << "✅ Profile exported successfully!" << std::endl;
286-
}
287-
288-
} catch (const std::exception& e) {
289-
std::cerr << "⚠️ Failed to export profile: " << e.what() << std::endl;
290-
std::cerr << " Falling back to file export..." << std::endl;
291-
292-
// Fall back to file export on error
293-
auto serialized = profile->serialize_to_vec();
294-
std::ofstream out("profile.pprof", std::ios::binary);
295-
out.write(reinterpret_cast<const char*>(serialized.data()), serialized.size());
296-
out.close();
297-
std::cout << "✅ Profile written to profile.pprof" << std::endl;
229+
"profile_dump.txt"
230+
)
231+
);
298232
}
299-
} else {
300-
// Save to file
301-
std::cout << "\n=== Saving to File ===" << std::endl;
302-
std::cout << "Serializing profile..." << std::endl;
303-
auto serialized = profile->serialize_to_vec();
304-
std::cout << "✅ Profile serialized to " << serialized.size() << " bytes" << std::endl;
233+
std::cout << "✅ Exporter created" << std::endl;
305234

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

311-
std::cout << "\nℹ️ To export to Datadog instead, set environment variables:" << std::endl;
312-
std::cout << " Agent mode: DD_AGENT_URL=http://localhost:8126" << std::endl;
313-
std::cout << " Agentless mode: DD_API_KEY=<your-api-key> [DD_SITE=datadoghq.com]" << std::endl;
240+
// Prepare metadata (same for all export modes)
241+
std::string app_metadata = R"({
242+
"app_version": "1.2.3",
243+
"build_id": "abc123",
244+
"profiling_mode": "continuous",
245+
"sample_count": 100
246+
})";
247+
std::vector<uint8_t> metadata_bytes(app_metadata.begin(), app_metadata.end());
248+
249+
// Export the profile (unified code path)
250+
std::cout << "Exporting profile with additional metadata..." << std::endl;
251+
(*exporter)->send_profile_with_cancellation(
252+
*profile,
253+
// Files to compress and attach
254+
{AttachmentFile{
255+
.name = "app_metadata.json",
256+
.data = {metadata_bytes.data(), metadata_bytes.size()}
257+
}},
258+
// Additional per-profile tags
259+
{
260+
Tag{.key = "export_id", .value = "12345"},
261+
Tag{.key = "host", .value = "example-host"}
262+
},
263+
// Process-level tags (comma-separated)
264+
"language:cpp,profiler_version:1.0,runtime:native",
265+
// Internal metadata (JSON string)
266+
R"({"profiler_version": "1.0", "custom_field": "demo"})",
267+
// System info (JSON string)
268+
R"({"os": "macos", "arch": "arm64", "cores": 8})",
269+
*cancel_token
270+
);
271+
std::cout << "✅ Profile exported successfully!" << std::endl;
272+
273+
// Print mode-specific info
274+
if (!agent_url && !api_key) {
275+
std::cout << "ℹ️ HTTP request written to profile_dump.txt" << std::endl;
276+
std::cout << "ℹ️ Use the utils in libdd-profiling to parse the HTTP dump" << std::endl;
277+
std::cout << "\nℹ️ To export to Datadog instead, set environment variables:" << std::endl;
278+
std::cout << " Agent mode: DD_AGENT_URL=http://localhost:8126" << std::endl;
279+
std::cout << " Agentless mode: DD_API_KEY=<your-api-key> [DD_SITE=datadoghq.com]" << std::endl;
280+
}
281+
} catch (const std::exception& e) {
282+
std::cerr << "⚠️ Failed to export profile: " << e.what() << std::endl;
314283
}
315284

316285
std::cout << "\n✅ Success!" << std::endl;

libdd-profiling/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@ serde = {version = "1.0", features = ["derive"]}
5050
serde_json = {version = "1.0"}
5151
target-triple = "0.1.4"
5252
thiserror = "2"
53-
tokio = {version = "1.23", features = ["rt", "macros"]}
53+
tokio = {version = "1.23", features = ["rt", "macros", "net", "io-util", "fs", "sync"]}
5454
tokio-util = "0.7.1"
55+
rand = "0.8"
56+
httparse = "1.9"
57+
multipart = "0.18"
5558
zstd = { version = "0.13", default-features = false }
5659
cxx = { version = "1.0", optional = true }
5760

libdd-profiling/src/cxx.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,15 @@ pub mod ffi {
145145
timeout_ms: u64,
146146
) -> Result<Box<ProfileExporter>>;
147147

148+
#[Self = "ProfileExporter"]
149+
fn create_file_exporter(
150+
profiling_library_name: &str,
151+
profiling_library_version: &str,
152+
family: &str,
153+
tags: Vec<Tag>,
154+
output_path: &str,
155+
) -> Result<Box<ProfileExporter>>;
156+
148157
// ProfileExporter methods
149158
/// Sends a profile to Datadog.
150159
///
@@ -518,6 +527,37 @@ impl ProfileExporter {
518527
Ok(Box::new(ProfileExporter { inner }))
519528
}
520529

530+
pub fn create_file_exporter(
531+
profiling_library_name: &str,
532+
profiling_library_version: &str,
533+
family: &str,
534+
tags: Vec<ffi::Tag>,
535+
output_path: &str,
536+
) -> anyhow::Result<Box<ProfileExporter>> {
537+
let endpoint = exporter::config::file(output_path)?;
538+
539+
let tags_vec: Vec<exporter::Tag> = tags
540+
.iter()
541+
.map(TryInto::try_into)
542+
.collect::<Result<Vec<_>, _>>()?;
543+
544+
let tags_option = if tags_vec.is_empty() {
545+
None
546+
} else {
547+
Some(tags_vec)
548+
};
549+
550+
let inner = exporter::ProfileExporter::new(
551+
profiling_library_name.to_string(),
552+
profiling_library_version.to_string(),
553+
family.to_string(),
554+
tags_option,
555+
endpoint,
556+
)?;
557+
558+
Ok(Box::new(ProfileExporter { inner }))
559+
}
560+
521561
/// Sends a profile to Datadog.
522562
///
523563
/// # Arguments

libdd-profiling/src/exporter/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ pub fn agentless<AsStrRef: AsRef<str>, IntoCow: Into<Cow<'static, str>>>(
7070
})
7171
}
7272

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

0 commit comments

Comments
 (0)