diff --git a/Cargo.lock b/Cargo.lock index 24bd9356376..1b4fee2a5bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,15 +19,15 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.2.15", + "getrandom 0.3.2", "once_cell", "version_check 0.9.5", - "zerocopy 0.7.35", + "zerocopy 0.8.24", ] [[package]] @@ -118,9 +118,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arc-swap" @@ -912,9 +912,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -924,9 +924,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -934,7 +934,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -1224,9 +1224,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -1601,7 +1601,7 @@ dependencies = [ "datadog-profiling", "ddcommon 18.0.0", "env_logger 0.11.6", - "indexmap 2.7.1", + "http 1.2.0", "lazy_static", "libc 0.2.173", "log", @@ -1609,10 +1609,11 @@ dependencies = [ "perfcnt", "rand 0.8.5", "rand_distr", + "reqwest 0.12.22", "rustc-hash 1.1.0", "rustc_version", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", "tracing-subscriber", "uuid", @@ -2928,7 +2929,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 0.26.7", ] [[package]] @@ -2975,7 +2976,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 0.26.7", ] [[package]] @@ -2993,16 +2994,21 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http 1.2.0", "http-body 1.0.1", "hyper 1.6.0", + "ipnet", + "libc 0.2.173", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -3232,6 +3238,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.8.0", + "cfg-if", + "libc 0.2.173", +] + [[package]] name = "ipconfig" version = "0.3.2" @@ -3250,6 +3267,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-terminal" version = "0.4.13" @@ -4141,7 +4168,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "059a34f111a9dee2ce1ac2826a68b24601c4298cfeb1a587c3cb493d5ab46f52" dependencies = [ - "libc 0.1.12", + "libc 0.2.173", "nix", ] @@ -4369,7 +4396,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustls", "socket2", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tracing", "web-time", @@ -4389,7 +4416,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.11", + "thiserror 2.0.12", "tinyvec", "tracing", "web-time", @@ -4714,10 +4741,50 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.26.7", "windows-registry", ] +[[package]] +name = "reqwest" +version = "0.12.22" +source = "git+https://github.com/seanmonstar/reqwest.git?rev=10ad5afa5985c9ce8f486cefa27e258a3e925f8b#10ad5afa5985c9ce8f486cefa27e258a3e925f8b" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower 0.5.2", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 1.0.1", +] + [[package]] name = "resolv-conf" version = "0.7.1" @@ -5284,9 +5351,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc 0.2.173", "windows-sys 0.52.0", @@ -5580,7 +5647,7 @@ dependencies = [ "memchr", "parse-display", "pin-project-lite", - "reqwest", + "reqwest 0.12.15", "serde", "serde_json", "serde_with", @@ -5602,11 +5669,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -5622,9 +5689,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", @@ -5754,17 +5821,19 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.43.0" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc 0.2.173", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", "tracing", @@ -5945,6 +6014,24 @@ dependencies = [ "tower-service", ] +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.8.0", + "bytes", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -6321,6 +6408,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "4.4.2" @@ -6474,9 +6570,9 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] diff --git a/profiling/Cargo.toml b/profiling/Cargo.toml index a3daa239213..06630693029 100644 --- a/profiling/Cargo.toml +++ b/profiling/Cargo.toml @@ -24,7 +24,7 @@ datadog-profiling = { git = "https://github.com/DataDog/libdatadog", tag = "v18. ddcommon = { git = "https://github.com/DataDog/libdatadog", tag = "v18.0.0" } datadog-library-config-ffi = { git = "https://github.com/DataDog/libdatadog", tag = "v18.0.0" } env_logger = { version = "0.11", default-features = false } -indexmap = { version = "2.2" } +http = "1" lazy_static = { version = "1.4" } libc = "0.2" # TRACE set to max to support runtime configuration. @@ -38,6 +38,12 @@ thiserror = "2" tracing = { version = "0.1", optional = true } uuid = { version = "1.0", features = ["v4"] } +[dependencies.reqwest] +git = "https://github.com/seanmonstar/reqwest.git" +rev = "10ad5afa5985c9ce8f486cefa27e258a3e925f8b" +default-features = false +features = ["blocking", "multipart", "rustls-tls"] + [dependencies.tracing-subscriber] version = "0.3" optional = true diff --git a/profiling/src/profiling/uploader.rs b/profiling/src/profiling/uploader.rs index 619f989d821..d8aba685459 100644 --- a/profiling/src/profiling/uploader.rs +++ b/profiling/src/profiling/uploader.rs @@ -1,19 +1,24 @@ -#[cfg(feature = "allocation_profiling")] -use crate::allocation::{ALLOCATION_PROFILING_COUNT, ALLOCATION_PROFILING_SIZE}; use crate::config::AgentEndpoint; -#[cfg(feature = "exception_profiling")] -use crate::exception::EXCEPTION_PROFILING_EXCEPTION_COUNT; use crate::profiling::{UploadMessage, UploadRequest}; use crate::{PROFILER_NAME_STR, PROFILER_VERSION_STR}; use chrono::{DateTime, Utc}; use crossbeam_channel::{select, Receiver}; use ddcommon::Endpoint; use log::{debug, info, warn}; +use reqwest::blocking::{multipart, ClientBuilder}; +use reqwest::header::{HeaderMap, HeaderValue}; use serde_json::json; use std::borrow::Cow; +use std::ops::Sub; use std::str; use std::sync::{Arc, Barrier}; +#[cfg(feature = "allocation_profiling")] +use crate::allocation::{ALLOCATION_PROFILING_COUNT, ALLOCATION_PROFILING_SIZE}; + +#[cfg(feature = "exception_profiling")] +use crate::exception::EXCEPTION_PROFILING_EXCEPTION_COUNT; + #[cfg(any( feature = "exception_profiling", feature = "allocation_profiling", @@ -84,36 +89,126 @@ impl Uploader { let index = message.index; let profile = message.profile; - let profiling_library_name: &str = &PROFILER_NAME_STR; - let profiling_library_version: &str = &PROFILER_VERSION_STR; - let agent_endpoint = &self.endpoint; - let endpoint = Endpoint::try_from(agent_endpoint)?; + // Serialize profile as compressed pprof + let serialized = + profile.serialize_into_compressed_pprof(Some(message.end_time), message.duration)?; + // Prepare multipart form with only profile.pprof + let mut form = multipart::Form::new().part( + "profile.pprof", + multipart::Part::bytes(serialized.buffer) + .file_name("profile.pprof") + .mime_str("application/octet-stream")?, + ); + + // Build tags string let tags = Some(Arc::unwrap_or_clone(index.tags)); - let mut exporter = datadog_profiling::exporter::ProfileExporter::new( - profiling_library_name, - profiling_library_version, - "php", - tags, - endpoint, - )?; + let mut tags_profiler = String::new(); + if let Some(tags_vec) = &tags { + for tag in tags_vec { + tags_profiler.push_str(tag.as_ref()); + tags_profiler.push(','); + } + } - let serialized = - profile.serialize_into_compressed_pprof(Some(message.end_time), message.duration)?; - exporter.set_timeout(10000); // 10 seconds in milliseconds - let request = exporter.build( - serialized, - &[], - &[], - None, - Self::create_internal_metadata(), - self.create_profiler_info(), - )?; - debug!("Sending profile to: {agent_endpoint}"); - let result = exporter.send(request, None)?; - Ok(result.status().as_u16()) + // Build internal metadata + let mut internal: serde_json::value::Value = + Self::create_internal_metadata().unwrap_or_else(|| json!({})); + internal["libdatadog_version"] = json!(env!("CARGO_PKG_VERSION")); + + // todo: use start_time + let start = message.end_time.sub(message.duration.unwrap()); + + // Build event JSON + let event = json!({ + "attachments": vec!["profile.pprof"], + "tags_profiler": tags_profiler, + "start": DateTime::::from(start).format("%Y-%m-%dT%H:%M:%S%.9fZ").to_string(), + "end": DateTime::::from(message.end_time).format("%Y-%m-%dT%H:%M:%S%.9fZ").to_string(), + "family": "php", + "version": "4", + "internal": internal, + "info": self.create_profiler_info().unwrap_or_else(|| json!({})), + }) + .to_string(); + + form = form.part( + "event", + multipart::Part::text(event) + .file_name("event.json") + .mime_str("application/json")?, + ); + + // Build headers + let mut headers = HeaderMap::new(); + headers.insert("Connection", HeaderValue::from_static("close")); + headers.insert( + "DD-EVP-ORIGIN", + HeaderValue::from_static(&PROFILER_NAME_STR), + ); + headers.insert( + "DD-EVP-ORIGIN-VERSION", + HeaderValue::from_static(&PROFILER_VERSION_STR), + ); + + // Build blocking client with optional unix socket + let mut client = ClientBuilder::new().timeout(std::time::Duration::from_secs(10)); + let mut endpoint = Endpoint::try_from(&self.endpoint)?.url; + if let AgentEndpoint::Socket(path) = &self.endpoint { + let mut parts = endpoint.into_parts(); + // reqwest doesn't support "unix" as a scheme, only "http" or + // "https", and "unix" urls as documented are currently HTTP only. + parts.scheme = Some(http::uri::Scheme::HTTP); + endpoint = parts.try_into()?; + let socket = path.display(); + debug!("using unix socket `{socket}` for profiling upload to URL `{endpoint}`"); + client = client.unix_socket(path.as_path()); + }; + let client = client.build()?; + + let response = client + .post(endpoint.to_string()) + .headers(headers) + .multipart(form) + .send()?; + + Ok(response.status().as_u16()) } + // fn upload(&self, message: Box) -> anyhow::Result { + // let index = message.index; + // let profile = message.profile; + // + // let profiling_library_name: &str = &PROFILER_NAME_STR; + // let profiling_library_version: &str = &PROFILER_VERSION_STR; + // let agent_endpoint = &self.endpoint; + // let endpoint = Endpoint::try_from(agent_endpoint)?; + // + // let tags = Some(Arc::unwrap_or_clone(index.tags)); + // let mut exporter = datadog_profiling::exporter::ProfileExporter::new( + // profiling_library_name, + // profiling_library_version, + // "php", + // tags, + // endpoint, + // )?; + // + // let serialized = + // profile.serialize_into_compressed_pprof(Some(message.end_time), message.duration)?; + // exporter.set_timeout(10000); // 10 seconds in milliseconds + // let request = exporter.build( + // serialized, + // &[], + // &[], + // None, + // Self::create_internal_metadata(), + // self.create_profiler_info(), + // )?; + // debug!("Sending profile to: {agent_endpoint}"); + // let result = exporter.send(request, None)?; + // Ok(result.status().as_u16()) + // } + pub fn run(self) { /* Safety: Called from Profiling::new, which is after config is * initialized, and before it's destroyed in mshutdown.