Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5c9f168
feat(profiling): parallel set and string set
morrisonlevi Nov 14, 2025
2f6bdcf
test: exercise thread safety
morrisonlevi Nov 14, 2025
7e5ae4b
feat(profiling): add ProfilesDictionary
morrisonlevi Nov 14, 2025
316f1ee
Merge branch 'main' into levi/profiles_dictionary
morrisonlevi Nov 17, 2025
2f8c573
feat(profiling): Profile::try_add_sample2
morrisonlevi Nov 17, 2025
a43f7bc
test: new functions
morrisonlevi Nov 18, 2025
939907b
test: write the test in a more obvious way
morrisonlevi Nov 19, 2025
cfc8a17
docs: improve ProfilesDictionary and related types
morrisonlevi Nov 19, 2025
89b7a8e
Merge branch 'main' into levi/profiles_dictionary
morrisonlevi Nov 19, 2025
aaf1b01
Merge branch 'levi/profiles_dictionary' into levi/try_add_sample2
morrisonlevi Nov 19, 2025
91d6ac3
refactor: try_new2 -> try_new_with_dictionary
morrisonlevi Nov 19, 2025
38c8e3c
fix: bench too
morrisonlevi Nov 20, 2025
45580ed
test: v1 and v2 have compat reprs
morrisonlevi Dec 2, 2025
524c417
refactor: move transmute into From
morrisonlevi Dec 2, 2025
1fb7d57
Merge branch 'levi/profiles_dictionary' into levi/try_add_sample2
morrisonlevi Dec 2, 2025
e8a1ad8
refactor: more cautious around transmutes
morrisonlevi Dec 3, 2025
4f0168e
docs: repr(C) Function/Mapping
morrisonlevi Dec 3, 2025
f1bec40
Merge branch 'main' into levi/profiles_dictionary
morrisonlevi Dec 3, 2025
8e35d78
Merge branch 'levi/profiles_dictionary' into levi/try_add_sample2
morrisonlevi Dec 3, 2025
f2b3f8a
refactor: move transmute into From
morrisonlevi Dec 2, 2025
7d451b3
Merge branch 'levi/profiles_dictionary' into levi/try_add_sample2
morrisonlevi Dec 3, 2025
f1a03bf
refactor: expand unsafe and better document it
morrisonlevi Dec 3, 2025
19bb0ef
docs: try_reserve_exact optimization for Box<[]>
morrisonlevi Dec 3, 2025
eae2db2
style: remove unused import
morrisonlevi Dec 3, 2025
0de818a
Merge branch 'levi/profiles_dictionary' into levi/try_add_sample2
morrisonlevi Dec 3, 2025
ef5c761
style: remove unused import
morrisonlevi Dec 3, 2025
b315745
refactor: impl Default for internal::Function
morrisonlevi Dec 3, 2025
d21dd81
bench: fix filename assignment
morrisonlevi Dec 3, 2025
b38db9c
Merge branch 'main' into levi/try_add_sample2
morrisonlevi Dec 3, 2025
e677b96
Merge branch 'main' into levi/try_add_sample2
morrisonlevi Dec 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions libdd-profiling-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
#![cfg_attr(not(test), deny(clippy::todo))]
#![cfg_attr(not(test), deny(clippy::unimplemented))]

#[cfg(all(feature = "symbolizer", not(target_os = "windows")))]
pub use symbolizer_ffi::*;

mod exporter;
mod profiles;
mod string_storage;

#[cfg(all(feature = "symbolizer", not(target_os = "windows")))]
pub use symbolizer_ffi::*;

// re-export crashtracker ffi
#[cfg(feature = "crashtracker-ffi")]
pub use libdd_crashtracker_ffi::*;
Expand Down
3 changes: 1 addition & 2 deletions libdd-profiling-ffi/src/profiles/datatypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ use anyhow::Context;
use function_name::named;
use libdd_common_ffi::slice::{AsBytes, ByteSlice, CharSlice, Slice};
use libdd_common_ffi::{wrap_with_ffi_result, Error, Handle, Timespec, ToInner};
use libdd_profiling::api;
use libdd_profiling::api::ManagedStringId;
use libdd_profiling::api::{self, ManagedStringId};
use libdd_profiling::internal;
use std::num::NonZeroI64;
use std::str::Utf8Error;
Expand Down
157 changes: 157 additions & 0 deletions libdd-profiling/benches/add_samples.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Copyright 2025-Present Datadog, Inc.
// SPDX-License-Identifier: Apache-2.0

use criterion::*;
use libdd_profiling::api2::Location2;
use libdd_profiling::profiles::collections::SetId;
use libdd_profiling::profiles::datatypes::{Function, FunctionId2, MappingId2};
use libdd_profiling::{self as profiling, api, api2};

fn make_sample_types() -> Vec<api::ValueType<'static>> {
vec![api::ValueType::new("samples", "count")]
}

fn make_stack_api(frames: &[Frame]) -> (Vec<api::Location<'static>>, Vec<i64>) {
// No mappings in Ruby, but the v1 API requires it.
let mapping = api::Mapping::default();
let mut locations = Vec::with_capacity(frames.len());
for frame in frames {
locations.push(api::Location {
mapping,
function: api::Function {
name: frame.function_name,
filename: frame.function_name,
..Default::default()
},
line: frame.line_number as i64,
..Default::default()
});
}
let values = vec![1i64];
(locations, values)
}

fn make_stack_api2(frames: &[Frame2]) -> (Vec<Location2>, Vec<i64>) {
let mut locations = Vec::with_capacity(frames.len());

for frame in frames {
locations.push(Location2 {
mapping: MappingId2::default(),
function: frame.function,
address: 0,
line: frame.line_number as i64,
});
Comment on lines +37 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this make sense as a .into() function

}

let values = vec![1i64];
(locations, values)
}

#[derive(Clone, Copy)]
struct Frame {
file_name: &'static str,
line_number: u32,
function_name: &'static str,
}
impl Frame {
pub const fn new(
file_name: &'static str,
line_number: u32,
function_name: &'static str,
) -> Self {
Self {
file_name,
line_number,
function_name,
}
}
}

#[derive(Clone, Copy)]
struct Frame2 {
function: FunctionId2,
line_number: u32,
}

pub fn bench_add_sample_vs_add2(c: &mut Criterion) {
let sample_types = make_sample_types();
let dict = profiling::profiles::datatypes::ProfilesDictionary::try_new().unwrap();

// This is root-to-leaf, instead of leaf-to-root. We'll reverse it below.
// Taken from a Ruby app, everything here is source-available.
let mut frames = [
Frame::new("/usr/local/bundle/gems/logging-2.4.0/lib/logging/diagnostic_context.rb", 474, "create_with_logging_context"),
Frame::new("/usr/local/bundle/gems/puma-6.4.3/lib/puma/thread_pool.rb", 155, "spawn_thread"),
Frame::new("/usr/local/bundle/gems/puma-6.4.3/lib/puma/server.rb", 245, "run"),
Frame::new("/usr/local/bundle/gems/puma-6.4.3/lib/puma/server.rb", 464, "process_client"),
Frame::new("/usr/local/bundle/gems/puma-6.4.3/lib/puma/request.rb", 99, "handle_request"),
Frame::new("/usr/local/bundle/gems/puma-6.4.3/lib/puma/thread_pool.rb", 378, "with_force_shutdown"),
Frame::new("/usr/local/bundle/gems/puma-6.4.3/lib/puma/request.rb", 100, "handle_request"),
Frame::new("/usr/local/bundle/gems/puma-6.4.3/lib/puma/configuration.rb", 272, "call"),
Frame::new("/usr/local/bundle/gems/railties-7.0.8.7/lib/rails/engine.rb", 530, "call"),
Frame::new("/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/tracing/contrib/rack/middlewares.rb", 474, "call"),
Frame::new("/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb", 17, "call"),
Frame::new("/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/tracing/contrib/rack/middlewares.rb", 70, "call"),
Frame::new("/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/appsec/contrib/rack/request_middleware.rb", 82, "call"),
Frame::new("/usr/local/lib/libruby.so.3.3", 0, "catch"),
Frame::new("/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/appsec/contrib/rack/request_middleware.rb", 85, "catch"),
Frame::new("/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/appsec/instrumentation/gateway.rb", 41, "push"),
Frame::new("/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/appsec/instrumentation/gateway.rb", 37, "push"),
Frame::new("/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/appsec/instrumentation/gateway/middleware.rb", 18, "call"),
];
frames.reverse();

let strings = dict.strings();
let functions = dict.functions();

let frames2 = frames.map(|f| {
let set_id = functions
.try_insert(Function {
name: strings.try_insert(f.file_name).unwrap(),
system_name: Default::default(),
file_name: strings.try_insert(f.file_name).unwrap(),
})
.unwrap();
Frame2 {
function: unsafe { core::mem::transmute::<SetId<Function>, FunctionId2>(set_id) },
line_number: f.line_number,
}
});
let dict = profiling::profiles::collections::Arc::try_new(dict).unwrap();

c.bench_function("profile_add_sample_frames_x1000", |b| {
b.iter(|| {
let mut profile = profiling::internal::Profile::try_new(&sample_types, None).unwrap();
let (locations, values) = make_stack_api(frames.as_slice());
for _ in 0..1000 {
let sample = api::Sample {
locations: locations.clone(),
values: &values,
labels: vec![],
};
black_box(profile.try_add_sample(sample, None)).unwrap();
}
black_box(profile.only_for_testing_num_aggregated_samples())
})
});

c.bench_function("profile_add_sample2_frames_x1000", |b| {
b.iter(|| {
let mut profile = profiling::internal::Profile::try_new_with_dictionary(
&sample_types,
None,
dict.try_clone().unwrap(),
)
.unwrap();
let (locations, values) = make_stack_api2(frames2.as_slice());
for _ in 0..1000 {
// Provide an empty iterator for labels conversion path
let labels_iter = std::iter::empty::<anyhow::Result<api2::Label>>();
black_box(profile.try_add_sample2(&locations, &values, labels_iter, None)).unwrap();
}
black_box(profile.only_for_testing_num_aggregated_samples())
})
});
}

criterion_group!(benches, bench_add_sample_vs_add2);
3 changes: 2 additions & 1 deletion libdd-profiling/benches/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use criterion::criterion_main;

mod add_samples;
mod interning_strings;

criterion_main!(interning_strings::benches);
criterion_main!(interning_strings::benches, add_samples::benches);
57 changes: 57 additions & 0 deletions libdd-profiling/src/api2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0

use crate::profiles::datatypes::{FunctionId2, MappingId2, StringId2};

#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
pub struct Location2 {
pub mapping: MappingId2,
pub function: FunctionId2,

/// The instruction address for this location, if available. It
/// should be within [Mapping.memory_start...Mapping.memory_limit]
/// for the corresponding mapping. A non-leaf address may be in the
/// middle of a call instruction. It is up to display tools to find
/// the beginning of the instruction if necessary.
pub address: u64,
pub line: i64,
}

#[derive(Copy, Clone, Debug, Default)]
pub struct Label<'a> {
pub key: StringId2,

/// At most one of `.str` and `.num` should not be empty.
pub str: &'a str,
pub num: i64,

/// Should only be present when num is present.
/// Specifies the units of num.
/// Use arbitrary string (for example, "requests") as a custom count unit.
/// If no unit is specified, consumer may apply heuristic to deduce the unit.
/// Consumers may also interpret units like "bytes" and "kilobytes" as memory
/// units and units like "seconds" and "nanoseconds" as time units,
/// and apply appropriate unit conversions to these.
pub num_unit: &'a str,
}
Comment on lines +21 to +37
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to use Rust enums here to enforce this?

Copy link
Contributor Author

@morrisonlevi morrisonlevi Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the code does model it this way, such as the internal::Label. But the api2::Label is meant to be more similar to api::Label for easier adoption, and it does not use enums because it more closely mirrors the FFI.


impl<'a> Label<'a> {
pub const fn str(key: StringId2, str: &'a str) -> Label<'a> {
Label {
key,
str,
num: 0,
num_unit: "",
}
}

pub const fn num(key: StringId2, num: i64, num_unit: &'a str) -> Label<'a> {
Label {
key,
str: "",
num,
num_unit,
}
}
}
7 changes: 7 additions & 0 deletions libdd-profiling/src/internal/owned_types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ pub struct Period {
pub value: i64,
}

impl<'a> From<api::Period<'a>> for Period {
#[inline]
fn from(period: api::Period<'a>) -> Self {
Period::from(&period)
}
}

impl<'a> From<&'a api::Period<'a>> for Period {
#[inline]
fn from(period: &'a api::Period<'a>) -> Self {
Expand Down
Loading
Loading