-
Notifications
You must be signed in to change notification settings - Fork 13
ekump/APMSP-2151 create ddsketch ffi crate #1135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
75c84c6
1c78d42
196b21a
99a5ab7
938761c
d10aeef
26d59f7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ data-pipeline = [] | |
symbolizer = [] | ||
library-config = [] | ||
log = [] | ||
ddsketch = [] | ||
|
||
[lib] | ||
bench = false | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,6 +60,30 @@ impl CString { | |
Ok(Self::from_std(std::ffi::CString::new(t)?)) | ||
} | ||
|
||
/// Creates a new `CString` from the given input, or returns an empty `CString` | ||
/// if the input contains null bytes. | ||
Comment on lines
+63
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In what situations would we have weird strings with null bytes in the middle? 👀 |
||
/// | ||
/// This method will never panic, as an empty string is guaranteed to not contain | ||
/// null bytes and can always be converted to a `CString`. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use ddcommon_ffi::CString; | ||
/// | ||
/// let good = CString::new_or_empty("hello world"); | ||
/// assert_eq!(good.as_cstr().into_std().to_str().unwrap(), "hello world"); | ||
/// | ||
/// let bad = CString::new_or_empty("hello\0world"); | ||
/// assert_eq!(bad.as_cstr().into_std().to_str().unwrap(), ""); | ||
/// ``` | ||
pub fn new_or_empty<T: Into<Vec<u8>>>(t: T) -> Self { | ||
Self::new(t).unwrap_or_else(|_| { | ||
#[allow(clippy::unwrap_used)] | ||
Self::new("").unwrap() | ||
}) | ||
} | ||
|
||
pub fn as_cstr(&self) -> CStr<'_> { | ||
CStr { | ||
ptr: self.ptr, | ||
|
@@ -118,6 +142,18 @@ mod tests { | |
assert_eq!(s.as_cstr().into_std().to_str().unwrap(), "hello"); | ||
} | ||
|
||
#[test] | ||
fn test_cstring_new_or_empty() { | ||
let good = CString::new_or_empty("hello world"); | ||
assert_eq!(good.as_cstr().into_std().to_str().unwrap(), "hello world"); | ||
|
||
let bad = CString::new_or_empty("hello\0world"); | ||
assert_eq!(bad.as_cstr().into_std().to_str().unwrap(), ""); | ||
|
||
let empty = CString::new_or_empty(""); | ||
assert_eq!(empty.as_cstr().into_std().to_str().unwrap(), ""); | ||
} | ||
|
||
#[test] | ||
fn test_raw_cstr() { | ||
let s: &'static [u8] = b"abc\0"; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
[package] | ||
name = "ddsketch-ffi" | ||
edition.workspace = true | ||
version.workspace = true | ||
rust-version.workspace = true | ||
license.workspace = true | ||
|
||
[lib] | ||
crate-type = ["lib", "staticlib", "cdylib"] | ||
bench = false | ||
|
||
[features] | ||
default = ["cbindgen"] | ||
cbindgen = ["build_common/cbindgen", "ddcommon-ffi/cbindgen"] | ||
|
||
[build-dependencies] | ||
build_common = { path = "../build-common" } | ||
|
||
[dependencies] | ||
datadog-ddsketch = { path = "../ddsketch" } | ||
ddcommon-ffi = { path = "../ddcommon-ffi", default-features = false } | ||
|
||
[dev-dependencies] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
extern crate build_common; | ||
|
||
use build_common::generate_and_configure_header; | ||
|
||
fn main() { | ||
let header_name = "ddsketch.h"; | ||
generate_and_configure_header(header_name); | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,35 @@ | ||||||
# Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ | ||||||
# SPDX-License-Identifier: Apache-2.0 | ||||||
|
||||||
language = "C" | ||||||
cpp_compat = true | ||||||
tab_width = 2 | ||||||
header = """// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ | ||||||
// SPDX-License-Identifier: Apache-2.0 | ||||||
""" | ||||||
include_guard = "DDOG_DDSKETCH_H" | ||||||
style = "both" | ||||||
pragma_once = true | ||||||
no_includes = true | ||||||
sys_includes = ["stdbool.h", "stddef.h", "stdint.h"] | ||||||
includes = ["common.h"] | ||||||
|
||||||
[parse] | ||||||
parse_deps = true | ||||||
include = ["ddcommon-ffi", "datadog-ddsketch"] | ||||||
|
||||||
[export] | ||||||
include = ["ddsketch-ffi"] | ||||||
prefix = "ddog_" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or possibly just |
||||||
renaming_overrides_prefixing = true | ||||||
|
||||||
[export.rename] | ||||||
"DDSketchError" = "DDSketchError" | ||||||
"DDSketchErrorCode" = "DDSketchErrorCode" | ||||||
|
||||||
[fn] | ||||||
must_use = "DDOG_CHECK_RETURN" | ||||||
|
||||||
[enum] | ||||||
prefix_with_name = true | ||||||
rename_variants = "ScreamingSnakeCase" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use ddcommon_ffi::CString; | ||
use std::fmt::Display; | ||
|
||
/// Represent error codes that `DDSketchError` struct can hold | ||
#[repr(C)] | ||
#[derive(Copy, Clone, Debug, PartialEq)] | ||
pub enum DDSketchErrorCode { | ||
InvalidArgument, | ||
InvalidInput, | ||
Internal, | ||
} | ||
|
||
impl Display for DDSketchErrorCode { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
match self { | ||
Self::InvalidArgument => write!(f, "Invalid argument provided"), | ||
Self::InvalidInput => write!(f, "Invalid input"), | ||
Self::Internal => write!(f, "Internal error"), | ||
} | ||
} | ||
} | ||
|
||
/// Structure that contains error information that DDSketch FFI API can return. | ||
#[repr(C)] | ||
#[derive(Debug)] | ||
pub struct DDSketchError { | ||
pub code: DDSketchErrorCode, | ||
pub msg: CString, | ||
} | ||
Comment on lines
+29
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we do keep |
||
|
||
impl DDSketchError { | ||
pub fn new(code: DDSketchErrorCode, msg: &str) -> Self { | ||
Self { | ||
code, | ||
msg: CString::new_or_empty(msg), | ||
} | ||
} | ||
} | ||
Comment on lines
+26
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious about the need for this -- is |
||
|
||
impl From<Box<dyn std::error::Error>> for DDSketchError { | ||
fn from(value: Box<dyn std::error::Error>) -> Self { | ||
DDSketchError::new(DDSketchErrorCode::Internal, &value.to_string()) | ||
} | ||
} | ||
|
||
/// Frees `error` and all its contents. After being called error will not point to a valid memory | ||
/// address so any further actions on it could lead to undefined behavior. | ||
/// | ||
/// # Safety | ||
/// | ||
/// Only pass null or a pointer to a valid DDSketchError created by this library. | ||
#[no_mangle] | ||
pub unsafe extern "C" fn ddog_ddsketch_error_free(error: Option<Box<DDSketchError>>) { | ||
drop(error) | ||
} | ||
Comment on lines
+56
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm hoping we may be able to move away from the ddsketch-specific error code; but if not, I think this should be |
||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn constructor_test() { | ||
let code = DDSketchErrorCode::InvalidArgument; | ||
let error = Box::new(DDSketchError::new(code, &code.to_string())); | ||
|
||
assert_eq!(error.code, DDSketchErrorCode::InvalidArgument); | ||
let msg = error.msg.as_cstr().into_std().to_str().unwrap(); | ||
assert_eq!(msg, DDSketchErrorCode::InvalidArgument.to_string()); | ||
} | ||
|
||
#[test] | ||
fn destructor_test() { | ||
let code = DDSketchErrorCode::InvalidArgument; | ||
let error = Box::new(DDSketchError::new(code, &code.to_string())); | ||
|
||
assert_eq!(error.code, DDSketchErrorCode::InvalidArgument); | ||
let msg = error.msg.as_cstr().into_std().to_str().unwrap(); | ||
assert_eq!(msg, DDSketchErrorCode::InvalidArgument.to_string()); | ||
|
||
unsafe { ddog_ddsketch_error_free(Some(error)) }; | ||
} | ||
|
||
#[test] | ||
fn test_error_with_null_bytes() { | ||
let code = DDSketchErrorCode::InvalidInput; | ||
let error = Box::new(DDSketchError::new(code, "Error with\0null bytes")); | ||
|
||
assert_eq!(error.code, DDSketchErrorCode::InvalidInput); | ||
let msg = error.msg.as_cstr().into_std().to_str().unwrap(); | ||
assert_eq!(msg, ""); // Should fall back to empty string | ||
|
||
unsafe { ddog_ddsketch_error_free(Some(error)) }; | ||
} | ||
Comment on lines
+86
to
+96
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re: my comment in ddcommon for the null bytes in the middle of the string, I don't quite understand how this would happen in normal code? 👀 |
||
} |
Uh oh!
There was an error while loading. Please reload this page.