|
| 1 | +// Copyright 2025-Present Datadog, Inc. |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +use criterion::*; |
| 5 | +use libdd_profiling::api2::Location2; |
| 6 | +use libdd_profiling::profiles::datatypes::{Function, FunctionId2, MappingId2}; |
| 7 | +use libdd_profiling::{self as profiling, api, api2}; |
| 8 | + |
| 9 | +fn make_sample_types() -> Vec<api::ValueType<'static>> { |
| 10 | + vec![api::ValueType::new("samples", "count")] |
| 11 | +} |
| 12 | + |
| 13 | +fn make_stack_api(frames: &[Frame]) -> (Vec<api::Location<'static>>, Vec<i64>) { |
| 14 | + // No mappings in Ruby, but the v1 API requires it. |
| 15 | + let mapping = api::Mapping::default(); |
| 16 | + let mut locations = Vec::with_capacity(frames.len()); |
| 17 | + for frame in frames { |
| 18 | + locations.push(api::Location { |
| 19 | + mapping, |
| 20 | + function: api::Function { |
| 21 | + name: frame.function_name, |
| 22 | + filename: frame.file_name, |
| 23 | + ..Default::default() |
| 24 | + }, |
| 25 | + line: frame.line_number as i64, |
| 26 | + ..Default::default() |
| 27 | + }); |
| 28 | + } |
| 29 | + let values = vec![1i64]; |
| 30 | + (locations, values) |
| 31 | +} |
| 32 | + |
| 33 | +fn make_stack_api2(frames: &[Frame2]) -> (Vec<Location2>, Vec<i64>) { |
| 34 | + let mut locations = Vec::with_capacity(frames.len()); |
| 35 | + |
| 36 | + for frame in frames { |
| 37 | + locations.push(Location2 { |
| 38 | + mapping: MappingId2::default(), |
| 39 | + function: frame.function, |
| 40 | + address: 0, |
| 41 | + line: frame.line_number as i64, |
| 42 | + }); |
| 43 | + } |
| 44 | + |
| 45 | + let values = vec![1i64]; |
| 46 | + (locations, values) |
| 47 | +} |
| 48 | + |
| 49 | +#[derive(Clone, Copy)] |
| 50 | +struct Frame { |
| 51 | + file_name: &'static str, |
| 52 | + line_number: u32, |
| 53 | + function_name: &'static str, |
| 54 | +} |
| 55 | + |
| 56 | +impl Frame { |
| 57 | + pub const fn new( |
| 58 | + file_name: &'static str, |
| 59 | + line_number: u32, |
| 60 | + function_name: &'static str, |
| 61 | + ) -> Self { |
| 62 | + Self { |
| 63 | + file_name, |
| 64 | + line_number, |
| 65 | + function_name, |
| 66 | + } |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +#[derive(Clone, Copy)] |
| 71 | +struct Frame2 { |
| 72 | + function: FunctionId2, |
| 73 | + line_number: u32, |
| 74 | +} |
| 75 | + |
| 76 | +pub fn bench_add_sample_vs_add2(c: &mut Criterion) { |
| 77 | + let sample_types = make_sample_types(); |
| 78 | + let dict = profiling::profiles::datatypes::ProfilesDictionary::try_new().unwrap(); |
| 79 | + |
| 80 | + // This is root-to-leaf, instead of leaf-to-root. We'll reverse it below. |
| 81 | + // Taken from a Ruby app, everything here is source-available. |
| 82 | + let mut frames = [ |
| 83 | + Frame::new("/usr/local/bundle/gems/logging-2.4.0/lib/logging/diagnostic_context.rb", 474, "create_with_logging_context"), |
| 84 | + Frame::new("/usr/local/bundle/gems/puma-6.4.3/lib/puma/thread_pool.rb", 155, "spawn_thread"), |
| 85 | + Frame::new("/usr/local/bundle/gems/puma-6.4.3/lib/puma/server.rb", 245, "run"), |
| 86 | + Frame::new("/usr/local/bundle/gems/puma-6.4.3/lib/puma/server.rb", 464, "process_client"), |
| 87 | + Frame::new("/usr/local/bundle/gems/puma-6.4.3/lib/puma/request.rb", 99, "handle_request"), |
| 88 | + Frame::new("/usr/local/bundle/gems/puma-6.4.3/lib/puma/thread_pool.rb", 378, "with_force_shutdown"), |
| 89 | + Frame::new("/usr/local/bundle/gems/puma-6.4.3/lib/puma/request.rb", 100, "handle_request"), |
| 90 | + Frame::new("/usr/local/bundle/gems/puma-6.4.3/lib/puma/configuration.rb", 272, "call"), |
| 91 | + Frame::new("/usr/local/bundle/gems/railties-7.0.8.7/lib/rails/engine.rb", 530, "call"), |
| 92 | + Frame::new("/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/tracing/contrib/rack/middlewares.rb", 474, "call"), |
| 93 | + Frame::new("/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb", 17, "call"), |
| 94 | + Frame::new("/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/tracing/contrib/rack/middlewares.rb", 70, "call"), |
| 95 | + Frame::new("/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/appsec/contrib/rack/request_middleware.rb", 82, "call"), |
| 96 | + Frame::new("/usr/local/lib/libruby.so.3.3", 0, "catch"), |
| 97 | + Frame::new("/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/appsec/contrib/rack/request_middleware.rb", 85, "catch"), |
| 98 | + Frame::new("/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/appsec/instrumentation/gateway.rb", 41, "push"), |
| 99 | + Frame::new("/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/appsec/instrumentation/gateway.rb", 37, "push"), |
| 100 | + Frame::new("/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/appsec/instrumentation/gateway/middleware.rb", 18, "call"), |
| 101 | + ]; |
| 102 | + frames.reverse(); |
| 103 | + |
| 104 | + let strings = dict.strings(); |
| 105 | + let functions = dict.functions(); |
| 106 | + |
| 107 | + let frames2 = frames.map(|f| { |
| 108 | + let set_id = functions |
| 109 | + .try_insert(Function { |
| 110 | + name: strings.try_insert(f.file_name).unwrap(), |
| 111 | + system_name: Default::default(), |
| 112 | + file_name: strings.try_insert(f.file_name).unwrap(), |
| 113 | + }) |
| 114 | + .unwrap(); |
| 115 | + Frame2 { |
| 116 | + function: FunctionId2::from(set_id), |
| 117 | + line_number: f.line_number, |
| 118 | + } |
| 119 | + }); |
| 120 | + let dict = profiling::profiles::collections::Arc::try_new(dict).unwrap(); |
| 121 | + |
| 122 | + c.bench_function("profile_add_sample_frames_x1000", |b| { |
| 123 | + b.iter(|| { |
| 124 | + let mut profile = profiling::internal::Profile::try_new(&sample_types, None).unwrap(); |
| 125 | + let (locations, values) = make_stack_api(frames.as_slice()); |
| 126 | + for _ in 0..1000 { |
| 127 | + let sample = api::Sample { |
| 128 | + locations: locations.clone(), |
| 129 | + values: &values, |
| 130 | + labels: vec![], |
| 131 | + }; |
| 132 | + black_box(profile.try_add_sample(sample, None)).unwrap(); |
| 133 | + } |
| 134 | + black_box(profile.only_for_testing_num_aggregated_samples()) |
| 135 | + }) |
| 136 | + }); |
| 137 | + |
| 138 | + c.bench_function("profile_add_sample2_frames_x1000", |b| { |
| 139 | + b.iter(|| { |
| 140 | + let mut profile = profiling::internal::Profile::try_new_with_dictionary( |
| 141 | + &sample_types, |
| 142 | + None, |
| 143 | + dict.try_clone().unwrap(), |
| 144 | + ) |
| 145 | + .unwrap(); |
| 146 | + let (locations, values) = make_stack_api2(frames2.as_slice()); |
| 147 | + for _ in 0..1000 { |
| 148 | + // Provide an empty iterator for labels conversion path |
| 149 | + let labels_iter = std::iter::empty::<anyhow::Result<api2::Label>>(); |
| 150 | + // SAFETY: all ids come from the profile's dictionary. |
| 151 | + black_box(unsafe { |
| 152 | + profile.try_add_sample2(&locations, &values, labels_iter, None) |
| 153 | + }) |
| 154 | + .unwrap(); |
| 155 | + } |
| 156 | + black_box(profile.only_for_testing_num_aggregated_samples()) |
| 157 | + }) |
| 158 | + }); |
| 159 | +} |
| 160 | + |
| 161 | +criterion_group!(benches, bench_add_sample_vs_add2); |
0 commit comments