Skip to content

Commit 25b69f1

Browse files
hooliohgleocadie
andauthored
[crashtracker] Add benches infrastructure (#1180)
Co-authored-by: gleocadie <[email protected]>
1 parent 256abe2 commit 25b69f1

File tree

12 files changed

+314
-46
lines changed

12 files changed

+314
-46
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

benchmark/run_benchmarks_ci.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pushd "${PROJECT_DIR}" > /dev/null
2222

2323
# Run benchmarks
2424
message "Running benchmarks"
25-
cargo bench --workspace -- --warm-up-time 1 --measurement-time 5 --sample-size=200
25+
cargo bench --workspace --features datadog-crashtracker/benchmarking -- --warm-up-time 1 --measurement-time 5 --sample-size=200
2626
message "Finished running benchmarks"
2727

2828
# Copy the benchmark results to the output directory

datadog-crashtracker/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ edition.workspace = true
66
version.workspace = true
77
rust-version.workspace = true
88
license.workspace = true
9+
autobenches = false
910

1011
[lib]
1112
crate-type = ["lib"]
@@ -16,6 +17,11 @@ name = "crashtracker-receiver"
1617
path = "src/bin/crashtracker_receiver.rs"
1718
bench = false
1819

20+
[[bench]]
21+
name = "main"
22+
harness = false
23+
path = "benches/main.rs"
24+
1925
[features]
2026
default = ["collector", "receiver", "collector_windows"]
2127
# Enables the in-process collection of crash-info
@@ -25,6 +31,8 @@ receiver = []
2531
# Enables the collection of crash-info on Windows
2632
collector_windows = []
2733
generate-unit-test-files = []
34+
# Enables benchmark functionality
35+
benchmarking = ["generate-unit-test-files"]
2836

2937
[target.'cfg(unix)'.dependencies]
3038
# Should be kept in sync with the libdatadog symbolizer crate (also using blasesym)
@@ -58,6 +66,7 @@ thiserror = "1.0"
5866
windows = { version = "0.59.0", features = ["Win32_System_Diagnostics_Debug", "Win32_System_Diagnostics_ToolHelp", "Win32_System_ErrorReporting", "Win32_System_Kernel", "Win32_System_ProcessStatus", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_Security"] }
5967

6068
[dev-dependencies]
69+
criterion = { version = "0.5.1" }
6170
goblin = "0.9.3"
6271
tempfile = { version = "3.3" }
6372

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use criterion::{criterion_group, criterion_main, Criterion};
5+
6+
#[cfg(all(unix, feature = "benchmarking"))]
7+
mod receiver_bench;
8+
9+
#[cfg(all(unix, feature = "benchmarking"))]
10+
fn active_benches(_: &mut Criterion) {
11+
receiver_bench::benches();
12+
}
13+
14+
#[cfg(any(windows, not(feature = "benchmarking")))]
15+
fn active_benches(_: &mut Criterion) {
16+
println!("Benchmarks are disabled.");
17+
}
18+
criterion_group!(benches, active_benches);
19+
criterion_main!(benches);
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use criterion::{black_box, criterion_group, BenchmarkId, Criterion};
5+
use datadog_crashtracker::benchmark::receiver_entry_point;
6+
use datadog_crashtracker::shared::constants::*;
7+
use datadog_crashtracker::{
8+
default_signals, get_data_folder_path, CrashtrackerConfiguration, SharedLibrary,
9+
StacktraceCollection,
10+
};
11+
use std::fmt::Write;
12+
use std::time::Duration;
13+
use tokio::io::BufReader;
14+
15+
macro_rules! add_frame {
16+
($report:expr, $fn:expr, $lib:expr) => {
17+
add_frame!($report, $lib.get_symbol_address($fn).unwrap().as_str());
18+
};
19+
($report:expr, $address:expr) => {
20+
writeln!(
21+
$report,
22+
"{{ \"ip\": \"{}\", \"module_address\": \"0x21\", \"sp\": \"0x11\" }}",
23+
$address
24+
)
25+
.expect("Failed to write frame");
26+
};
27+
}
28+
29+
fn add_proc_info(report: &mut String) {
30+
writeln!(report, "{DD_CRASHTRACK_BEGIN_PROCINFO}")
31+
.expect("Failed to write DD_CRASHTRACK_BEGIN_PROCINFO");
32+
writeln!(report, "{{ \"pid\": {} }}", std::process::id()).expect("Failed to write PID");
33+
writeln!(report, "{DD_CRASHTRACK_END_PROCINFO}")
34+
.expect("Failed to write DD_CRASHTRACK_END_PROCINFO");
35+
}
36+
37+
fn add_config(report: &mut String) {
38+
writeln!(report, "{DD_CRASHTRACK_BEGIN_CONFIG}")
39+
.expect("Failed to write DD_CRASHTRACK_BEGIN_CONFIG");
40+
let config = CrashtrackerConfiguration::new(
41+
vec![], // additional_files
42+
true, // create_alt_stack
43+
true, // use_alt_stack
44+
None,
45+
StacktraceCollection::EnabledWithSymbolsInReceiver,
46+
default_signals(),
47+
Some(Duration::from_secs(10)),
48+
Some("".to_string()), // unix_socket_path
49+
true, // demangle_names
50+
)
51+
.expect("Failed to create crashtracker configuration");
52+
let config_str =
53+
serde_json::to_string(&config).expect("Failed to serialize crashtracker configuration");
54+
writeln!(report, "{}", config_str).expect("Failed to write crashtracker configuration");
55+
writeln!(report, "{DD_CRASHTRACK_END_CONFIG}")
56+
.expect("Failed to write DD_CRASHTRACK_END_CONFIG");
57+
}
58+
59+
fn add_stacktrace(report: &mut String, test_cpp_so: &SharedLibrary, test_c_so: &SharedLibrary) {
60+
writeln!(report, "{DD_CRASHTRACK_BEGIN_STACKTRACE}")
61+
.expect("Failed to write DD_CRASHTRACK_BEGIN_STACKTRACE");
62+
63+
add_frame!(report, "my_function", test_c_so);
64+
add_frame!(report, "func1", test_c_so);
65+
add_frame!(report, "func2", test_c_so);
66+
add_frame!(report, "0x01");
67+
add_frame!(report, "0x02");
68+
add_frame!(report, "_Z12cpp_functionv", test_cpp_so);
69+
add_frame!(
70+
report,
71+
"_ZN10FirstClass10InnerClass12InnerMethod1Ev",
72+
test_cpp_so
73+
);
74+
add_frame!(
75+
report,
76+
"_ZN10FirstClass10InnerClass12InnerMethod2Ev",
77+
test_cpp_so
78+
);
79+
add_frame!(report, "0x03");
80+
add_frame!(report, "0x03");
81+
add_frame!(report, "0x05");
82+
add_frame!(report, "func3", test_c_so);
83+
add_frame!(report, "_ZN10FirstClass7Method1Ev", test_cpp_so);
84+
add_frame!(report, "func4", test_c_so);
85+
add_frame!(
86+
report,
87+
"_ZN10FirstClass7Method2EibNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE",
88+
test_cpp_so
89+
);
90+
add_frame!(report, "0x06");
91+
add_frame!(report, "func5", test_c_so);
92+
add_frame!(report, "func6", test_c_so);
93+
add_frame!(
94+
report,
95+
"_ZN11MyNamespace16ClassInNamespace18MethodInNamespace1Efx",
96+
test_cpp_so
97+
);
98+
add_frame!(report, "0x07");
99+
add_frame!(
100+
report,
101+
"_ZN11MyNamespace16ClassInNamespace18MethodInNamespace2Edc",
102+
test_cpp_so
103+
);
104+
add_frame!(report, "0x08");
105+
add_frame!(report, "func6", test_c_so);
106+
add_frame!(report, "0x09");
107+
add_frame!(
108+
report,
109+
"_ZN11MyNamespace16ClassInNamespace21InnerClassInNamespace12InnerMethod1Ev",
110+
test_cpp_so
111+
);
112+
add_frame!(report, "0x0A");
113+
add_frame!(
114+
report,
115+
"_ZN11MyNamespace16ClassInNamespace21InnerClassInNamespace12InnerMethod2Eiix",
116+
test_cpp_so
117+
);
118+
add_frame!(report, "0x0B");
119+
add_frame!(report, "func7", test_c_so);
120+
add_frame!(report, "func8", test_c_so);
121+
add_frame!(
122+
report,
123+
"_ZN11MyNamespace16ClassInNamespace21InnerClassInNamespace12InnerMethod2Eiix",
124+
test_cpp_so
125+
);
126+
add_frame!(report, "func9", test_c_so);
127+
add_frame!(report, "func10", test_c_so);
128+
add_frame!(report, "0x00");
129+
130+
writeln!(report, "{DD_CRASHTRACK_END_STACKTRACE}")
131+
.expect("Failed to write DD_CRASHTRACK_END_STACKTRACE");
132+
}
133+
134+
fn create_crash_report(test_cpp_so: &SharedLibrary, test_c_so: &SharedLibrary) -> String {
135+
// Manual test revealed that the report size was arount 3000 bytes
136+
let mut report = String::with_capacity(3000);
137+
add_proc_info(&mut report);
138+
add_config(&mut report);
139+
add_stacktrace(&mut report, test_cpp_so, test_c_so);
140+
writeln!(report, "{DD_CRASHTRACK_DONE}").expect("Failed to write DD_CRASHTRACK_DONE");
141+
report
142+
}
143+
144+
async fn bench_receiver_entry_point_from_str(data: &str) {
145+
let cursor = std::io::Cursor::new(data.as_bytes());
146+
let reader = BufReader::new(cursor);
147+
let timeout = Duration::from_millis(5000);
148+
149+
let _ = receiver_entry_point(timeout, reader).await;
150+
}
151+
152+
fn load_test_libraries() -> (SharedLibrary, SharedLibrary) {
153+
let sofile_c_path = get_data_folder_path()
154+
.expect("Failed to get the data folder path")
155+
.join("libtest.so")
156+
.canonicalize()
157+
.unwrap();
158+
159+
let sofile_cpp_path = get_data_folder_path()
160+
.expect("Failed to get the data folder path")
161+
.join("libtest_cpp.so")
162+
.canonicalize()
163+
.unwrap();
164+
165+
let test_c_so = SharedLibrary::open(sofile_c_path.to_str().unwrap()).unwrap();
166+
let test_cpp_so = SharedLibrary::open(sofile_cpp_path.to_str().unwrap()).unwrap();
167+
(test_cpp_so, test_c_so)
168+
}
169+
170+
pub fn receiver_entry_point_benchmarks(c: &mut Criterion) {
171+
let mut group = c.benchmark_group("receiver_entry_point");
172+
173+
// the libraries must be opened as long as the benchmark is running
174+
// That why we pass them as references
175+
let (sofile_cpp, sofile_c) = load_test_libraries();
176+
let report = create_crash_report(&sofile_cpp, &sofile_c);
177+
group.bench_with_input(
178+
BenchmarkId::new("report", report.len()),
179+
&report,
180+
|b, data| {
181+
b.iter(|| {
182+
let rt = tokio::runtime::Builder::new_current_thread()
183+
.enable_all()
184+
.build()
185+
.unwrap();
186+
rt.block_on(bench_receiver_entry_point_from_str(black_box(data)))
187+
});
188+
},
189+
);
190+
}
191+
192+
criterion_group!(benches, receiver_entry_point_benchmarks);

datadog-crashtracker/src/common.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use libc::c_void;
5+
use std::ffi::CString;
6+
use std::path::Path;
7+
use std::path::PathBuf;
8+
9+
pub fn get_data_folder_path() -> std::io::Result<PathBuf> {
10+
Path::new(&env!("CARGO_MANIFEST_DIR"))
11+
.join("data")
12+
.canonicalize()
13+
}
14+
15+
pub struct SharedLibrary {
16+
handle: *mut c_void,
17+
}
18+
19+
impl SharedLibrary {
20+
pub fn open(lib_path: &str) -> Result<Self, String> {
21+
let cstr = CString::new(lib_path).map_err(|e| e.to_string())?;
22+
// Use RTLD_NOW or another flag
23+
let handle = unsafe { libc::dlopen(cstr.as_ptr(), libc::RTLD_NOW) };
24+
if handle.is_null() {
25+
Err("Failed to open library".to_string())
26+
} else {
27+
Ok(Self { handle })
28+
}
29+
}
30+
31+
pub fn get_symbol_address(&self, symbol: &str) -> Result<String, String> {
32+
let cstr = CString::new(symbol).map_err(|e| e.to_string())?;
33+
let sym = unsafe { libc::dlsym(self.handle, cstr.as_ptr()) };
34+
if sym.is_null() {
35+
Err(format!("Failed to find symbol: {}", symbol))
36+
} else {
37+
Ok(format!("{:p}", sym))
38+
}
39+
}
40+
}
41+
42+
impl Drop for SharedLibrary {
43+
fn drop(&mut self) {
44+
if !self.handle.is_null() {
45+
unsafe { libc::dlclose(self.handle) };
46+
}
47+
}
48+
}

datadog-crashtracker/src/crash_info/stacktrace.rs

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -395,54 +395,13 @@ mod tests {
395395
#[cfg(test)]
396396
mod unix_test {
397397
use super::*;
398-
use libc::c_void;
399-
use std::ffi::CString;
400-
use std::path::Path;
401-
use std::path::PathBuf;
402-
403-
struct SharedLibrary {
404-
handle: *mut c_void,
405-
}
406-
407-
impl SharedLibrary {
408-
fn open(lib_path: &str) -> Result<Self, String> {
409-
let cstr = CString::new(lib_path).map_err(|e| e.to_string())?;
410-
// Use RTLD_NOW or another flag
411-
let handle = unsafe { libc::dlopen(cstr.as_ptr(), libc::RTLD_NOW) };
412-
if handle.is_null() {
413-
Err("Failed to open library".to_string())
414-
} else {
415-
Ok(Self { handle })
416-
}
417-
}
418-
419-
fn get_symbol_address(&self, symbol: &str) -> Result<String, String> {
420-
let cstr = CString::new(symbol).map_err(|e| e.to_string())?;
421-
let sym = unsafe { libc::dlsym(self.handle, cstr.as_ptr()) };
422-
if sym.is_null() {
423-
Err(format!("Failed to find symbol: {}", symbol))
424-
} else {
425-
Ok(format!("{:p}", sym))
426-
}
427-
}
428-
}
429-
430-
impl Drop for SharedLibrary {
431-
fn drop(&mut self) {
432-
if !self.handle.is_null() {
433-
unsafe { libc::dlclose(self.handle) };
434-
}
435-
}
436-
}
437-
438-
fn get_data_folder_path() -> PathBuf {
439-
Path::new(&env!("CARGO_MANIFEST_DIR")).join("data")
440-
}
398+
use crate::{get_data_folder_path, SharedLibrary};
441399

442400
#[test]
443401
#[cfg_attr(miri, ignore)]
444402
fn test_normalize_ip() {
445403
let test_so = get_data_folder_path()
404+
.expect("Failed to get the data folder path")
446405
.join("libtest.so")
447406
.canonicalize()
448407
.unwrap();
@@ -476,6 +435,7 @@ mod unix_test {
476435
#[cfg_attr(miri, ignore)]
477436
fn test_normalize_ip_cpp() {
478437
let test_so = get_data_folder_path()
438+
.expect("Failed to get the data folder path")
479439
.join("libtest_cpp.so")
480440
.canonicalize()
481441
.unwrap();
@@ -509,6 +469,7 @@ mod unix_test {
509469
#[cfg_attr(miri, ignore)]
510470
fn test_symbolization() {
511471
let test_so = get_data_folder_path()
472+
.expect("Failed to get the data folder path")
512473
.join("libtest.so")
513474
.canonicalize()
514475
.unwrap();
@@ -535,6 +496,7 @@ mod unix_test {
535496
#[cfg_attr(miri, ignore)]
536497
fn test_symbolization_cpp() {
537498
let test_so = get_data_folder_path()
499+
.expect("Failed to get the data folder path")
538500
.join("libtest_cpp.so")
539501
.canonicalize()
540502
.unwrap();

0 commit comments

Comments
 (0)