diff --git a/examples/ffi/exporter.cpp b/examples/ffi/exporter.cpp index b96af1a481..091515ae26 100644 --- a/examples/ffi/exporter.cpp +++ b/examples/ffi/exporter.cpp @@ -97,8 +97,9 @@ int main(int argc, char *argv[]) { auto *encoded_profile = &serialize_result.ok; + // Set a custom timeout of 10000ms (10 seconds) instead of the default 3000ms auto endpoint = - ddog_prof_Endpoint_agentless(DDOG_CHARSLICE_C_BARE("datad0g.com"), to_slice_c_char(api_key)); + ddog_prof_Endpoint_agentless(DDOG_CHARSLICE_C_BARE("datad0g.com"), to_slice_c_char(api_key), 10000); ddog_Vec_Tag tags = ddog_Vec_Tag_new(); ddog_Vec_Tag_PushResult tag_result = @@ -123,7 +124,6 @@ int main(int argc, char *argv[]) { auto exporter = &exporter_new_result.ok; auto files_to_compress_and_export = ddog_prof_Exporter_Slice_File_empty(); - auto files_to_export_unmodified = ddog_prof_Exporter_Slice_File_empty(); ddog_CharSlice internal_metadata_example = DDOG_CHARSLICE_C_BARE( "{\"no_signals_workaround_enabled\": \"true\", \"execution_trace_enabled\": \"false\"}"); @@ -132,32 +132,12 @@ int main(int argc, char *argv[]) { DDOG_CHARSLICE_C_BARE("{\"application\": {\"start_time\": \"2024-01-24T11:17:22+0000\"}, " "\"platform\": {\"kernel\": \"Darwin Kernel 22.5.0\"}}"); - auto res = ddog_prof_Exporter_set_timeout(exporter, 30000); - if (res.tag == DDOG_VOID_RESULT_ERR) { - print_error("Failed to set the timeout", res.err); - ddog_Error_drop(&res.err); - return 1; - } - - auto build_result = ddog_prof_Exporter_Request_build( - exporter, encoded_profile, files_to_compress_and_export, files_to_export_unmodified, nullptr, nullptr, - &internal_metadata_example, &info_example); - ddog_prof_EncodedProfile_drop(encoded_profile); - - if (build_result.tag == DDOG_PROF_REQUEST_RESULT_ERR_HANDLE_REQUEST) { - print_error("Failed to build request: ", build_result.err); - ddog_Error_drop(&build_result.err); - return 1; - } - - auto request = &build_result.ok; - auto cancel = ddog_CancellationToken_new(); auto cancel_for_background_thread = ddog_CancellationToken_clone(&cancel); // As an example of CancellationToken usage, here we create a background // thread that sleeps for some time and then cancels a request early (e.g. - // before the timeout in ddog_ProfileExporter_send is hit). + // before the timeout in ddog_prof_Exporter_send_blocking is hit). // // If the request is faster than the sleep time, no cancellation takes place. std::thread trigger_cancel_if_request_takes_too_long_thread( @@ -174,7 +154,9 @@ int main(int argc, char *argv[]) { trigger_cancel_if_request_takes_too_long_thread.detach(); int exit_code = 0; - auto send_result = ddog_prof_Exporter_send(exporter, request, &cancel); + auto send_result = ddog_prof_Exporter_send_blocking( + exporter, encoded_profile, files_to_compress_and_export, + nullptr, nullptr, &internal_metadata_example, &info_example, &cancel); if (send_result.tag == DDOG_PROF_RESULT_HTTP_STATUS_ERR_HTTP_STATUS) { print_error("Failed to send profile: ", send_result.err); exit_code = 1; @@ -183,7 +165,6 @@ int main(int argc, char *argv[]) { printf("Response code: %d\n", send_result.ok.code); } - ddog_prof_Exporter_Request_drop(request); ddog_prof_Exporter_drop(exporter); ddog_CancellationToken_drop(&cancel); return exit_code; diff --git a/libdd-common/src/lib.rs b/libdd-common/src/lib.rs index 6b04d10dee..6ec3934a9d 100644 --- a/libdd-common/src/lib.rs +++ b/libdd-common/src/lib.rs @@ -303,4 +303,21 @@ impl Endpoint { pub fn is_file_endpoint(&self) -> bool { self.url.scheme_str() == Some("file") } + + /// Set a custom timeout for this endpoint. + /// If not called, uses the default timeout of 3000ms. + /// + /// # Arguments + /// * `timeout_ms` - Timeout in milliseconds. Pass 0 to use the default timeout (3000ms). + /// + /// # Returns + /// Self with the timeout set, allowing for method chaining + pub fn with_timeout(mut self, timeout_ms: u64) -> Self { + self.timeout_ms = if timeout_ms == 0 { + Self::DEFAULT_TIMEOUT + } else { + timeout_ms + }; + self + } } diff --git a/libdd-profiling-ffi/src/exporter.rs b/libdd-profiling-ffi/src/exporter.rs index f5cc194b36..0b9024f83c 100644 --- a/libdd-profiling-ffi/src/exporter.rs +++ b/libdd-profiling-ffi/src/exporter.rs @@ -7,11 +7,9 @@ use function_name::named; use libdd_common::tag::Tag; use libdd_common_ffi::slice::{AsBytes, ByteSlice, CharSlice, Slice}; -use libdd_common_ffi::{ - wrap_with_ffi_result, wrap_with_void_ffi_result, Handle, Result, ToInner, VoidResult, -}; +use libdd_common_ffi::{wrap_with_ffi_result, Handle, Result, ToInner}; use libdd_profiling::exporter; -use libdd_profiling::exporter::{ProfileExporter, Request}; +use libdd_profiling::exporter::ProfileExporter; use libdd_profiling::internal::EncodedProfile; use std::borrow::Cow; use std::str::FromStr; @@ -21,8 +19,8 @@ type TokioCancellationToken = tokio_util::sync::CancellationToken; #[allow(dead_code)] #[repr(C)] pub enum ProfilingEndpoint<'a> { - Agent(CharSlice<'a>), - Agentless(CharSlice<'a>, CharSlice<'a>), + Agent(CharSlice<'a>, u64), + Agentless(CharSlice<'a>, CharSlice<'a>, u64), File(CharSlice<'a>), } @@ -47,21 +45,27 @@ pub struct HttpStatus(u16); /// Creates an endpoint that uses the agent. /// # Arguments /// * `base_url` - Contains a URL with scheme, host, and port e.g. "https://agent:8126/". +/// * `timeout_ms` - Timeout in milliseconds. Use 0 for default timeout (3000ms). #[no_mangle] -pub extern "C" fn ddog_prof_Endpoint_agent(base_url: CharSlice) -> ProfilingEndpoint { - ProfilingEndpoint::Agent(base_url) +pub extern "C" fn ddog_prof_Endpoint_agent( + base_url: CharSlice, + timeout_ms: u64, +) -> ProfilingEndpoint { + ProfilingEndpoint::Agent(base_url, timeout_ms) } /// Creates an endpoint that uses the Datadog intake directly aka agentless. /// # Arguments /// * `site` - Contains a host and port e.g. "datadoghq.com". /// * `api_key` - Contains the Datadog API key. +/// * `timeout_ms` - Timeout in milliseconds. Use 0 for default timeout (3000ms). #[no_mangle] pub extern "C" fn ddog_prof_Endpoint_agentless<'a>( site: CharSlice<'a>, api_key: CharSlice<'a>, + timeout_ms: u64, ) -> ProfilingEndpoint<'a> { - ProfilingEndpoint::Agentless(site, api_key) + ProfilingEndpoint::Agentless(site, api_key, timeout_ms) } /// Creates an endpoint that writes to a file. @@ -92,17 +96,18 @@ pub unsafe fn try_to_endpoint( // convert to utf8 losslessly -- URLs and API keys should all be ASCII, so // a failed result is likely to be an error. match endpoint { - ProfilingEndpoint::Agent(url) => { + ProfilingEndpoint::Agent(url, timeout_ms) => { let base_url = try_to_url(url)?; - exporter::config::agent(base_url) + Ok(exporter::config::agent(base_url)?.with_timeout(timeout_ms)) } - ProfilingEndpoint::Agentless(site, api_key) => { + ProfilingEndpoint::Agentless(site, api_key, timeout_ms) => { let site_str = site.try_to_utf8()?; let api_key_str = api_key.try_to_utf8()?; - exporter::config::agentless( + Ok(exporter::config::agentless( Cow::Owned(site_str.to_owned()), Cow::Owned(api_key_str.to_owned()), - ) + )? + .with_timeout(timeout_ms)) } ProfilingEndpoint::File(filename) => { let filename = filename.try_to_utf8()?; @@ -155,22 +160,6 @@ pub unsafe extern "C" fn ddog_prof_Exporter_new( }) } -/// Sets the value for the exporter's timeout. -/// # Arguments -/// * `exporter` - ProfileExporter instance. -/// * `timeout_ms` - timeout in milliseconds. -#[no_mangle] -#[named] -#[must_use] -pub unsafe extern "C" fn ddog_prof_Exporter_set_timeout( - mut exporter: *mut Handle, - timeout_ms: u64, -) -> VoidResult { - wrap_with_void_ffi_result!({ - exporter.to_inner_mut()?.set_timeout(timeout_ms); - }) -} - /// # Safety /// The `exporter` may be null, but if non-null the pointer must point to a /// valid `ddog_prof_Exporter_Request` object made by the Rust Global @@ -194,62 +183,6 @@ unsafe fn into_vec_files<'a>(slice: Slice<'a, File>) -> Vec> .collect() } -/// If successful, builds a `ddog_prof_Exporter_Request` object based on the -/// profile data supplied. If unsuccessful, it returns an error message. -/// -/// For details on the `optional_internal_metadata_json`, please reference the Datadog-internal -/// "RFC: Attaching internal metadata to pprof profiles". -/// If you use this parameter, please update the RFC with your use-case, so we can keep track of how -/// this is getting used. -/// -/// For details on the `optional_info_json`, please reference the Datadog-internal -/// "RFC: Pprof System Info Support". -/// -/// # Safety -/// The `exporter`, `optional_additional_stats`, and `optional_endpoint_stats` args should be -/// valid objects created by this module. -/// NULL is allowed for `optional_additional_tags`, `optional_endpoints_stats`, -/// `optional_internal_metadata_json` and `optional_info_json`. -/// Consumes the `SerializedProfile` -#[no_mangle] -#[must_use] -#[named] -pub unsafe extern "C" fn ddog_prof_Exporter_Request_build( - mut exporter: *mut Handle, - mut profile: *mut Handle, - files_to_compress_and_export: Slice, - files_to_export_unmodified: Slice, - optional_additional_tags: Option<&libdd_common_ffi::Vec>, - optional_process_tags: Option<&CharSlice>, - optional_internal_metadata_json: Option<&CharSlice>, - optional_info_json: Option<&CharSlice>, -) -> Result> { - wrap_with_ffi_result!({ - let exporter = exporter.to_inner_mut()?; - let profile = *profile.take()?; - let files_to_compress_and_export = into_vec_files(files_to_compress_and_export); - let files_to_export_unmodified = into_vec_files(files_to_export_unmodified); - let tags = optional_additional_tags.map(|tags| tags.iter().cloned().collect()); - // Convert CharSlice to &str without copying - let process_tags_str = optional_process_tags - .map(|cs| cs.try_to_utf8()) - .transpose()?; - let internal_metadata = parse_json("internal_metadata", optional_internal_metadata_json)?; - let info = parse_json("info", optional_info_json)?; - - let request = exporter.build( - profile, - files_to_compress_and_export.as_slice(), - files_to_export_unmodified.as_slice(), - tags.as_ref(), - process_tags_str, - internal_metadata, - info, - )?; - anyhow::Ok(request.into()) - }) -} - unsafe fn parse_json( string_id: &str, json_string: Option<&CharSlice>, @@ -271,38 +204,56 @@ unsafe fn parse_json( } } -/// # Safety -/// Each pointer of `request` may be null, but if non-null the inner-most -/// pointer must point to a valid `ddog_prof_Exporter_Request` object made by -/// the Rust Global allocator. -#[no_mangle] -pub unsafe extern "C" fn ddog_prof_Exporter_Request_drop(mut request: *mut Handle) { - // Technically, this function has been designed so if it's double-dropped - // then it's okay, but it's not something that should be relied on. - drop(request.take()) -} - -/// Sends the request, returning the HttpStatus. +/// Builds a request and sends it, returning the HttpStatus. +/// This is a more efficient version of calling `ddog_prof_Exporter_Request_build` +/// followed by `ddog_prof_Exporter_send`, as it avoids exposing the intermediate +/// `Request` object. /// /// # Arguments -/// * `exporter` - Borrows the exporter for sending the request. -/// * `request` - Takes ownership of the request, replacing it with a null pointer. This is why it -/// takes a double-pointer, rather than a single one. -/// * `cancel` - Borrows the cancel, if any. +/// * `exporter` - Borrows the exporter. +/// * `profile` - Takes ownership of the profile. +/// * `files_to_compress_and_export` - Files to compress and attach to the profile. +/// * `optional_additional_tags` - Additional tags to include with this profile. +/// * `optional_process_tags` - Process-level tags as a comma-separated string. +/// * `optional_internal_metadata_json` - Internal metadata as a JSON string. +/// * `optional_info_json` - System info as a JSON string. +/// * `cancel` - Optional cancellation token. /// /// # Safety -/// All non-null arguments MUST have been created by created by apis in this module. +/// All non-null arguments MUST have been created by APIs in this module. #[no_mangle] #[must_use] #[named] -pub unsafe extern "C" fn ddog_prof_Exporter_send( +pub unsafe extern "C" fn ddog_prof_Exporter_send_blocking( mut exporter: *mut Handle, - mut request: *mut Handle, + mut profile: *mut Handle, + files_to_compress_and_export: Slice, + optional_additional_tags: Option<&libdd_common_ffi::Vec>, + optional_process_tags: Option<&CharSlice>, + optional_internal_metadata_json: Option<&CharSlice>, + optional_info_json: Option<&CharSlice>, mut cancel: *mut Handle, ) -> Result { wrap_with_ffi_result!({ - let request = *request.take().context("request")?; let exporter = exporter.to_inner_mut()?; + let profile = *profile.take()?; + let files_to_compress_and_export = into_vec_files(files_to_compress_and_export); + let tags = optional_additional_tags.map(|tags| tags.iter().cloned().collect()); + let process_tags_str = optional_process_tags + .map(|cs| cs.try_to_utf8()) + .transpose()?; + let internal_metadata = parse_json("internal_metadata", optional_internal_metadata_json)?; + let info = parse_json("info", optional_info_json)?; + + let request = exporter.build( + profile, + files_to_compress_and_export.as_slice(), + tags.as_ref(), + process_tags_str, + internal_metadata, + info, + )?; + let cancel = cancel.to_inner_mut().ok(); let response = exporter.send(request, cancel.as_deref())?; @@ -385,10 +336,8 @@ pub unsafe extern "C" fn ddog_CancellationToken_drop( #[cfg(test)] mod tests { use super::*; - use http_body_util::BodyExt; use libdd_common::tag; use libdd_common_ffi::Slice; - use serde_json::json; fn profiling_library_name() -> CharSlice<'static> { CharSlice::from("dd-trace-foo") @@ -410,29 +359,6 @@ mod tests { CharSlice::from(base_url()) } - fn parsed_event_json(request: libdd_common_ffi::Result>) -> serde_json::Value { - // Safety: This is a test - let request = unsafe { request.unwrap().take().unwrap() }; - // Really hacky way of getting the event.json file contents, because I didn't want to - // implement a full multipart parser and didn't find a particularly good - // alternative. If you do figure out a better way, there's another copy of this code - // in the profiling tests, please update there too :) - let body = request.body(); - let body_bytes: String = String::from_utf8_lossy( - &futures::executor::block_on(body.collect()) - .unwrap() - .to_bytes(), - ) - .to_string(); - let event_json = body_bytes - .lines() - .skip_while(|line| !line.contains(r#"filename="event.json""#)) - .nth(2) - .unwrap(); - - serde_json::from_str(event_json).unwrap() - } - #[test] // This test invokes an external function SecTrustSettingsCopyCertificates // which Miri cannot evaluate. @@ -446,7 +372,7 @@ mod tests { profiling_library_version(), family(), Some(&tags), - ddog_prof_Endpoint_agent(endpoint()), + ddog_prof_Endpoint_agent(endpoint(), 0), ) }; @@ -463,194 +389,59 @@ mod tests { // This test invokes an external function SecTrustSettingsCopyCertificates // which Miri cannot evaluate. #[cfg_attr(miri, ignore)] - fn test_build() { + fn test_send_blocking() { let exporter_result = unsafe { ddog_prof_Exporter_new( profiling_library_name(), profiling_library_version(), family(), None, - ddog_prof_Endpoint_agent(endpoint()), + ddog_prof_Endpoint_agent(endpoint(), 0), ) }; let mut exporter = exporter_result.unwrap(); let profile = &mut EncodedProfile::test_instance().unwrap().into(); - let timeout_milliseconds = 90; - unsafe { - ddog_prof_Exporter_set_timeout(&mut exporter, timeout_milliseconds).unwrap(); - } - let build_result = unsafe { - ddog_prof_Exporter_Request_build( + // This should fail with a connection error since there's no server, + // but it validates that the function works end-to-end + let send_result = unsafe { + ddog_prof_Exporter_send_blocking( &mut exporter, profile, Slice::empty(), - Slice::empty(), None, None, None, None, + &mut Handle::empty(), ) }; - let parsed_event_json = parsed_event_json(build_result); - - assert_eq!(parsed_event_json["attachments"], json!(["profile.pprof"])); - assert_eq!(parsed_event_json["endpoint_counts"], json!(null)); - #[cfg(not(windows))] - { - assert_eq!( - parsed_event_json["start"], - json!("1970-01-01T00:00:12.000000034Z") - ); - assert_eq!( - parsed_event_json["end"], - json!("1970-01-01T00:00:56.000000078Z") - ); - } - // Windows is less accurate on timestamps - #[cfg(windows)] - { - assert_eq!( - parsed_event_json["start"], - json!("1970-01-01T00:00:12.000000000Z") - ); - assert_eq!( - parsed_event_json["end"], - json!("1970-01-01T00:00:56.000000000Z") - ); - } - - assert_eq!(parsed_event_json["family"], json!("native")); - - let internal = parsed_event_json.get("internal").unwrap(); - assert!(internal.is_object()); - - let libdd_version = internal.get("libdatadog_version"); - assert!(libdd_version.is_some()); - assert!(libdd_version.unwrap().is_string()); - assert_eq!(parsed_event_json["version"], json!("4")); - - // TODO: Assert on contents of attachments, as well as on the headers/configuration for the - // exporter - } - - #[test] - // This test invokes an external function SecTrustSettingsCopyCertificates - // which Miri cannot evaluate. - #[cfg_attr(miri, ignore)] - fn test_build_with_internal_metadata() { - let exporter_result = unsafe { - ddog_prof_Exporter_new( - profiling_library_name(), - profiling_library_version(), - family(), - None, - ddog_prof_Endpoint_agent(endpoint()), - ) - }; - - let mut exporter = exporter_result.unwrap(); - - let profile = &mut EncodedProfile::test_instance().unwrap().into(); - let timeout_milliseconds = 90; - unsafe { - ddog_prof_Exporter_set_timeout(&mut exporter, timeout_milliseconds).unwrap(); - } - - let raw_internal_metadata = CharSlice::from( - r#" - { - "no_signals_workaround_enabled": "true", - "execution_trace_enabled": "false", - "extra object": {"key": [1, 2, true]} + // Expect an error since no server is running + match send_result { + Result::Err(_) => { + // Expected - no server running + } + Result::Ok(_) => { + panic!("Expected error since no server is running"); } - "#, - ); - - let build_result = unsafe { - ddog_prof_Exporter_Request_build( - &mut exporter, - profile, - Slice::empty(), - Slice::empty(), - None, - None, - Some(&raw_internal_metadata), - None, - ) - }; - - let parsed_event_json = parsed_event_json(build_result); - - let internal = parsed_event_json.get("internal").unwrap(); - - assert_eq!(internal["no_signals_workaround_enabled"], "true"); - assert_eq!(internal["execution_trace_enabled"], "false"); - assert_eq!(internal["extra object"], json!({"key": [1, 2, true]})); - assert!(internal["libdatadog_version"].is_string()); - assert_eq!(internal["libdatadog_version"], env!("CARGO_PKG_VERSION")); - } - - #[test] - // This test invokes an external function SecTrustSettingsCopyCertificates - // which Miri cannot evaluate. - #[cfg_attr(miri, ignore)] - fn test_build_with_process_tags() { - let exporter_result = unsafe { - ddog_prof_Exporter_new( - profiling_library_name(), - profiling_library_version(), - family(), - None, - ddog_prof_Endpoint_agent(endpoint()), - ) - }; - - let mut exporter = exporter_result.unwrap(); - - let profile = &mut EncodedProfile::test_instance().unwrap().into(); - let timeout_milliseconds = 90; - unsafe { - ddog_prof_Exporter_set_timeout(&mut exporter, timeout_milliseconds).unwrap(); } - - // Create process_tags as CharSlice - let expected_process_tags_str = "entrypoint.basedir:net10.0,entrypoint.name:buggybits.program,entrypoint.workdir:this_folder,runtime_platform:x86_64-pc-windows-msvc"; - let expected_process_tags = CharSlice::from(expected_process_tags_str); - - let build_result = unsafe { - ddog_prof_Exporter_Request_build( - &mut exporter, - profile, - Slice::empty(), - Slice::empty(), - None, - Some(&expected_process_tags), - None, - None, - ) - }; - - let parsed_event_json = parsed_event_json(build_result); - - assert_eq!(parsed_event_json["process_tags"], expected_process_tags_str); } #[test] // This test invokes an external function SecTrustSettingsCopyCertificates // which Miri cannot evaluate. #[cfg_attr(miri, ignore)] - fn test_build_with_invalid_internal_metadata() { + fn test_send_blocking_with_metadata() { let exporter_result = unsafe { ddog_prof_Exporter_new( profiling_library_name(), profiling_library_version(), family(), None, - ddog_prof_Endpoint_agent(endpoint()), + ddog_prof_Endpoint_agent(endpoint(), 0), ) }; @@ -658,209 +449,33 @@ mod tests { let profile = &mut EncodedProfile::test_instance().unwrap().into(); - let timeout_milliseconds = 90; - unsafe { - ddog_prof_Exporter_set_timeout(&mut exporter, timeout_milliseconds).unwrap(); - } - - let raw_internal_metadata = CharSlice::from("this is not a valid json string"); + let raw_internal_metadata = CharSlice::from(r#"{"test": "value"}"#); + let raw_info = CharSlice::from(r#"{"runtime": {"engine": "test"}}"#); + let process_tags = CharSlice::from("tag1:value1,tag2:value2"); - let build_result = unsafe { - ddog_prof_Exporter_Request_build( + // This should fail with a connection error since there's no server, + // but it validates that the function accepts all parameters + let send_result = unsafe { + ddog_prof_Exporter_send_blocking( &mut exporter, profile, Slice::empty(), - Slice::empty(), - None, None, + Some(&process_tags), Some(&raw_internal_metadata), - None, - ) - }; - - let message = build_result.unwrap_err(); - assert!(String::from(message).starts_with( - r#"ddog_prof_Exporter_Request_build failed: Failed to parse contents of internal_metadata json string (`this is not a valid json string`)"# - )); - } - - #[test] - // This test invokes an external function SecTrustSettingsCopyCertificates - // which Miri cannot evaluate. - #[cfg_attr(miri, ignore)] - fn test_build_with_info() { - let exporter_result = unsafe { - ddog_prof_Exporter_new( - profiling_library_name(), - profiling_library_version(), - family(), - None, - ddog_prof_Endpoint_agent(endpoint()), - ) - }; - - let mut exporter = exporter_result.unwrap(); - - let profile = &mut EncodedProfile::test_instance().unwrap().into(); - let timeout_milliseconds = 90; - unsafe { - ddog_prof_Exporter_set_timeout(&mut exporter, timeout_milliseconds).unwrap(); - } - - let raw_info = CharSlice::from( - r#" - { - "application": { - "start_time": "2024-01-24T11:17:22+0000", - "env": "test" - }, - "platform": { - "kernel": "Darwin Kernel Version 22.5.0", - "hostname": "COMP-XSDF" - }, - "runtime": { - "engine": "ruby", - "version": "3.2.0" - }, - "profiler": { - "version": "1.32.0", - "libdatadog": "1.2.3-darwin", - "settings": { - "profiling": { - "advanced": { - "allocation": true, - "heap": true - } - } - } - } - } - "#, - ); - - let build_result = unsafe { - ddog_prof_Exporter_Request_build( - &mut exporter, - profile, - Slice::empty(), - Slice::empty(), - None, - None, - None, - Some(&raw_info), - ) - }; - - let parsed_event_json = parsed_event_json(build_result); - - assert_eq!( - parsed_event_json["info"], - json!({ - "application": { - "start_time": "2024-01-24T11:17:22+0000", - "env": "test", - }, - "platform": { - "kernel": "Darwin Kernel Version 22.5.0", - "hostname": "COMP-XSDF" - }, - "runtime": { - "engine": "ruby", - "version": "3.2.0" - }, - "profiler": { - "version": "1.32.0", - "libdatadog": "1.2.3-darwin", - "settings": { - "profiling": { - "advanced": { - "allocation": true, - "heap": true - } - } - } - } - }) - ); - } - - #[test] - // This test invokes an external function SecTrustSettingsCopyCertificates - // which Miri cannot evaluate. - #[cfg_attr(miri, ignore)] - fn test_build_with_invalid_info() { - let exporter_result = unsafe { - ddog_prof_Exporter_new( - profiling_library_name(), - profiling_library_version(), - family(), - None, - ddog_prof_Endpoint_agent(endpoint()), - ) - }; - - let exporter = &mut exporter_result.unwrap(); - - let profile = &mut EncodedProfile::test_instance().unwrap().into(); - let timeout_milliseconds = 90; - unsafe { - ddog_prof_Exporter_set_timeout(exporter, timeout_milliseconds).unwrap(); - } - - let raw_info = CharSlice::from("this is not a valid json string"); - - let build_result = unsafe { - ddog_prof_Exporter_Request_build( - exporter, - profile, - Slice::empty(), - Slice::empty(), - None, - None, - None, Some(&raw_info), + &mut Handle::empty(), ) }; - let message = build_result.unwrap_err(); - assert!(String::from(message).starts_with( - r#"ddog_prof_Exporter_Request_build failed: Failed to parse contents of info json string (`this is not a valid json string`)"# - )); - } - - #[test] - fn test_build_failure() { - let profile = &mut EncodedProfile::test_instance().unwrap().into(); - let exporter = &mut Handle::empty(); - let build_result = unsafe { - ddog_prof_Exporter_Request_build( - exporter, // No exporter, will fail - profile, - Slice::empty(), - Slice::empty(), - None, - None, - None, - None, - ) - }; - - build_result.unwrap_err(); - } - - #[test] - fn send_fails_with_null() { - let exporter = &mut Handle::empty(); - let request = &mut Handle::empty(); - let cancel = &mut Handle::empty(); - unsafe { - let error = ddog_prof_Exporter_send(exporter, request, cancel) - .unwrap_err() - .to_string(); - assert_eq!( - "ddog_prof_Exporter_send failed: request: inner pointer was null, indicates use after free", - error - ); + // Expect an error since no server is running + match send_result { + Result::Err(_) => { + // Expected - no server running + } + Result::Ok(_) => { + panic!("Expected error since no server is running"); + } } } } diff --git a/libdd-profiling/src/cxx.rs b/libdd-profiling/src/cxx.rs index 5432cd88d2..6709ca75ae 100644 --- a/libdd-profiling/src/cxx.rs +++ b/libdd-profiling/src/cxx.rs @@ -638,7 +638,6 @@ impl ProfileExporter { let request = self.inner.build( encoded, &files_to_compress_vec, - &[], // files_to_export_unmodified - empty additional_tags_vec.as_ref(), process_tags_opt, internal_metadata_json, diff --git a/libdd-profiling/src/exporter/mod.rs b/libdd-profiling/src/exporter/mod.rs index f8c04848ee..cfce951a3b 100644 --- a/libdd-profiling/src/exporter/mod.rs +++ b/libdd-profiling/src/exporter/mod.rs @@ -185,7 +185,6 @@ impl ProfileExporter { &self, profile: EncodedProfile, files_to_compress_and_export: &[File], - files_to_export_unmodified: &[File], additional_tags: Option<&Vec>, process_tags: Option<&str>, internal_metadata: Option, @@ -240,7 +239,6 @@ impl ProfileExporter { let attachments: Vec = files_to_compress_and_export .iter() - .chain(files_to_export_unmodified.iter()) .map(|file| file.name.to_owned()) .chain(iter::once("profile.pprof".to_string())) .collect(); @@ -303,15 +301,6 @@ impl ProfileExporter { form.add_reader_file(file.name, Cursor::new(encoded), file.name); } - for file in files_to_export_unmodified { - let encoded = file.bytes.to_vec(); - /* The Datadog RFC examples strip off the file extension, but the exact behavior - * isn't specified. This does the simple thing of using the filename - * without modification for the form name because intake does not care - * about these name of the form field for these attachments. - */ - form.add_reader_file(file.name, Cursor::new(encoded), file.name) - } // Add the actual pprof form.add_reader_file( "profile.pprof", @@ -346,10 +335,6 @@ impl ProfileExporter { .runtime .block_on(request.send(&self.exporter.client, cancel)) } - - pub fn set_timeout(&mut self, timeout_ms: u64) { - self.endpoint.timeout_ms = timeout_ms; - } } impl Exporter { diff --git a/libdd-profiling/tests/form.rs b/libdd-profiling/tests/form.rs index fff3764282..321e862be3 100644 --- a/libdd-profiling/tests/form.rs +++ b/libdd-profiling/tests/form.rs @@ -5,7 +5,7 @@ use libdd_profiling::exporter::{ProfileExporter, Request}; use libdd_profiling::internal::EncodedProfile; fn multipart( - exporter: &mut ProfileExporter, + exporter: &ProfileExporter, internal_metadata: Option, info: Option, process_tags: Option<&str>, @@ -13,16 +13,11 @@ fn multipart( let profile = EncodedProfile::test_instance().expect("To get a profile"); let files_to_compress_and_export = &[]; - let files_to_export_unmodified = &[]; - - let timeout: u64 = 10_000; - exporter.set_timeout(timeout); let request = exporter .build( profile, files_to_compress_and_export, - files_to_export_unmodified, None, process_tags, internal_metadata, @@ -30,8 +25,13 @@ fn multipart( ) .expect("request to be built"); + // Verify timeout is set from endpoint + let expected_timeout = 10_000; let actual_timeout = request.timeout().expect("timeout to exist"); - assert_eq!(actual_timeout, std::time::Duration::from_millis(timeout)); + assert_eq!( + actual_timeout, + std::time::Duration::from_millis(expected_timeout) + ); request } @@ -76,8 +76,9 @@ mod tests { let profiling_library_name = "dd-trace-foo"; let profiling_library_version = "1.2.3"; let base_url = "http://localhost:8126".parse().expect("url to parse"); - let endpoint = config::agent(base_url).expect("endpoint to construct"); - let mut exporter = ProfileExporter::new( + let mut endpoint = config::agent(base_url).expect("endpoint to construct"); + endpoint.timeout_ms = 10_000; + let exporter = ProfileExporter::new( profiling_library_name, profiling_library_version, "php", @@ -86,7 +87,7 @@ mod tests { ) .expect("exporter to construct"); - let request = multipart(&mut exporter, None, None, None); + let request = multipart(&exporter, None, None, None); assert_eq!( request.uri().to_string(), @@ -140,8 +141,9 @@ mod tests { let profiling_library_name = "dd-trace-foo"; let profiling_library_version = "1.2.3"; let base_url = "http://localhost:8126".parse().expect("url to parse"); - let endpoint = config::agent(base_url).expect("endpoint to construct"); - let mut exporter = ProfileExporter::new( + let mut endpoint = config::agent(base_url).expect("endpoint to construct"); + endpoint.timeout_ms = 10_000; + let exporter = ProfileExporter::new( profiling_library_name, profiling_library_version, "php", @@ -156,7 +158,7 @@ mod tests { "extra object": {"key": [1, 2, true]}, "libdatadog_version": env!("CARGO_PKG_VERSION"), }); - let request = multipart(&mut exporter, Some(internal_metadata.clone()), None, None); + let request = multipart(&exporter, Some(internal_metadata.clone()), None, None); let parsed_event_json = parsed_event_json(request); assert_eq!(parsed_event_json["internal"], internal_metadata); @@ -170,8 +172,9 @@ mod tests { let profiling_library_name = "dd-trace-foo"; let profiling_library_version = "1.2.3"; let base_url = "http://localhost:8126".parse().expect("url to parse"); - let endpoint = config::agent(base_url).expect("endpoint to construct"); - let mut exporter = ProfileExporter::new( + let mut endpoint = config::agent(base_url).expect("endpoint to construct"); + endpoint.timeout_ms = 10_000; + let exporter = ProfileExporter::new( profiling_library_name, profiling_library_version, "php", @@ -181,7 +184,7 @@ mod tests { .expect("exporter to construct"); let expected_process_tags = "entrypoint.basedir:net10.0,entrypoint.name:buggybits.program,entrypoint.workdir:this_folder,runtime_platform:x86_64-pc-windows-msvc"; - let request = multipart(&mut exporter, None, None, Some(expected_process_tags)); + let request = multipart(&exporter, None, None, Some(expected_process_tags)); let parsed_event_json = parsed_event_json(request); assert_eq!(parsed_event_json["process_tags"], expected_process_tags); @@ -195,8 +198,9 @@ mod tests { let profiling_library_name = "dd-trace-foo"; let profiling_library_version = "1.2.3"; let base_url = "http://localhost:8126".parse().expect("url to parse"); - let endpoint = config::agent(base_url).expect("endpoint to construct"); - let mut exporter = ProfileExporter::new( + let mut endpoint = config::agent(base_url).expect("endpoint to construct"); + endpoint.timeout_ms = 10_000; + let exporter = ProfileExporter::new( profiling_library_name, profiling_library_version, "php", @@ -221,7 +225,7 @@ mod tests { "settings": {} } }); - let request = multipart(&mut exporter, None, Some(info.clone()), None); + let request = multipart(&exporter, None, Some(info.clone()), None); let parsed_event_json = parsed_event_json(request); assert_eq!(parsed_event_json["info"], info); @@ -235,8 +239,10 @@ mod tests { let profiling_library_name = "dd-trace-foo"; let profiling_library_version = "1.2.3"; let api_key = "1234567890123456789012"; - let endpoint = config::agentless("datadoghq.com", api_key).expect("endpoint to construct"); - let mut exporter = ProfileExporter::new( + let mut endpoint = + config::agentless("datadoghq.com", api_key).expect("endpoint to construct"); + endpoint.timeout_ms = 10_000; + let exporter = ProfileExporter::new( profiling_library_name, profiling_library_version, "php", @@ -245,7 +251,7 @@ mod tests { ) .expect("exporter to construct"); - let request = multipart(&mut exporter, None, None, None); + let request = multipart(&exporter, None, None, None); assert_eq!( request.uri().to_string(),