diff --git a/.gitmodules b/.gitmodules index 03307501..e143b34e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ [submodule "crates/divan_compat/examples/src/the_algorithms/Rust"] path = crates/divan_compat/examples/src/the_algorithms/Rust url = https://github.com/TheAlgorithms/Rust.git +[submodule "crates/codspeed/instrument-hooks"] + path = crates/codspeed/instrument-hooks + url = https://github.com/CodSpeedHQ/instrument-hooks diff --git a/Cargo.lock b/Cargo.lock index e35de18c..83b9d166 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -387,12 +387,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dfdb4953a096c551ce9ace855a604d702e6e62d77fac690575ae347571717f5" [[package]] -name = "bincode" -version = "1.3.3" +name = "bindgen" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "serde", + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools 0.10.5", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", ] [[package]] @@ -502,9 +513,22 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.6" +version = "1.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" +checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] [[package]] name = "cfg-if" @@ -545,6 +569,17 @@ dependencies = [ "half", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.17" @@ -591,7 +626,8 @@ name = "codspeed" version = "3.0.5" dependencies = [ "anyhow", - "bincode", + "bindgen", + "cc", "colored", "glob", "libc", @@ -894,6 +930,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "find-msvc-tools" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" + [[package]] name = "float-cmp" version = "0.9.0" @@ -1184,6 +1226,16 @@ version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "libmimalloc-sys" version = "0.1.39" @@ -1230,6 +1282,12 @@ dependencies = [ "libmimalloc-sys", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -1241,9 +1299,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -1251,6 +1309,16 @@ dependencies = [ "libc", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -1433,6 +1501,16 @@ dependencies = [ "termtree", ] +[[package]] +name = "prettyplease" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro-crate" version = "3.2.0" @@ -1569,6 +1647,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" version = "0.37.27" @@ -1652,6 +1736,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -2017,6 +2107,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/crates/codspeed/Cargo.toml b/crates/codspeed/Cargo.toml index 70232db8..c2834acb 100644 --- a/crates/codspeed/Cargo.toml +++ b/crates/codspeed/Cargo.toml @@ -19,11 +19,10 @@ keywords = ["codspeed", "benchmark"] [dependencies] anyhow = { workspace = true } -bincode = "1.3.3" colored = "2.0.0" glob = "0.3.2" libc = "^0.2" -nix = { version = "0.29.0", features = ["fs"] } +nix = { version = "0.30.1", features = ["time"] } serde = { workspace = true } serde_json = { workspace = true } statrs = { version = "0.18.0", default-features = false } @@ -35,3 +34,7 @@ harness = false [dev-dependencies] tempfile = { workspace = true } + +[build-dependencies] +bindgen = "0.72.1" +cc = "1.0" diff --git a/crates/codspeed/build.rs b/crates/codspeed/build.rs new file mode 100644 index 00000000..3eef6527 --- /dev/null +++ b/crates/codspeed/build.rs @@ -0,0 +1,24 @@ +use std::{env, path::PathBuf}; + +fn main() { + // Compile the C library + cc::Build::new() + .file("instrument-hooks/dist/core.c") + .include("instrument-hooks/includes") + .flag("-w") // Suppress all warnings + .compile("instrument_hooks"); + + let bindings = bindgen::Builder::default() + .header("instrument-hooks/includes/core.h") + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .generate() + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} diff --git a/crates/codspeed/instrument-hooks b/crates/codspeed/instrument-hooks new file mode 160000 index 00000000..0d3de57f --- /dev/null +++ b/crates/codspeed/instrument-hooks @@ -0,0 +1 @@ +Subproject commit 0d3de57fe46ef97714a41ed028096f6f84fdbd2a diff --git a/crates/codspeed/src/fifo.rs b/crates/codspeed/src/fifo.rs deleted file mode 100644 index 347cb5e9..00000000 --- a/crates/codspeed/src/fifo.rs +++ /dev/null @@ -1,229 +0,0 @@ -pub use super::shared::*; -use anyhow::bail; -use nix::libc::O_NONBLOCK; -use nix::sys::stat; -use nix::unistd::{self, unlink}; -use std::fs::{File, OpenOptions}; -use std::io::Read; -use std::os::unix::fs::OpenOptionsExt; -use std::path::{Path, PathBuf}; - -pub struct BenchGuard { - ctl_fifo: FifoIpc, - ack_fifo: FifoIpc, -} - -impl BenchGuard { - pub fn new(ctl_fifo: &str, ack_fifo: &str) -> anyhow::Result { - let mut instance = Self { - ctl_fifo: FifoIpc::connect(ctl_fifo)?.with_writer()?, - ack_fifo: FifoIpc::connect(ack_fifo)?.with_reader()?, - }; - - instance.send_cmd(Command::SetIntegration { - name: "codspeed-rust".into(), - version: env!("CARGO_PKG_VERSION").into(), - })?; // FIXME: Just send it once - instance.send_cmd(Command::StartBenchmark)?; - - Ok(instance) - } - - pub fn new_with_runner_fifo() -> anyhow::Result { - Self::new(RUNNER_CTL_FIFO, RUNNER_ACK_FIFO) - } - - fn send_cmd(&mut self, cmd: Command) -> anyhow::Result<()> { - self.ctl_fifo.send_cmd(cmd)?; - self.ack_fifo.wait_for_ack(); - - Ok(()) - } -} - -impl Drop for BenchGuard { - fn drop(&mut self) { - self.send_cmd(Command::StopBenchmark) - .expect("Failed to send stop command"); - } -} - -pub fn send_cmd(cmd: Command) -> anyhow::Result<()> { - let mut writer = FifoIpc::connect(RUNNER_CTL_FIFO)?.with_writer()?; - writer.send_cmd(cmd).unwrap(); - - let mut reader = FifoIpc::connect(RUNNER_ACK_FIFO)?.with_reader()?; - reader.wait_for_ack(); - - Ok(()) -} - -pub struct FifoIpc { - path: PathBuf, - reader: Option, - writer: Option, -} - -impl FifoIpc { - /// Creates a new FIFO at the specified path and connects to it. - /// - /// ```rust - /// use codspeed::fifo::{FifoIpc, Command}; - /// - /// // Create the reader before the writer (required!): - /// let mut read_fifo = FifoIpc::create("/tmp/doctest.fifo").unwrap().with_reader().unwrap(); - /// - /// // Connect to the FIFO and send a command - /// let mut fifo = FifoIpc::connect("/tmp/doctest.fifo").unwrap().with_writer().unwrap(); - /// fifo.send_cmd(Command::StartBenchmark).unwrap(); - /// - /// // Receive the command in the reader - /// let cmd = read_fifo.recv_cmd().unwrap(); - /// assert_eq!(cmd, Command::StartBenchmark); - /// ``` - pub fn create>(path: P) -> anyhow::Result { - // Remove the previous FIFO (if it exists) - let _ = unlink(path.as_ref()); - - // Create the FIFO with RWX permissions for the owner - unistd::mkfifo(path.as_ref(), stat::Mode::S_IRWXU)?; - - Self::connect(path.as_ref()) - } - - pub fn connect>(path: P) -> anyhow::Result { - let path = path.into(); - - if !path.exists() { - bail!("FIFO does not exist: {}", path.display()); - } - - Ok(Self { - path, - reader: None, - writer: None, - }) - } - - pub fn with_reader(mut self) -> anyhow::Result { - self.reader = Some( - OpenOptions::new() - .write(true) - .read(true) - .custom_flags(O_NONBLOCK) - .open(&self.path)?, - ); - Ok(self) - } - - /// WARNING: Writer must be opened _AFTER_ the reader. - pub fn with_writer(mut self) -> anyhow::Result { - self.writer = Some( - OpenOptions::new() - .write(true) - .custom_flags(O_NONBLOCK) - .open(&self.path)?, - ); - Ok(self) - } - - pub fn recv_cmd(&mut self) -> anyhow::Result { - // First read the length (u32 = 4 bytes) - let mut len_buffer = [0u8; 4]; - self.read_exact(&mut len_buffer)?; - let message_len = u32::from_le_bytes(len_buffer) as usize; - - // Try to read the message - let mut buffer = vec![0u8; message_len]; - loop { - if self.read_exact(&mut buffer).is_ok() { - break; - } - } - - let decoded = bincode::deserialize(&buffer)?; - Ok(decoded) - } - - pub fn send_cmd(&mut self, cmd: Command) -> anyhow::Result<()> { - use std::io::Write; - - let encoded = bincode::serialize(&cmd)?; - self.write_all(&(encoded.len() as u32).to_le_bytes())?; - self.write_all(&encoded)?; - Ok(()) - } - - pub fn wait_for_ack(&mut self) { - loop { - if let Ok(Command::Ack) = self.recv_cmd() { - break; - } - } - } -} - -impl std::io::Write for FifoIpc { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - if let Some(writer) = self.writer.as_mut() { - writer.write(buf) - } else { - Err(std::io::Error::new( - std::io::ErrorKind::NotConnected, - "Writer not initialized", - )) - } - } - - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } -} - -impl std::io::Read for FifoIpc { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - if let Some(reader) = self.reader.as_mut() { - reader.read(buf) - } else { - Err(std::io::Error::new( - std::io::ErrorKind::NotConnected, - "Reader not initialized", - )) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::io::Write; - - #[test] - fn test_ipc_write_read() { - let mut fifo = FifoIpc::create("/tmp/test1.fifo") - .unwrap() - .with_reader() - .unwrap() - .with_writer() - .unwrap(); - - fifo.write_all(b"Hello").unwrap(); - let mut buffer = [0; 5]; - fifo.read_exact(&mut buffer).unwrap(); - assert_eq!(&buffer, b"Hello"); - } - - #[test] - fn test_ipc_send_recv_cmd() { - let mut fifo = FifoIpc::create("/tmp/test2.fifo") - .unwrap() - .with_reader() - .unwrap() - .with_writer() - .unwrap(); - - fifo.send_cmd(Command::StartBenchmark).unwrap(); - let cmd = fifo.recv_cmd().unwrap(); - assert_eq!(cmd, Command::StartBenchmark); - } -} diff --git a/crates/codspeed/src/instrument_hooks/ffi.rs b/crates/codspeed/src/instrument_hooks/ffi.rs new file mode 100644 index 00000000..04d76a43 --- /dev/null +++ b/crates/codspeed/src/instrument_hooks/ffi.rs @@ -0,0 +1,6 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/crates/codspeed/src/instrument_hooks/mod.rs b/crates/codspeed/src/instrument_hooks/mod.rs new file mode 100644 index 00000000..12ae6164 --- /dev/null +++ b/crates/codspeed/src/instrument_hooks/mod.rs @@ -0,0 +1,176 @@ +#[cfg(unix)] +mod ffi; + +#[cfg(unix)] +mod unix_impl { + use nix::sys::time::TimeValLike; + + use super::ffi; + use std::ffi::CString; + use std::sync::OnceLock; + + pub struct InstrumentHooks(*mut ffi::InstrumentHooks); + + unsafe impl Send for InstrumentHooks {} + unsafe impl Sync for InstrumentHooks {} + + impl InstrumentHooks { + #[inline(always)] + pub fn new() -> Option { + let ptr = unsafe { ffi::instrument_hooks_init() }; + if ptr.is_null() { + None + } else { + Some(InstrumentHooks(ptr)) + } + } + + /// Returns a singleton instance of `InstrumentHooks`. + #[inline(always)] + pub fn instance() -> &'static Self { + static INSTANCE: OnceLock = OnceLock::new(); + INSTANCE.get_or_init(|| { + let instance = + InstrumentHooks::new().expect("Failed to initialize InstrumentHooks"); + instance + .set_integration("codspeed-rust", env!("CARGO_PKG_VERSION")) + .expect("Failed to set integration"); + instance + }) + } + + #[inline(always)] + pub fn is_instrumented(&self) -> bool { + unsafe { ffi::instrument_hooks_is_instrumented(self.0) } + } + + #[inline(always)] + pub fn start_benchmark(&self) -> Result<(), i8> { + let result = unsafe { ffi::instrument_hooks_start_benchmark(self.0) }; + if result == 0 { + Ok(()) + } else { + Err(result) + } + } + + #[inline(always)] + pub fn stop_benchmark(&self) -> Result<(), i8> { + let result = unsafe { ffi::instrument_hooks_stop_benchmark(self.0) }; + if result == 0 { + Ok(()) + } else { + Err(result) + } + } + + #[inline(always)] + pub fn set_executed_benchmark(&self, uri: &str) -> Result<(), i8> { + let pid = std::process::id() as i32; + let c_uri = CString::new(uri).map_err(|_| -1i8)?; + let result = unsafe { + ffi::instrument_hooks_set_executed_benchmark(self.0, pid, c_uri.as_ptr()) + }; + if result == 0 { + Ok(()) + } else { + Err(result) + } + } + + #[inline(always)] + pub fn set_integration(&self, name: &str, version: &str) -> Result<(), i8> { + let c_name = CString::new(name).map_err(|_| -1i8)?; + let c_version = CString::new(version).map_err(|_| -1i8)?; + let result = unsafe { + ffi::instrument_hooks_set_integration(self.0, c_name.as_ptr(), c_version.as_ptr()) + }; + if result == 0 { + Ok(()) + } else { + Err(result) + } + } + + #[inline(always)] + pub fn add_benchmark_timestamps(&self, start: u64, end: u64) { + let pid = std::process::id(); + + unsafe { + ffi::instrument_hooks_add_marker( + self.0, + pid, + ffi::MARKER_TYPE_BENCHMARK_START as u8, + start, + ) + }; + unsafe { + ffi::instrument_hooks_add_marker( + self.0, + pid, + ffi::MARKER_TYPE_BENCHMARK_END as u8, + end, + ) + }; + } + + #[inline(always)] + pub fn current_timestamp() -> u64 { + nix::time::clock_gettime(nix::time::ClockId::CLOCK_MONOTONIC) + .expect("Failed to get current time") + .num_nanoseconds() as u64 + } + } + + impl Drop for InstrumentHooks { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { ffi::instrument_hooks_deinit(self.0) }; + } + } + } +} + +#[cfg(not(unix))] +mod other_impl { + pub struct InstrumentHooks; + + impl InstrumentHooks { + pub fn instance() -> &'static Self { + static INSTANCE: InstrumentHooks = InstrumentHooks; + &INSTANCE + } + + pub fn is_instrumented(&self) -> bool { + false + } + + pub fn start_benchmark(&self) -> Result<(), i8> { + Ok(()) + } + + pub fn stop_benchmark(&self) -> Result<(), i8> { + Ok(()) + } + + pub fn set_executed_benchmark(&self, _uri: &str) -> Result<(), i8> { + Ok(()) + } + + pub fn set_integration(&self, _name: &str, _version: &str) -> Result<(), i8> { + Ok(()) + } + + pub fn add_benchmark_timestamps(&self, _start: u64, _end: u64) {} + + pub fn current_timestamp() -> u64 { + 0 + } + } +} + +#[cfg(unix)] +pub use unix_impl::InstrumentHooks; + +#[cfg(not(unix))] +pub use other_impl::InstrumentHooks; diff --git a/crates/codspeed/src/lib.rs b/crates/codspeed/src/lib.rs index aa7a51d1..929c3662 100644 --- a/crates/codspeed/src/lib.rs +++ b/crates/codspeed/src/lib.rs @@ -1,11 +1,9 @@ pub mod codspeed; -#[cfg(unix)] -pub mod fifo; +pub mod instrument_hooks; mod macros; mod measurement; mod request; -mod shared; pub mod utils; pub mod walltime_results; diff --git a/crates/codspeed/src/shared.rs b/crates/codspeed/src/shared.rs deleted file mode 100644 index 1c8481c0..00000000 --- a/crates/codspeed/src/shared.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! WARNING: Has to be in sync with `runner`. - -#[cfg(unix)] -pub const RUNNER_CTL_FIFO: &str = "/tmp/runner.ctl.fifo"; -#[cfg(unix)] -pub const RUNNER_ACK_FIFO: &str = "/tmp/runner.ack.fifo"; - -#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)] -pub enum Command { - CurrentBenchmark { pid: u32, uri: String }, - StartBenchmark, - StopBenchmark, - Ack, - PingPerf, - SetIntegration { name: String, version: String }, - Err, -} diff --git a/crates/criterion_compat/criterion_fork/src/analysis/mod.rs b/crates/criterion_compat/criterion_fork/src/analysis/mod.rs index 817d4da4..979a0106 100644 --- a/crates/criterion_compat/criterion_fork/src/analysis/mod.rs +++ b/crates/criterion_compat/criterion_fork/src/analysis/mod.rs @@ -297,19 +297,13 @@ mod codspeed { ) { let (uri, bench_name) = create_uri_and_name(id, c); - #[cfg(unix)] + if let Err(error) = + ::codspeed::instrument_hooks::InstrumentHooks::instance().set_executed_benchmark(&uri) { - if let Err(error) = - ::codspeed::fifo::send_cmd(codspeed::fifo::Command::CurrentBenchmark { - pid: std::process::id(), - uri: uri.clone(), - }) + if ::codspeed::utils::running_with_codspeed_runner() + && ::codspeed::utils::is_perf_enabled() { - if codspeed::utils::running_with_codspeed_runner() - && codspeed::utils::is_perf_enabled() - { - eprintln!("Failed to send benchmark URI to runner: {error:?}"); - } + eprintln!("Failed to set executed benchmark URI: {error:?}"); } } diff --git a/crates/criterion_compat/criterion_fork/src/bencher.rs b/crates/criterion_compat/criterion_fork/src/bencher.rs index 198b5ccb..7eb53a59 100644 --- a/crates/criterion_compat/criterion_fork/src/bencher.rs +++ b/crates/criterion_compat/criterion_fork/src/bencher.rs @@ -4,6 +4,8 @@ use std::iter::IntoIterator; use std::time::Duration; use std::time::Instant; +use codspeed::instrument_hooks::InstrumentHooks; + use crate::black_box; use crate::measurement::{Measurement, WallTime}; use crate::BatchSize; @@ -93,6 +95,8 @@ impl<'a, M: Measurement> Bencher<'a, M> { R: FnMut() -> O, { self.iterated = true; + + let bench_start = InstrumentHooks::current_timestamp(); let time_start = Instant::now(); let start = self.measurement.start(); for _ in 0..self.iters { @@ -100,6 +104,8 @@ impl<'a, M: Measurement> Bencher<'a, M> { } self.value = self.measurement.end(start); self.elapsed_time = time_start.elapsed(); + let bench_end = InstrumentHooks::current_timestamp(); + InstrumentHooks::instance().add_benchmark_timestamps(bench_start, bench_end); } /// Times a `routine` by executing it many times and relying on `routine` to measure its own execution time. @@ -153,9 +159,12 @@ impl<'a, M: Measurement> Bencher<'a, M> { R: FnMut(u64) -> M::Value, { self.iterated = true; + let bench_start = InstrumentHooks::current_timestamp(); let time_start = Instant::now(); self.value = routine(self.iters); self.elapsed_time = time_start.elapsed(); + let bench_end = InstrumentHooks::current_timestamp(); + InstrumentHooks::instance().add_benchmark_timestamps(bench_start, bench_end); } #[doc(hidden)] @@ -282,9 +291,13 @@ impl<'a, M: Measurement> Bencher<'a, M> { for _ in 0..self.iters { let input = black_box(setup()); + let bench_start = InstrumentHooks::current_timestamp(); let start = self.measurement.start(); let output = routine(input); let end = self.measurement.end(start); + let bench_end = InstrumentHooks::current_timestamp(); + InstrumentHooks::instance().add_benchmark_timestamps(bench_start, bench_end); + self.value = self.measurement.add(&self.value, &end); drop(black_box(output)); @@ -298,9 +311,13 @@ impl<'a, M: Measurement> Bencher<'a, M> { let inputs = black_box((0..batch_size).map(|_| setup()).collect::>()); let mut outputs = Vec::with_capacity(batch_size as usize); + let bench_start = InstrumentHooks::current_timestamp(); let start = self.measurement.start(); outputs.extend(inputs.into_iter().map(&mut routine)); let end = self.measurement.end(start); + let bench_end = InstrumentHooks::current_timestamp(); + InstrumentHooks::instance().add_benchmark_timestamps(bench_start, bench_end); + self.value = self.measurement.add(&self.value, &end); black_box(outputs); @@ -386,9 +403,13 @@ impl<'a, M: Measurement> Bencher<'a, M> { for _ in 0..self.iters { let mut input = black_box(setup()); + let bench_start = InstrumentHooks::current_timestamp(); let start = self.measurement.start(); let output = routine(&mut input); let end = self.measurement.end(start); + let bench_end = InstrumentHooks::current_timestamp(); + InstrumentHooks::instance().add_benchmark_timestamps(bench_start, bench_end); + self.value = self.measurement.add(&self.value, &end); drop(black_box(output)); @@ -403,9 +424,13 @@ impl<'a, M: Measurement> Bencher<'a, M> { let mut inputs = black_box((0..batch_size).map(|_| setup()).collect::>()); let mut outputs = Vec::with_capacity(batch_size as usize); + let bench_start = InstrumentHooks::current_timestamp(); let start = self.measurement.start(); outputs.extend(inputs.iter_mut().map(&mut routine)); let end = self.measurement.end(start); + let bench_end = InstrumentHooks::current_timestamp(); + InstrumentHooks::instance().add_benchmark_timestamps(bench_start, bench_end); + self.value = self.measurement.add(&self.value, &end); black_box(outputs); @@ -497,6 +522,7 @@ impl<'a, 'b, A: AsyncExecutor, M: Measurement> AsyncBencher<'a, 'b, A, M> { let AsyncBencher { b, runner } = self; runner.block_on(async { b.iterated = true; + let bench_start = InstrumentHooks::current_timestamp(); let time_start = Instant::now(); let start = b.measurement.start(); for _ in 0..b.iters { @@ -504,6 +530,8 @@ impl<'a, 'b, A: AsyncExecutor, M: Measurement> AsyncBencher<'a, 'b, A, M> { } b.value = b.measurement.end(start); b.elapsed_time = time_start.elapsed(); + let bench_end = InstrumentHooks::current_timestamp(); + InstrumentHooks::instance().add_benchmark_timestamps(bench_start, bench_end); }); } @@ -565,9 +593,12 @@ impl<'a, 'b, A: AsyncExecutor, M: Measurement> AsyncBencher<'a, 'b, A, M> { let AsyncBencher { b, runner } = self; runner.block_on(async { b.iterated = true; + let bench_start = InstrumentHooks::current_timestamp(); let time_start = Instant::now(); b.value = routine(b.iters).await; b.elapsed_time = time_start.elapsed(); + let bench_end = InstrumentHooks::current_timestamp(); + InstrumentHooks::instance().add_benchmark_timestamps(bench_start, bench_end); }) } @@ -713,9 +744,13 @@ impl<'a, 'b, A: AsyncExecutor, M: Measurement> AsyncBencher<'a, 'b, A, M> { for _ in 0..b.iters { let input = black_box(setup()); + let bench_start = InstrumentHooks::current_timestamp(); let start = b.measurement.start(); let output = routine(input).await; let end = b.measurement.end(start); + let bench_end = InstrumentHooks::current_timestamp(); + InstrumentHooks::instance().add_benchmark_timestamps(bench_start, bench_end); + b.value = b.measurement.add(&b.value, &end); drop(black_box(output)); @@ -729,12 +764,16 @@ impl<'a, 'b, A: AsyncExecutor, M: Measurement> AsyncBencher<'a, 'b, A, M> { let inputs = black_box((0..batch_size).map(|_| setup()).collect::>()); let mut outputs = Vec::with_capacity(batch_size as usize); + let bench_start = InstrumentHooks::current_timestamp(); let start = b.measurement.start(); // Can't use .extend here like the sync version does for input in inputs { outputs.push(routine(input).await); } let end = b.measurement.end(start); + let bench_end = InstrumentHooks::current_timestamp(); + InstrumentHooks::instance().add_benchmark_timestamps(bench_start, bench_end); + b.value = b.measurement.add(&b.value, &end); black_box(outputs); @@ -826,9 +865,13 @@ impl<'a, 'b, A: AsyncExecutor, M: Measurement> AsyncBencher<'a, 'b, A, M> { for _ in 0..b.iters { let mut input = black_box(setup()); + let bench_start = InstrumentHooks::current_timestamp(); let start = b.measurement.start(); let output = routine(&mut input).await; let end = b.measurement.end(start); + let bench_end = InstrumentHooks::current_timestamp(); + InstrumentHooks::instance().add_benchmark_timestamps(bench_start, bench_end); + b.value = b.measurement.add(&b.value, &end); drop(black_box(output)); @@ -843,12 +886,16 @@ impl<'a, 'b, A: AsyncExecutor, M: Measurement> AsyncBencher<'a, 'b, A, M> { let inputs = black_box((0..batch_size).map(|_| setup()).collect::>()); let mut outputs = Vec::with_capacity(batch_size as usize); + let bench_start = InstrumentHooks::current_timestamp(); let start = b.measurement.start(); // Can't use .extend here like the sync version does for mut input in inputs { outputs.push(routine(&mut input).await); } let end = b.measurement.end(start); + let bench_end = InstrumentHooks::current_timestamp(); + InstrumentHooks::instance().add_benchmark_timestamps(bench_start, bench_end); + b.value = b.measurement.add(&b.value, &end); black_box(outputs); diff --git a/crates/criterion_compat/criterion_fork/src/routine.rs b/crates/criterion_compat/criterion_fork/src/routine.rs index 09ee41b4..9c5ca65d 100644 --- a/crates/criterion_compat/criterion_fork/src/routine.rs +++ b/crates/criterion_compat/criterion_fork/src/routine.rs @@ -1,3 +1,5 @@ +use codspeed::instrument_hooks::InstrumentHooks; + use crate::benchmark::BenchmarkConfig; use crate::connection::OutgoingMessage; use crate::measurement::Measurement; @@ -192,9 +194,13 @@ pub(crate) trait Routine { } let m_elapsed = { - #[cfg(unix)] - let _guard = codspeed::fifo::BenchGuard::new_with_runner_fifo(); - self.bench(measurement, &m_iters, parameter) + let hooks = InstrumentHooks::instance(); + + let _ = hooks.start_benchmark(); + let value = self.bench(measurement, &m_iters, parameter); + let _ = hooks.stop_benchmark(); + + value }; let m_iters_f: Vec = m_iters.iter().map(|&x| x as f64).collect(); diff --git a/crates/divan_compat/Cargo.toml b/crates/divan_compat/Cargo.toml index dfcb3a00..04b38c4e 100644 --- a/crates/divan_compat/Cargo.toml +++ b/crates/divan_compat/Cargo.toml @@ -29,3 +29,15 @@ harness = false [[bench]] name = "sleep_benches" harness = false + +[[bench]] +name = "drop_example" +harness = false + +[[bench]] +name = "thread_example" +harness = false + +[[bench]] +name = "fib_example" +harness = false diff --git a/crates/divan_compat/benches/drop_example.rs b/crates/divan_compat/benches/drop_example.rs new file mode 100644 index 00000000..5543796e --- /dev/null +++ b/crates/divan_compat/benches/drop_example.rs @@ -0,0 +1,45 @@ +use codspeed_divan_compat::Bencher; + +struct LargeInput { + data: Vec, +} + +impl Drop for LargeInput { + #[inline(never)] + fn drop(&mut self) { + // Simulate a large drop by performing some computation + let sum: u8 = self.data.iter().copied().sum(); + std::hint::black_box(sum); // Prevent optimization + } +} + +impl LargeInput { + fn new() -> Self { + Self { + data: vec![42; 1024 * 1024 /* 1MiB */], + } + } + + fn process(&self) -> u64 { + // Simulate some work on the data + std::thread::sleep(std::time::Duration::from_millis(50)); + self.data.iter().map(|&x| x as u64).sum() + } +} + +#[codspeed_divan_compat::bench] +fn bench_large_input(bencher: Bencher) { + bencher + .with_inputs(LargeInput::new) + .bench_values(|input| input.process()); +} + +#[codspeed_divan_compat::bench] +fn bench_large_input_local(bencher: Bencher) { + let input = LargeInput::new(); + bencher.bench_local(|| input.process()); +} + +fn main() { + codspeed_divan_compat::main(); +} diff --git a/crates/divan_compat/benches/fib_example.rs b/crates/divan_compat/benches/fib_example.rs new file mode 100644 index 00000000..1440ba83 --- /dev/null +++ b/crates/divan_compat/benches/fib_example.rs @@ -0,0 +1,26 @@ +fn fib(n: u32) -> u32 { + match n { + 0 => 0, + 1 => 1, + n => fib(n - 1) + fib(n - 2), + } +} + +#[codspeed_divan_compat::bench] +fn fib_30() -> u32 { + codspeed_divan_compat::black_box(fib(30)) +} + +#[codspeed_divan_compat::bench] +fn fib_20() -> u32 { + codspeed_divan_compat::black_box(fib(20)) +} + +#[codspeed_divan_compat::bench] +fn fib_10() -> u32 { + codspeed_divan_compat::black_box(fib(10)) +} + +fn main() { + codspeed_divan_compat::main(); +} diff --git a/crates/divan_compat/benches/sleep_benches.rs b/crates/divan_compat/benches/sleep_benches.rs index 7a8931ec..963545b8 100644 --- a/crates/divan_compat/benches/sleep_benches.rs +++ b/crates/divan_compat/benches/sleep_benches.rs @@ -1,25 +1,25 @@ -#[divan::bench] +#[codspeed_divan_compat::bench] fn sleep_1ms() { std::thread::sleep(std::time::Duration::from_millis(1)); } -#[divan::bench] +#[codspeed_divan_compat::bench] fn sleep_10ms() { std::thread::sleep(std::time::Duration::from_millis(10)); } -#[divan::bench] +#[codspeed_divan_compat::bench] fn sleep_50ms() { std::thread::sleep(std::time::Duration::from_millis(50)); } -#[divan::bench] +#[codspeed_divan_compat::bench] fn sleep_100ms() { std::thread::sleep(std::time::Duration::from_millis(100)); } // Tests COD-1044, do not modify the sample size or count! -#[divan::bench(sample_size = 3, sample_count = 6)] +#[codspeed_divan_compat::bench(sample_size = 3, sample_count = 6)] fn sleep_100ms_with_custom_sample() { std::thread::sleep(std::time::Duration::from_millis(100)); } diff --git a/crates/divan_compat/benches/thread_example.rs b/crates/divan_compat/benches/thread_example.rs new file mode 100644 index 00000000..1695033b --- /dev/null +++ b/crates/divan_compat/benches/thread_example.rs @@ -0,0 +1,33 @@ +fn fibo(n: u64) -> u64 { + if n <= 1 { + n + } else { + fibo(n - 1) + fibo(n - 2) + } +} + +#[codspeed_divan_compat::bench(args = [30, 31, 32])] +fn fib_in_thread(n: usize) { + let handle = std::thread::spawn(move || codspeed_divan_compat::black_box(fibo(n as u64))); + handle.join().unwrap(); +} + +#[codspeed_divan_compat::bench(args = [30, 31, 32])] +fn fib_in_thread_bench(bencher: codspeed_divan_compat::Bencher, n: usize) { + bencher.bench(|| { + let handle = std::thread::spawn(move || codspeed_divan_compat::black_box(fibo(n as u64))); + handle.join().unwrap() + }) +} + +#[codspeed_divan_compat::bench(args = [30, 31, 32])] +fn fib_in_thread_bench_local(bencher: codspeed_divan_compat::Bencher, n: usize) { + bencher.bench_local(|| { + let handle = std::thread::spawn(move || codspeed_divan_compat::black_box(fibo(n as u64))); + handle.join().unwrap() + }) +} + +fn main() { + codspeed_divan_compat::main(); +} diff --git a/crates/divan_compat/divan_fork/src/bench/mod.rs b/crates/divan_compat/divan_fork/src/bench/mod.rs index e5e6de6c..560fe5f8 100644 --- a/crates/divan_compat/divan_fork/src/bench/mod.rs +++ b/crates/divan_compat/divan_fork/src/bench/mod.rs @@ -29,6 +29,7 @@ mod args; mod defer; mod options; +use ::codspeed::instrument_hooks::InstrumentHooks; use defer::{DeferSlot, DeferStore}; pub use self::{ @@ -657,8 +658,7 @@ impl<'a> BenchContext<'a> { let bench_overheads = timer.bench_overheads(); - #[cfg(unix)] - let _guard = codspeed::fifo::BenchGuard::new_with_runner_fifo(); + let _ = InstrumentHooks::instance().start_benchmark(); while { // Conditions for when sampling is over: if elapsed_picos >= max_picos { @@ -812,8 +812,7 @@ impl<'a> BenchContext<'a> { elapsed_picos = elapsed_picos.saturating_add(progress_picos); } } - #[cfg(unix)] - core::mem::drop(_guard); + let _ = InstrumentHooks::instance().stop_benchmark(); // Reset flag for ignoring allocations. crate::alloc::IGNORE_ALLOC.set(false); @@ -899,6 +898,7 @@ impl<'a> BenchContext<'a> { let sample_start: UntaggedTimestamp; let sample_end: UntaggedTimestamp; + let instrument_hooks = InstrumentHooks::instance(); if size_of::() == 0 && (size_of::() == 0 || !mem::needs_drop::()) { // Use a range instead of `defer_store` to make the benchmarking // loop cheaper. @@ -914,6 +914,8 @@ impl<'a> BenchContext<'a> { } sync_threads(true); + + let start_time = InstrumentHooks::current_timestamp(); sample_start = UntaggedTimestamp::start(timer_kind); // Sample loop: @@ -926,6 +928,9 @@ impl<'a> BenchContext<'a> { } sample_end = UntaggedTimestamp::end(timer_kind); + let end_time = InstrumentHooks::current_timestamp(); + instrument_hooks.add_benchmark_timestamps(start_time, end_time); + sync_threads(false); save_alloc_info(); @@ -967,6 +972,7 @@ impl<'a> BenchContext<'a> { let defer_slots_iter = defer_slots_slice.iter(); sync_threads(true); + let start_time = InstrumentHooks::current_timestamp(); sample_start = UntaggedTimestamp::start(timer_kind); // Sample loop: @@ -981,6 +987,8 @@ impl<'a> BenchContext<'a> { } sample_end = UntaggedTimestamp::end(timer_kind); + let end_time = InstrumentHooks::current_timestamp(); + instrument_hooks.add_benchmark_timestamps(start_time, end_time); sync_threads(false); save_alloc_info(); @@ -1020,6 +1028,7 @@ impl<'a> BenchContext<'a> { let defer_inputs_iter = defer_inputs_slice.iter(); sync_threads(true); + let start_time = InstrumentHooks::current_timestamp(); sample_start = UntaggedTimestamp::start(timer_kind); // Sample loop: @@ -1030,6 +1039,8 @@ impl<'a> BenchContext<'a> { } sample_end = UntaggedTimestamp::end(timer_kind); + let end_time = InstrumentHooks::current_timestamp(); + instrument_hooks.add_benchmark_timestamps(start_time, end_time); sync_threads(false); save_alloc_info(); diff --git a/crates/divan_compat/divan_fork/src/divan.rs b/crates/divan_compat/divan_fork/src/divan.rs index 8ee1e013..3a78d6c1 100644 --- a/crates/divan_compat/divan_fork/src/divan.rs +++ b/crates/divan_compat/divan_fork/src/divan.rs @@ -430,19 +430,13 @@ mod codspeed { bench_context.samples.time_samples.iter().map(|s| s.duration.picos / 1_000).collect(); let max_time_ns = bench_context.options.max_time.map(|t| t.as_nanos()); - #[cfg(unix)] + if let Err(error) = + ::codspeed::instrument_hooks::InstrumentHooks::instance().set_executed_benchmark(&uri) { - if let Err(error) = - ::codspeed::fifo::send_cmd(codspeed::fifo::Command::CurrentBenchmark { - pid: std::process::id(), - uri: uri.clone(), - }) + if ::codspeed::utils::running_with_codspeed_runner() + && ::codspeed::utils::is_perf_enabled() { - if codspeed::utils::running_with_codspeed_runner() - && codspeed::utils::is_perf_enabled() - { - eprintln!("Failed to send benchmark URI to runner: {error:?}"); - } + eprintln!("Failed to set executed benchmark URI: {error:?}"); } }