diff --git a/Cargo.lock b/Cargo.lock index d1d92a749..3ced437f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,6 +145,14 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "ddcommon-ffi" +version = "0.7.0-rc.1" +dependencies = [ + "anyhow", + "ddcommon", +] + [[package]] name = "ddprof" version = "0.7.0-rc.1" @@ -182,6 +190,7 @@ version = "0.7.0-rc.1" dependencies = [ "chrono", "ddcommon", + "ddcommon-ffi", "ddprof-exporter", "ddprof-profiles", "hyper", @@ -1091,6 +1100,14 @@ dependencies = [ "tokio", ] +[[package]] +name = "tools" +version = "0.1.0" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "tower-service" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index 068db5a83..7dee5b97a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,9 @@ members = [ "ddprof-ffi", "ddprof-profiles", "ddcommon", + "ddcommon-ffi", "ddtelemetry", + "tools", ] # https://doc.rust-lang.org/cargo/reference/resolver.html#feature-resolver-version-2 resolver = "2" diff --git a/LICENSE-3rdparty.yml b/LICENSE-3rdparty.yml index 691047c98..7ea2f6074 100644 --- a/LICENSE-3rdparty.yml +++ b/LICENSE-3rdparty.yml @@ -1,5 +1,5 @@ --- -root_name: "ddprof, ddprof-exporter, ddcommon, ddprof-profiles, ddprof-ffi" +root_name: "ddprof, ddprof-exporter, ddcommon, ddprof-profiles, ddprof-ffi, ddcommon-ffi, ddtelemetry" third_party_libraries: - package_name: aho-corasick package_version: 0.7.18 diff --git a/cmake/DDProfConfig.cmake.in b/cmake/DDProfConfig.cmake.in index a426082d0..2524d2aac 100644 --- a/cmake/DDProfConfig.cmake.in +++ b/cmake/DDProfConfig.cmake.in @@ -9,7 +9,7 @@ else () set(DDProf_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/..") endif () -find_path(DDProf_INCLUDE_DIR ddprof/ffi.h +find_path(DDProf_INCLUDE_DIR datadog/profiling.h HINTS ${DDProf_ROOT}/include ) diff --git a/ddcommon-ffi/Cargo.toml b/ddcommon-ffi/Cargo.toml new file mode 100644 index 000000000..9dfc698f2 --- /dev/null +++ b/ddcommon-ffi/Cargo.toml @@ -0,0 +1,14 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present Datadog, Inc. + +[package] +name = "ddcommon-ffi" +version = "0.7.0-rc.1" +edition = "2021" +license = "Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ddcommon = { path = "../ddcommon", version = "0.7.0-rc.1" } +anyhow = "1.0" diff --git a/ddcommon-ffi/cbindgen.toml b/ddcommon-ffi/cbindgen.toml new file mode 100644 index 000000000..ebdfc6310 --- /dev/null +++ b/ddcommon-ffi/cbindgen.toml @@ -0,0 +1,51 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present Datadog, Inc. + +language = "C" +tab_width = 2 +header = """// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present Datadog, Inc. +""" +include_guard = "DDOG_COMMON_H" +style = "both" + +no_includes = true +sys_includes = ["stdbool.h", "stddef.h", "stdint.h"] + +after_includes = """ + +#if defined(_MSC_VER) +#define DDOG_CHARSLICE_C(string) \\ +/* NOTE: Compilation fails if you pass in a char* instead of a literal */ {.ptr = "" string, .len = sizeof(string) - 1} +#else +#define DDOG_CHARSLICE_C(string) \\ +/* NOTE: Compilation fails if you pass in a char* instead of a literal */ ((ddog_CharSlice){ .ptr = "" string, .len = sizeof(string) - 1 }) +#endif + +#if defined(__cplusplus) && (__cplusplus >= 201703L) +# define DDOG_CHECK_RETURN [[nodiscard]] +#elif defined(_Check_return_) /* SAL */ +# define DDOG_CHECK_RETURN _Check_return_ +#elif (defined(__has_attribute) && __has_attribute(warn_unused_result)) || \\ + (defined(__GNUC__) && (__GNUC__ >= 4)) +# define DDOG_CHECK_RETURN __attribute__((__warn_unused_result__)) +#else +# define DDOG_CHECK_RETURN +#endif""" + +[export] +prefix = "ddog_" + +[export.mangle] +rename_types="SnakeCase" + +[enum] +prefix_with_name = true +rename_variants = "ScreamingSnakeCase" + +[fn] +must_use = "DDOG_CHECK_RETURN" + +[parse] +parse_deps = true +include = ["ddcommon"] diff --git a/ddcommon-ffi/src/lib.rs b/ddcommon-ffi/src/lib.rs new file mode 100644 index 000000000..7325a4368 --- /dev/null +++ b/ddcommon-ffi/src/lib.rs @@ -0,0 +1,9 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present Datadog, Inc. + +pub mod slice; +pub mod tags; +pub mod vec; + +pub use slice::Slice; +pub use vec::Vec; diff --git a/ddprof-ffi/src/slice.rs b/ddcommon-ffi/src/slice.rs similarity index 99% rename from ddprof-ffi/src/slice.rs rename to ddcommon-ffi/src/slice.rs index 3ae26a5a1..f52714603 100644 --- a/ddprof-ffi/src/slice.rs +++ b/ddcommon-ffi/src/slice.rs @@ -185,7 +185,7 @@ impl<'a> From<&'a str> for Slice<'a, c_char> { mod test { use std::os::raw::c_char; - use crate::*; + use crate::slice::*; #[test] fn slice_from_into_slice() { diff --git a/ddprof-ffi/src/tags.rs b/ddcommon-ffi/src/tags.rs similarity index 79% rename from ddprof-ffi/src/tags.rs rename to ddcommon-ffi/src/tags.rs index 77c68992c..53a0460e1 100644 --- a/ddprof-ffi/src/tags.rs +++ b/ddcommon-ffi/src/tags.rs @@ -1,17 +1,17 @@ // Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2022-Present Datadog, Inc. -use crate::{AsBytes, CharSlice}; +use crate::{slice::AsBytes, slice::CharSlice}; use ddcommon::tag::{parse_tags, Tag}; #[must_use] #[no_mangle] -pub extern "C" fn ddprof_ffi_Vec_tag_new() -> crate::Vec { +pub extern "C" fn ddog_Vec_tag_new() -> crate::Vec { crate::Vec::default() } #[no_mangle] -pub extern "C" fn ddprof_ffi_Vec_tag_drop(_: crate::Vec) {} +pub extern "C" fn ddog_Vec_tag_drop(_: crate::Vec) {} #[repr(C)] pub enum PushTagResult { @@ -20,7 +20,7 @@ pub enum PushTagResult { } #[no_mangle] -pub extern "C" fn ddprof_ffi_PushTagResult_drop(_: PushTagResult) {} +pub extern "C" fn ddog_PushTagResult_drop(_: PushTagResult) {} /// Creates a new Tag from the provided `key` and `value` by doing a utf8 /// lossy conversion, and pushes into the `vec`. The strings `key` and `value` @@ -32,7 +32,7 @@ pub extern "C" fn ddprof_ffi_PushTagResult_drop(_: PushTagResult) {} /// `.len` properties claim. #[must_use] #[no_mangle] -pub unsafe extern "C" fn ddprof_ffi_Vec_tag_push( +pub unsafe extern "C" fn ddog_Vec_tag_push( vec: &mut crate::Vec, key: CharSlice, value: CharSlice, @@ -59,7 +59,7 @@ pub struct ParseTagsResult { /// .len property. #[must_use] #[no_mangle] -pub unsafe extern "C" fn ddprof_ffi_Vec_tag_parse(string: CharSlice) -> ParseTagsResult { +pub unsafe extern "C" fn ddog_Vec_tag_parse(string: CharSlice) -> ParseTagsResult { let string = string.to_utf8_lossy(); let (tags, error) = parse_tags(string.as_ref()); ParseTagsResult { @@ -75,22 +75,21 @@ mod tests { #[test] fn empty_tag_name() { unsafe { - let mut tags = ddprof_ffi_Vec_tag_new(); - let result = - ddprof_ffi_Vec_tag_push(&mut tags, CharSlice::from(""), CharSlice::from("woof")); + let mut tags = ddog_Vec_tag_new(); + let result = ddog_Vec_tag_push(&mut tags, CharSlice::from(""), CharSlice::from("woof")); assert!(!matches!(result, PushTagResult::Ok)); } } #[test] fn test_lifetimes() { - let mut tags = ddprof_ffi_Vec_tag_new(); + let mut tags = ddog_Vec_tag_new(); unsafe { // make a string here so it has a scoped lifetime let key = String::from("key1"); { let value = String::from("value1"); - let result = ddprof_ffi_Vec_tag_push( + let result = ddog_Vec_tag_push( &mut tags, CharSlice::from(key.as_str()), CharSlice::from(value.as_str()), @@ -106,12 +105,9 @@ mod tests { #[test] fn test_get() { unsafe { - let mut tags = ddprof_ffi_Vec_tag_new(); - let result = ddprof_ffi_Vec_tag_push( - &mut tags, - CharSlice::from("sound"), - CharSlice::from("woof"), - ); + let mut tags = ddog_Vec_tag_new(); + let result = + ddog_Vec_tag_push(&mut tags, CharSlice::from("sound"), CharSlice::from("woof")); assert!(matches!(result, PushTagResult::Ok)); assert_eq!(1, tags.len()); assert_eq!("sound:woof", tags.get(0).unwrap().to_string()); @@ -123,7 +119,7 @@ mod tests { let dd_tags = "env:staging:east, tags:, env_staging:east"; // contains an error // SAFETY: CharSlices from Rust strings are safe. - let result = unsafe { ddprof_ffi_Vec_tag_parse(CharSlice::from(dd_tags)) }; + let result = unsafe { ddog_Vec_tag_parse(CharSlice::from(dd_tags)) }; assert_eq!(2, result.tags.len()); assert_eq!("env:staging:east", result.tags.get(0).unwrap().to_string()); assert_eq!("env_staging:east", result.tags.get(1).unwrap().to_string()); diff --git a/ddprof-ffi/src/vec.rs b/ddcommon-ffi/src/vec.rs similarity index 99% rename from ddprof-ffi/src/vec.rs rename to ddcommon-ffi/src/vec.rs index 1e49078ca..4ea1399b3 100644 --- a/ddprof-ffi/src/vec.rs +++ b/ddcommon-ffi/src/vec.rs @@ -3,7 +3,7 @@ extern crate alloc; -use crate::Slice; +use crate::slice::Slice; use std::io::Write; use std::marker::PhantomData; use std::mem::ManuallyDrop; diff --git a/ddprof-ffi/Cargo.toml b/ddprof-ffi/Cargo.toml index 00a721f1d..adacd9df3 100644 --- a/ddprof-ffi/Cargo.toml +++ b/ddprof-ffi/Cargo.toml @@ -18,5 +18,6 @@ ddprof-exporter = { path = "../ddprof-exporter", version = "0.7.0-rc.1" } ddprof-profiles = { path = "../ddprof-profiles", version = "0.7.0-rc.1" } hyper = {version = "0.14", default-features = false} ddcommon = { path = "../ddcommon", version = "0.7.0-rc.1" } +ddcommon-ffi = { path = "../ddcommon-ffi", version = "0.7.0-rc.1" } libc = "0.2" tokio-util = "0.7.1" diff --git a/ddprof-ffi/cbindgen.toml b/ddprof-ffi/cbindgen.toml index e418aa509..b4f89a35e 100644 --- a/ddprof-ffi/cbindgen.toml +++ b/ddprof-ffi/cbindgen.toml @@ -6,35 +6,15 @@ tab_width = 2 header = """// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present Datadog, Inc. """ -include_guard = "DDPROF_FFI_H" +include_guard = "DDOG_PROFILING_H" style = "both" no_includes = true sys_includes = ["stdbool.h", "stddef.h", "stdint.h"] - -after_includes = """ - -#if defined(_MSC_VER) -#define DDPROF_FFI_CHARSLICE_C(string) \\ -/* NOTE: Compilation fails if you pass in a char* instead of a literal */ {.ptr = "" string, .len = sizeof(string) - 1} -#else -#define DDPROF_FFI_CHARSLICE_C(string) \\ -/* NOTE: Compilation fails if you pass in a char* instead of a literal */ ((ddprof_ffi_CharSlice){ .ptr = "" string, .len = sizeof(string) - 1 }) -#endif - -#if defined(__cplusplus) && (__cplusplus >= 201703L) -# define DD_CHECK_RETURN [[nodiscard]] -#elif defined(_Check_return_) /* SAL */ -# define DD_CHECK_RETURN _Check_return_ -#elif (defined(__has_attribute) && __has_attribute(warn_unused_result)) || \\ - (defined(__GNUC__) && (__GNUC__ >= 4)) -# define DD_CHECK_RETURN __attribute__((__warn_unused_result__)) -#else -# define DD_CHECK_RETURN -#endif""" +includes = ["datadog/common.h"] [export] -prefix = "ddprof_ffi_" +prefix = "ddog_" [export.mangle] rename_types="SnakeCase" @@ -44,8 +24,8 @@ prefix_with_name = true rename_variants = "ScreamingSnakeCase" [fn] -must_use = "DD_CHECK_RETURN" +must_use = "DDOG_CHECK_RETURN" [parse] parse_deps = true -include = ["ddcommon", "ddprof-exporter", "ddprof-profiles", "ux"] +include = ["ddcommon", "ddcommon-ffi", "ddprof-exporter", "ddprof-profiles", "ux"] diff --git a/ddprof-ffi/src/exporter.rs b/ddprof-ffi/src/exporter.rs index e1458bb0e..15c4b931c 100644 --- a/ddprof-ffi/src/exporter.rs +++ b/ddprof-ffi/src/exporter.rs @@ -4,8 +4,9 @@ #![allow(renamed_and_removed_lints)] #![allow(clippy::box_vec)] -use crate::{AsBytes, ByteSlice, CharSlice, Slice, Timespec}; +use crate::Timespec; use ddcommon::tag::Tag; +use ddcommon_ffi::slice::{AsBytes, ByteSlice, CharSlice, Slice}; use ddprof_exporter as exporter; use exporter::ProfileExporterV3; use std::borrow::Cow; @@ -16,16 +17,16 @@ use std::str::FromStr; #[repr(C)] pub enum SendResult { HttpResponse(HttpStatus), - Err(crate::Vec), + Err(ddcommon_ffi::Vec), } #[repr(C)] pub enum NewProfileExporterV3Result { Ok(*mut ProfileExporterV3), - Err(crate::Vec), + Err(ddcommon_ffi::Vec), } -#[export_name = "ddprof_ffi_NewProfileExporterV3Result_drop"] +#[export_name = "ddog_NewProfileExporterV3Result_drop"] pub unsafe extern "C" fn new_profile_exporter_v3_result_drop(result: NewProfileExporterV3Result) { match result { NewProfileExporterV3Result::Ok(ptr) => { @@ -64,7 +65,7 @@ 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/". -#[export_name = "ddprof_ffi_EndpointV3_agent"] +#[export_name = "ddog_EndpointV3_agent"] pub extern "C" fn endpoint_agent(base_url: CharSlice) -> EndpointV3 { EndpointV3::Agent(base_url) } @@ -73,7 +74,7 @@ pub extern "C" fn endpoint_agent(base_url: CharSlice) -> EndpointV3 { /// # Arguments /// * `site` - Contains a host and port e.g. "datadoghq.com". /// * `api_key` - Contains the Datadog API key. -#[export_name = "ddprof_ffi_EndpointV3_agentless"] +#[export_name = "ddog_EndpointV3_agentless"] pub extern "C" fn endpoint_agentless<'a>( site: CharSlice<'a>, api_key: CharSlice<'a>, @@ -115,10 +116,10 @@ unsafe fn try_to_endpoint( } #[must_use] -#[export_name = "ddprof_ffi_ProfileExporterV3_new"] +#[export_name = "ddog_ProfileExporterV3_new"] pub extern "C" fn profile_exporter_new( family: CharSlice, - tags: Option<&crate::Vec>, + tags: Option<&ddcommon_ffi::Vec>, endpoint: EndpointV3, ) -> NewProfileExporterV3Result { match || -> Result> { @@ -132,7 +133,7 @@ pub extern "C" fn profile_exporter_new( } } -#[export_name = "ddprof_ffi_ProfileExporterV3_delete"] +#[export_name = "ddog_ProfileExporterV3_delete"] pub extern "C" fn profile_exporter_delete(exporter: Option>) { std::mem::drop(exporter) } @@ -154,13 +155,13 @@ unsafe fn into_vec_files<'a>(slice: Slice<'a, File>) -> Vec>, start: Timespec, end: Timespec, files: Slice, - additional_tags: Option<&crate::Vec>, + additional_tags: Option<&ddcommon_ffi::Vec>, timeout_ms: u64, ) -> Option> { match exporter { @@ -193,7 +194,7 @@ pub unsafe extern "C" fn profile_exporter_build( /// # Safety /// All non-null arguments MUST have been created by created by apis in this module. #[must_use] -#[export_name = "ddprof_ffi_ProfileExporterV3_send"] +#[export_name = "ddog_ProfileExporterV3_send"] pub unsafe extern "C" fn profile_exporter_send( exporter: Option>, request: Option>, @@ -202,7 +203,7 @@ pub unsafe extern "C" fn profile_exporter_send( let exp_ptr = match exporter { None => { let buf: &[u8] = b"Failed to export: exporter was null"; - return SendResult::Err(crate::Vec::from(Vec::from(buf))); + return SendResult::Err(ddcommon_ffi::Vec::from(Vec::from(buf))); } Some(e) => e, }; @@ -210,7 +211,7 @@ pub unsafe extern "C" fn profile_exporter_send( let request_ptr = match request { None => { let buf: &[u8] = b"Failed to export: request was null"; - return SendResult::Err(crate::Vec::from(Vec::from(buf))); + return SendResult::Err(ddcommon_ffi::Vec::from(Vec::from(buf))); } Some(req) => req, }; @@ -228,7 +229,7 @@ pub unsafe extern "C" fn profile_exporter_send( } #[no_mangle] -pub extern "C" fn ddprof_ffi_Request_drop(_request: Option>) {} +pub extern "C" fn ddog_Request_drop(_request: Option>) {} fn unwrap_cancellation_token<'a>( cancel: Option>, @@ -244,7 +245,7 @@ fn unwrap_cancellation_token<'a>( /// Can be passed as an argument to send and then be used to asynchronously cancel it from a different thread. #[no_mangle] #[must_use] -pub extern "C" fn ddprof_ffi_CancellationToken_new() -> *mut CancellationToken { +pub extern "C" fn ddog_CancellationToken_new() -> *mut CancellationToken { Box::into_raw(Box::new(CancellationToken( tokio_util::sync::CancellationToken::new(), ))) @@ -258,22 +259,22 @@ pub extern "C" fn ddprof_ffi_CancellationToken_new() -> *mut CancellationToken { /// /// Thus, it's possible to do something like: /// ```c -/// cancel_t1 = ddprof_ffi_CancellationToken_new(); -/// cancel_t2 = ddprof_ffi_CancellationToken_clone(cancel_t1); +/// cancel_t1 = ddog_CancellationToken_new(); +/// cancel_t2 = ddog_CancellationToken_clone(cancel_t1); /// /// // On thread t1: -/// ddprof_ffi_ProfileExporterV3_send(..., cancel_t1); -/// ddprof_ffi_CancellationToken_drop(cancel_t1); +/// ddog_ProfileExporterV3_send(..., cancel_t1); +/// ddog_CancellationToken_drop(cancel_t1); /// /// // On thread t2: -/// ddprof_ffi_CancellationToken_cancel(cancel_t2); -/// ddprof_ffi_CancellationToken_drop(cancel_t2); +/// ddog_CancellationToken_cancel(cancel_t2); +/// ddog_CancellationToken_drop(cancel_t2); /// ``` /// /// Without clone, both t1 and t2 would need to synchronize to make sure neither was using the cancel /// before it could be dropped. With clone, there is no need for such synchronization, both threads /// have their own cancel and should drop that cancel after they are done with it. -pub extern "C" fn ddprof_ffi_CancellationToken_clone( +pub extern "C" fn ddog_CancellationToken_clone( cancel: Option>, ) -> *mut CancellationToken { match unwrap_cancellation_token(cancel) { @@ -286,7 +287,7 @@ pub extern "C" fn ddprof_ffi_CancellationToken_clone( /// Note that cancellation is a terminal state; cancelling a token more than once does nothing. /// Returns `true` if token was successfully cancelled. #[no_mangle] -pub extern "C" fn ddprof_ffi_CancellationToken_cancel( +pub extern "C" fn ddog_CancellationToken_cancel( cancel: Option>, ) -> bool { let cancel_reference = match unwrap_cancellation_token(cancel) { @@ -303,11 +304,11 @@ pub extern "C" fn ddprof_ffi_CancellationToken_cancel( } #[no_mangle] -pub extern "C" fn ddprof_ffi_CancellationToken_drop(_cancel: Option>) { +pub extern "C" fn ddog_CancellationToken_drop(_cancel: Option>) { // _cancel implicitly dropped because we've turned it into a Box } -#[export_name = "ddprof_ffi_SendResult_drop"] +#[export_name = "ddog_SendResult_drop"] pub unsafe extern "C" fn send_result_drop(result: SendResult) { std::mem::drop(result) } @@ -315,7 +316,7 @@ pub unsafe extern "C" fn send_result_drop(result: SendResult) { #[cfg(test)] mod test { use crate::exporter::*; - use crate::Slice; + use ddcommon_ffi::Slice; fn family() -> CharSlice<'static> { CharSlice::from("native") @@ -331,7 +332,7 @@ mod test { #[test] fn profile_exporter_v3_new_and_delete() { - let mut tags = crate::Vec::default(); + let mut tags = ddcommon_ffi::Vec::default(); let host = Tag::new("host", "localhost").expect("static tags to be valid"); tags.push(host); diff --git a/ddprof-ffi/src/lib.rs b/ddprof-ffi/src/lib.rs index d6df0e094..d4711f457 100644 --- a/ddprof-ffi/src/lib.rs +++ b/ddprof-ffi/src/lib.rs @@ -8,12 +8,6 @@ use chrono::{DateTime, TimeZone, Utc}; mod exporter; mod profiles; -mod slice; -mod tags; -mod vec; - -pub use slice::{AsBytes, ByteSlice, CharSlice, Slice}; -pub use vec::Vec; /// Represents time since the Unix Epoch in seconds plus nanoseconds. #[repr(C)] diff --git a/ddprof-ffi/src/profiles.rs b/ddprof-ffi/src/profiles.rs index 9b377d835..a85fa54ae 100644 --- a/ddprof-ffi/src/profiles.rs +++ b/ddprof-ffi/src/profiles.rs @@ -1,7 +1,8 @@ // Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present Datadog, Inc. -use crate::{AsBytes, CharSlice, Slice, Timespec}; +use crate::Timespec; +use ddcommon_ffi::slice::{AsBytes, CharSlice, Slice}; use ddprof_profiles as profiles; use std::convert::{TryFrom, TryInto}; use std::error::Error; @@ -286,7 +287,7 @@ impl<'a> TryFrom> for profiles::api::Sample<'a> { } /// Create a new profile with the given sample types. Must call -/// `ddprof_ffi_Profile_free` when you are done with the profile. +/// `ddog_Profile_free` when you are done with the profile. /// /// # Arguments /// * `sample_types` @@ -299,7 +300,7 @@ impl<'a> TryFrom> for profiles::api::Sample<'a> { /// and must have the correct number of elements for the slice. #[no_mangle] #[must_use] -pub unsafe extern "C" fn ddprof_ffi_Profile_new( +pub unsafe extern "C" fn ddog_Profile_new( sample_types: Slice, period: Option<&Period>, start_time: Option<&Timespec>, @@ -318,8 +319,8 @@ pub unsafe extern "C" fn ddprof_ffi_Profile_new( #[no_mangle] /// # Safety /// The `profile` must point to an object created by another FFI routine in this -/// module, such as `ddprof_ffi_Profile_with_sample_types`. -pub unsafe extern "C" fn ddprof_ffi_Profile_free(_profile: Box) {} +/// module, such as `ddog_Profile_with_sample_types`. +pub unsafe extern "C" fn ddog_Profile_free(_profile: Box) {} #[no_mangle] /// # Safety @@ -327,10 +328,7 @@ pub unsafe extern "C" fn ddprof_ffi_Profile_free(_profile: Box u64 { +pub extern "C" fn ddog_Profile_add(profile: &mut ddprof_profiles::Profile, sample: Sample) -> u64 { match sample.try_into().map(|s| profile.add(s)) { Ok(r) => match r { Ok(id) => id.into(), @@ -344,7 +342,7 @@ pub extern "C" fn ddprof_ffi_Profile_add( pub struct EncodedProfile { start: Timespec, end: Timespec, - buffer: crate::Vec, + buffer: ddcommon_ffi::Vec, } impl From for EncodedProfile { @@ -359,11 +357,11 @@ impl From for EncodedProfile { #[repr(C)] pub enum SerializeResult { Ok(EncodedProfile), - Err(crate::Vec), + Err(ddcommon_ffi::Vec), } /// Serialize the aggregated profile. Don't forget to clean up the result by -/// calling ddprof_ffi_SerializeResult_drop. +/// calling ddog_SerializeResult_drop. /// /// # Arguments /// * `profile` - a reference to the profile being serialized. @@ -380,7 +378,7 @@ pub enum SerializeResult { /// The `end_time` must be null or otherwise point to a valid TimeSpec object. /// The `duration_nanos` must be null or otherwise point to a valid i64. #[no_mangle] -pub unsafe extern "C" fn ddprof_ffi_Profile_serialize( +pub unsafe extern "C" fn ddog_Profile_serialize( profile: &ddprof_profiles::Profile, end_time: Option<&Timespec>, duration_nanos: Option<&i64>, @@ -398,11 +396,11 @@ pub unsafe extern "C" fn ddprof_ffi_Profile_serialize( } #[no_mangle] -pub unsafe extern "C" fn ddprof_ffi_SerializeResult_drop(_result: SerializeResult) {} +pub unsafe extern "C" fn ddog_SerializeResult_drop(_result: SerializeResult) {} #[must_use] #[no_mangle] -pub unsafe extern "C" fn ddprof_ffi_Vec_u8_as_slice(vec: &crate::Vec) -> Slice { +pub unsafe extern "C" fn ddog_Vec_u8_as_slice(vec: &ddcommon_ffi::Vec) -> Slice { vec.as_slice() } @@ -419,7 +417,7 @@ pub unsafe extern "C" fn ddprof_ffi_Vec_u8_as_slice(vec: &crate::Vec) -> Sli /// can be called across an FFI boundary, the compiler cannot enforce this. /// If `time` is not null, it must point to a valid Timespec object. #[no_mangle] -pub unsafe extern "C" fn ddprof_ffi_Profile_reset( +pub unsafe extern "C" fn ddog_Profile_reset( profile: &mut ddprof_profiles::Profile, start_time: Option<&Timespec>, ) -> bool { @@ -429,14 +427,14 @@ pub unsafe extern "C" fn ddprof_ffi_Profile_reset( #[cfg(test)] mod test { use crate::profiles::*; - use crate::Slice; + use ddcommon_ffi::Slice; #[test] fn ctor_and_dtor() { unsafe { let sample_type: *const ValueType = &ValueType::new("samples", "count"); - let profile = ddprof_ffi_Profile_new(Slice::new(sample_type, 1), None, None); - ddprof_ffi_Profile_free(profile); + let profile = ddog_Profile_new(Slice::new(sample_type, 1), None, None); + ddog_Profile_free(profile); } } @@ -444,7 +442,7 @@ mod test { fn aggregate_samples() { unsafe { let sample_type: *const ValueType = &ValueType::new("samples", "count"); - let mut profile = ddprof_ffi_Profile_new(Slice::new(sample_type, 1), None, None); + let mut profile = ddog_Profile_new(Slice::new(sample_type, 1), None, None); let lines = &vec![Line { function: Function { @@ -481,19 +479,19 @@ mod test { let aggregator = &mut *profile; - let sample_id1 = ddprof_ffi_Profile_add(aggregator, sample); + let sample_id1 = ddog_Profile_add(aggregator, sample); assert_eq!(sample_id1, 1); - let sample_id2 = ddprof_ffi_Profile_add(aggregator, sample); + let sample_id2 = ddog_Profile_add(aggregator, sample); assert_eq!(sample_id1, sample_id2); - ddprof_ffi_Profile_free(profile); + ddog_Profile_free(profile); } } unsafe fn provide_distinct_locations_ffi() -> ddprof_profiles::Profile { let sample_type: *const ValueType = &ValueType::new("samples", "count"); - let mut profile = ddprof_ffi_Profile_new(Slice::new(sample_type, 1), None, None); + let mut profile = ddog_Profile_new(Slice::new(sample_type, 1), None, None); let main_lines = vec![Line { function: Function { @@ -552,10 +550,10 @@ mod test { let aggregator = &mut *profile; - let sample_id1 = ddprof_ffi_Profile_add(aggregator, main_sample); + let sample_id1 = ddog_Profile_add(aggregator, main_sample); assert_eq!(sample_id1, 1); - let sample_id2 = ddprof_ffi_Profile_add(aggregator, test_sample); + let sample_id2 = ddog_Profile_add(aggregator, test_sample); assert_eq!(sample_id2, 2); *profile diff --git a/examples/ffi/exporter.cpp b/examples/ffi/exporter.cpp index 1d5e2e402..6f93233dd 100644 --- a/examples/ffi/exporter.cpp +++ b/examples/ffi/exporter.cpp @@ -1,5 +1,6 @@ extern "C" { -#include +#include +#include } #include #include @@ -8,12 +9,12 @@ extern "C" { #include #include -static ddprof_ffi_Slice_c_char to_slice_c_char(const char *s) { +static ddog_Slice_c_char to_slice_c_char(const char *s) { return {.ptr = s, .len = strlen(s)}; } struct Deleter { - void operator()(ddprof_ffi_Profile *object) { ddprof_ffi_Profile_free(object); } + void operator()(ddog_Profile *object) { ddog_Profile_free(object); } }; template void print_error(const char *s, const T &err) { @@ -33,121 +34,121 @@ int main(int argc, char *argv[]) { const auto service = argv[1]; - const ddprof_ffi_ValueType wall_time = { - .type_ = DDPROF_FFI_CHARSLICE_C("wall-time"), - .unit = DDPROF_FFI_CHARSLICE_C("nanoseconds"), + const ddog_ValueType wall_time = { + .type_ = DDOG_CHARSLICE_C("wall-time"), + .unit = DDOG_CHARSLICE_C("nanoseconds"), }; - const ddprof_ffi_Slice_value_type sample_types = {&wall_time, 1}; - const ddprof_ffi_Period period = {wall_time, 60}; - std::unique_ptr profile{ - ddprof_ffi_Profile_new(sample_types, &period, nullptr)}; + const ddog_Slice_value_type sample_types = {&wall_time, 1}; + const ddog_Period period = {wall_time, 60}; + std::unique_ptr profile{ + ddog_Profile_new(sample_types, &period, nullptr)}; - ddprof_ffi_Line root_line = { + ddog_Line root_line = { .function = { - .name = DDPROF_FFI_CHARSLICE_C("{main}"), - .filename = DDPROF_FFI_CHARSLICE_C("/srv/example/index.php"), + .name = DDOG_CHARSLICE_C("{main}"), + .filename = DDOG_CHARSLICE_C("/srv/example/index.php"), }, .line = 0, }; - ddprof_ffi_Location root_location = { + ddog_Location root_location = { // yes, a zero-initialized mapping is valid .mapping = {}, .lines = {&root_line, 1}, }; int64_t value = 10; - const ddprof_ffi_Label label = { - .key = DDPROF_FFI_CHARSLICE_C("language"), - .str = DDPROF_FFI_CHARSLICE_C("php"), + const ddog_Label label = { + .key = DDOG_CHARSLICE_C("language"), + .str = DDOG_CHARSLICE_C("php"), }; - ddprof_ffi_Sample sample = { + ddog_Sample sample = { .locations = {&root_location, 1}, .values = {&value, 1}, .labels = {&label, 1}, }; - ddprof_ffi_Profile_add(profile.get(), sample); + ddog_Profile_add(profile.get(), sample); - ddprof_ffi_SerializeResult serialize_result = - ddprof_ffi_Profile_serialize(profile.get(), nullptr, nullptr); - if (serialize_result.tag == DDPROF_FFI_SERIALIZE_RESULT_ERR) { + ddog_SerializeResult serialize_result = + ddog_Profile_serialize(profile.get(), nullptr, nullptr); + if (serialize_result.tag == DDOG_SERIALIZE_RESULT_ERR) { print_error("Failed to serialize profile: ", serialize_result.err); return 1; } - ddprof_ffi_EncodedProfile *encoded_profile = &serialize_result.ok; + ddog_EncodedProfile *encoded_profile = &serialize_result.ok; - ddprof_ffi_EndpointV3 endpoint = ddprof_ffi_EndpointV3_agentless( - DDPROF_FFI_CHARSLICE_C("datad0g.com"), to_slice_c_char(api_key)); + ddog_EndpointV3 endpoint = ddog_EndpointV3_agentless( + DDOG_CHARSLICE_C("datad0g.com"), to_slice_c_char(api_key)); - ddprof_ffi_Vec_tag tags = ddprof_ffi_Vec_tag_new(); - ddprof_ffi_PushTagResult tag_result = - ddprof_ffi_Vec_tag_push(&tags, DDPROF_FFI_CHARSLICE_C("service"), to_slice_c_char(service)); - if (tag_result.tag == DDPROF_FFI_PUSH_TAG_RESULT_ERR) { + ddog_Vec_tag tags = ddog_Vec_tag_new(); + ddog_PushTagResult tag_result = + ddog_Vec_tag_push(&tags, DDOG_CHARSLICE_C("service"), to_slice_c_char(service)); + if (tag_result.tag == DDOG_PUSH_TAG_RESULT_ERR) { print_error("Failed to push tag: ", tag_result.err); - ddprof_ffi_PushTagResult_drop(tag_result); + ddog_PushTagResult_drop(tag_result); return 1; } - ddprof_ffi_PushTagResult_drop(tag_result); + ddog_PushTagResult_drop(tag_result); - ddprof_ffi_NewProfileExporterV3Result exporter_new_result = - ddprof_ffi_ProfileExporterV3_new(DDPROF_FFI_CHARSLICE_C("native"), &tags, endpoint); - ddprof_ffi_Vec_tag_drop(tags); + ddog_NewProfileExporterV3Result exporter_new_result = + ddog_ProfileExporterV3_new(DDOG_CHARSLICE_C("native"), &tags, endpoint); + ddog_Vec_tag_drop(tags); - if (exporter_new_result.tag == DDPROF_FFI_NEW_PROFILE_EXPORTER_V3_RESULT_ERR) { + if (exporter_new_result.tag == DDOG_NEW_PROFILE_EXPORTER_V3_RESULT_ERR) { print_error("Failed to create exporter: ", exporter_new_result.err); - ddprof_ffi_NewProfileExporterV3Result_drop(exporter_new_result); + ddog_NewProfileExporterV3Result_drop(exporter_new_result); return 1; } auto exporter = exporter_new_result.ok; - ddprof_ffi_File files_[] = {{ - .name = DDPROF_FFI_CHARSLICE_C("auto.pprof"), - .file = ddprof_ffi_Vec_u8_as_slice(&encoded_profile->buffer), + ddog_File files_[] = {{ + .name = DDOG_CHARSLICE_C("auto.pprof"), + .file = ddog_Vec_u8_as_slice(&encoded_profile->buffer), }}; - ddprof_ffi_Slice_file files = {.ptr = files_, .len = sizeof files_ / sizeof *files_}; + ddog_Slice_file files = {.ptr = files_, .len = sizeof files_ / sizeof *files_}; - ddprof_ffi_Request *request = ddprof_ffi_ProfileExporterV3_build( + ddog_Request *request = ddog_ProfileExporterV3_build( exporter, encoded_profile->start, encoded_profile->end, files, nullptr, 30000); - ddprof_ffi_CancellationToken *cancel = ddprof_ffi_CancellationToken_new(); - ddprof_ffi_CancellationToken *cancel_for_background_thread = - ddprof_ffi_CancellationToken_clone(cancel); + ddog_CancellationToken *cancel = ddog_CancellationToken_new(); + ddog_CancellationToken *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 ddprof_ffi_ProfileExporterV3_send is hit). + // before the timeout in ddog_ProfileExporterV3_send 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( - [](ddprof_ffi_CancellationToken *cancel_for_background_thread) { + [](ddog_CancellationToken *cancel_for_background_thread) { int timeout_ms = 5000; std::this_thread::sleep_for(std::chrono::milliseconds(timeout_ms)); printf("Request took longer than %d ms, triggering asynchronous " "cancellation\n", timeout_ms); - ddprof_ffi_CancellationToken_cancel(cancel_for_background_thread); - ddprof_ffi_CancellationToken_drop(cancel_for_background_thread); + ddog_CancellationToken_cancel(cancel_for_background_thread); + ddog_CancellationToken_drop(cancel_for_background_thread); }, cancel_for_background_thread); trigger_cancel_if_request_takes_too_long_thread.detach(); int exit_code = 0; - ddprof_ffi_SendResult send_result = ddprof_ffi_ProfileExporterV3_send(exporter, request, cancel); - if (send_result.tag == DDPROF_FFI_SEND_RESULT_ERR) { + ddog_SendResult send_result = ddog_ProfileExporterV3_send(exporter, request, cancel); + if (send_result.tag == DDOG_SEND_RESULT_ERR) { print_error("Failed to send profile: ", send_result.err); exit_code = 1; } else { printf("Response code: %d\n", send_result.http_response.code); } - ddprof_ffi_NewProfileExporterV3Result_drop(exporter_new_result); - ddprof_ffi_SendResult_drop(send_result); - ddprof_ffi_CancellationToken_drop(cancel); + ddog_NewProfileExporterV3Result_drop(exporter_new_result); + ddog_SendResult_drop(send_result); + ddog_CancellationToken_drop(cancel); return exit_code; } diff --git a/examples/ffi/profiles.c b/examples/ffi/profiles.c index 479990871..282f772ce 100644 --- a/examples/ffi/profiles.c +++ b/examples/ffi/profiles.c @@ -3,47 +3,48 @@ // developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present // Datadog, Inc. -#include +#include +#include #include /* Creates a profile with one sample type "wall-time" with period of "wall-time" * with unit 60 "nanoseconds". Adds one sample with a string label "language". */ int main(void) { - const struct ddprof_ffi_ValueType wall_time = { - .type_ = DDPROF_FFI_CHARSLICE_C("wall-time"), - .unit = DDPROF_FFI_CHARSLICE_C("nanoseconds"), + const struct ddog_ValueType wall_time = { + .type_ = DDOG_CHARSLICE_C("wall-time"), + .unit = DDOG_CHARSLICE_C("nanoseconds"), }; - const struct ddprof_ffi_Slice_value_type sample_types = {&wall_time, 1}; - const struct ddprof_ffi_Period period = {wall_time, 60}; + const struct ddog_Slice_value_type sample_types = {&wall_time, 1}; + const struct ddog_Period period = {wall_time, 60}; - ddprof_ffi_Profile *profile = ddprof_ffi_Profile_new(sample_types, &period, NULL); + ddog_Profile *profile = ddog_Profile_new(sample_types, &period, NULL); - struct ddprof_ffi_Line root_line = { + struct ddog_Line root_line = { .function = - (struct ddprof_ffi_Function){ - .name = DDPROF_FFI_CHARSLICE_C("{main}"), - .filename = DDPROF_FFI_CHARSLICE_C("/srv/example/index.php"), + (struct ddog_Function){ + .name = DDOG_CHARSLICE_C("{main}"), + .filename = DDOG_CHARSLICE_C("/srv/example/index.php"), }, .line = 0, }; - struct ddprof_ffi_Location root_location = { + struct ddog_Location root_location = { // yes, a zero-initialized mapping is valid - .mapping = (struct ddprof_ffi_Mapping){0}, - .lines = (struct ddprof_ffi_Slice_line){&root_line, 1}, + .mapping = (struct ddog_Mapping){0}, + .lines = (struct ddog_Slice_line){&root_line, 1}, }; int64_t value = 10; - const struct ddprof_ffi_Label label = { - .key = DDPROF_FFI_CHARSLICE_C("language"), - .str = DDPROF_FFI_CHARSLICE_C("php"), + const struct ddog_Label label = { + .key = DDOG_CHARSLICE_C("language"), + .str = DDOG_CHARSLICE_C("php"), }; - struct ddprof_ffi_Sample sample = { + struct ddog_Sample sample = { .locations = {&root_location, 1}, .values = {&value, 1}, .labels = {&label, 1}, }; - ddprof_ffi_Profile_add(profile, sample); - ddprof_ffi_Profile_free(profile); + ddog_Profile_add(profile, sample); + ddog_Profile_free(profile); return 0; } diff --git a/ffi-build.sh b/ffi-build.sh index c09319629..29e74ba91 100755 --- a/ffi-build.sh +++ b/ffi-build.sh @@ -134,8 +134,13 @@ if [ -n "$unexpected_native_libs" ]; then fi cd - -echo "Generating the $destdir/include/ddprof/ffi.h header..." -cbindgen --crate ddprof-ffi --config ddprof-ffi/cbindgen.toml --output "$destdir/include/ddprof/ffi.h" +echo "Building tools" +cargo build --package tools --bins + +echo "Generating $destdir/include/libdatadog headers..." +cbindgen --crate ddcommon-ffi --config ddcommon-ffi/cbindgen.toml --output "$destdir/include/datadog/common.h" +cbindgen --crate ddprof-ffi --config ddprof-ffi/cbindgen.toml --output "$destdir/include/datadog/profiling.h" +./target/debug/dedup_headers "$destdir/include/datadog/common.h" "$destdir/include/datadog/profiling.h" # CI doesn't have any clang tooling # clang-format -i "$destdir/include/ddprof/ffi.h" diff --git a/tools/Cargo.toml b/tools/Cargo.toml new file mode 100644 index 000000000..513eb2bb6 --- /dev/null +++ b/tools/Cargo.toml @@ -0,0 +1,16 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present Datadog, Inc. + +[package] +name = "tools" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +regex = "1" +lazy_static = "1.4" + +[[bin]] +name = "dedup_headers" diff --git a/tools/src/bin/dedup_headers.rs b/tools/src/bin/dedup_headers.rs new file mode 100644 index 000000000..4f71decda --- /dev/null +++ b/tools/src/bin/dedup_headers.rs @@ -0,0 +1,95 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present Datadog, Inc. + +/// Usage: +/// ./dedup_headers ... +/// +/// All type definitions will be removed from the child_headers, and moved to the base_header +/// if they are not already defined in the parent_header +use regex::{Match, Regex, RegexBuilder}; +use std::collections::HashSet; +use std::fs::{File, OpenOptions}; +use std::io::{self, BufReader, BufWriter, Read, Seek, Write}; + +fn collect_definitions(header: &str) -> Vec> { + lazy_static::lazy_static! { + static ref HEADER_TYPE_DECL_RE: Regex = RegexBuilder::new(r"^(/\*\*.*?\*/\n)?typedef (struct|enum) [a-zA-Z_0-9]+ +(\{.*?\} )?[a-zA-Z_0-9]+;\n+") + .multi_line(true) + .dot_matches_new_line(true) + .build() + .unwrap(); + } + HEADER_TYPE_DECL_RE.find_iter(header).collect() +} + +fn read(f: &mut BufReader<&File>) -> String { + let mut s = Vec::new(); + f.read_to_end(&mut s).unwrap(); + String::from_utf8(s).unwrap() +} + +fn write_parts(writer: &mut BufWriter<&File>, parts: &[&str]) -> io::Result<()> { + writer.get_ref().set_len(0)?; + writer.seek(io::SeekFrom::Start(0))?; + for part in parts { + writer.write_all(part.as_bytes())?; + } + Ok(()) +} + +fn content_without_defs<'a>(content: &'a str, defs: &[Match]) -> Vec<&'a str> { + let mut new_content_parts = Vec::new(); + let mut pos = 0; + for d in defs { + new_content_parts.push(&content[pos..d.start()]); + pos = d.end(); + } + new_content_parts.push(&content[pos..]); + new_content_parts +} + +fn main() { + let args: Vec<_> = std::env::args_os().collect(); + + let mut unique_child_defs: Vec = Vec::new(); + let mut present = HashSet::new(); + for child_def in args[2..].iter().flat_map(|p| { + let child_header = OpenOptions::new().read(true).write(true).open(p).unwrap(); + + let child_header_content = read(&mut BufReader::new(&child_header)); + let child_defs = collect_definitions(&child_header_content); + let new_content_parts = content_without_defs(&child_header_content, &child_defs); + + write_parts(&mut BufWriter::new(&child_header), &new_content_parts).unwrap(); + + child_defs + .into_iter() + .map(|m| m.as_str().to_owned()) + .collect::>() + }) { + if present.contains(&child_def) { + continue; + } + unique_child_defs.push(child_def.clone()); + present.insert(child_def); + } + + let base_header = OpenOptions::new() + .read(true) + .write(true) + .open(&args[1]) + .unwrap(); + let base_header_content = read(&mut BufReader::new(&base_header)); + let base_defs = collect_definitions(&base_header_content); + let base_defs_set: HashSet<_> = base_defs.iter().map(Match::as_str).collect(); + + let mut base_new_parts = vec![&base_header_content[..base_defs.last().unwrap().end()]]; + for child_def in &unique_child_defs { + if base_defs_set.contains(child_def.as_str()) { + continue; + } + base_new_parts.push(child_def); + } + base_new_parts.push(&base_header_content[base_defs.last().unwrap().end()..]); + write_parts(&mut BufWriter::new(&base_header), &base_new_parts).unwrap(); +} diff --git a/tools/src/lib.rs b/tools/src/lib.rs new file mode 100644 index 000000000..fcbbec1ca --- /dev/null +++ b/tools/src/lib.rs @@ -0,0 +1,2 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present Datadog, Inc.