Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,18 @@ jobs:
cache-target: release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- run: cargo test -p codspeed
if: runner.os == 'Linux'

# We don't support Windows/MacOS for now, due to libclang not found on some runners:
- run: LIBCLANG_PATH= cargo test -p codspeed
if: runner.os == 'macOS'
- run: |
$env:LIBCLANG_PATH=""
cargo test -p codspeed
if: runner.os == 'Windows'
shell: powershell

msrv-check:
runs-on: ubuntu-latest
Expand Down
4 changes: 1 addition & 3 deletions crates/codspeed/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,12 @@ anyhow = { workspace = true }
colored = "2.0.0"
glob = "0.3.2"
libc = "^0.2"
nix = { version = "0.30.1", features = ["time"] }
serde = { workspace = true }
serde_json = { workspace = true }
statrs = { version = "0.18.0", default-features = false }
uuid = { version = "1.12.1", features = ["v4"] }

[target.'cfg(target_os = "linux")'.dependencies]
nix = { version = "0.30.1", features = ["time"] }

[[bench]]
name = "native"
harness = false
Expand Down
5 changes: 5 additions & 0 deletions crates/codspeed/build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use std::{env, path::PathBuf};

fn main() {
if cfg!(not(target_os = "linux")) {
// The instrument-hooks library is only supported on Linux.
return;
}

// Compile the C library
cc::Build::new()
.file("instrument-hooks/dist/core.c")
Expand Down
248 changes: 148 additions & 100 deletions crates/codspeed/src/instrument_hooks/mod.rs
Original file line number Diff line number Diff line change
@@ -1,132 +1,180 @@
use std::ffi::CString;
use std::sync::OnceLock;

#[cfg(target_os = "linux")]
mod ffi;

pub struct InstrumentHooks(*mut ffi::InstrumentHooks);
#[cfg(target_os = "linux")]
mod linux_impl {
use nix::sys::time::TimeValLike;

unsafe impl Send for InstrumentHooks {}
unsafe impl Sync for InstrumentHooks {}
use super::ffi;
use std::ffi::CString;
use std::sync::OnceLock;

impl InstrumentHooks {
#[inline(always)]
pub fn new() -> Option<Self> {
let ptr = unsafe { ffi::instrument_hooks_init() };
if ptr.is_null() {
None
} else {
Some(InstrumentHooks(ptr))
}
}
pub struct InstrumentHooks(*mut ffi::InstrumentHooks);

/// Returns a singleton instance of `InstrumentHooks`.
#[inline(always)]
pub fn instance() -> &'static Self {
static INSTANCE: OnceLock<InstrumentHooks> = 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
})
}
unsafe impl Send for InstrumentHooks {}
unsafe impl Sync for InstrumentHooks {}

#[inline(always)]
pub fn is_instrumented(&self) -> bool {
unsafe { ffi::instrument_hooks_is_instrumented(self.0) }
}
impl InstrumentHooks {
#[inline(always)]
pub fn new() -> Option<Self> {
let ptr = unsafe { ffi::instrument_hooks_init() };
if ptr.is_null() {
None
} else {
Some(InstrumentHooks(ptr))
}
}

#[inline(always)]
pub fn start_benchmark(&self) -> Result<(), u8> {
let result = unsafe { ffi::instrument_hooks_start_benchmark(self.0) };
if result == 0 {
Ok(())
} else {
Err(result)
/// Returns a singleton instance of `InstrumentHooks`.
#[inline(always)]
pub fn instance() -> &'static Self {
static INSTANCE: OnceLock<InstrumentHooks> = 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 stop_benchmark(&self) -> Result<(), u8> {
let result = unsafe { ffi::instrument_hooks_stop_benchmark(self.0) };
if result == 0 {
Ok(())
} else {
Err(result)
#[inline(always)]
pub fn is_instrumented(&self) -> bool {
unsafe { ffi::instrument_hooks_is_instrumented(self.0) }
}
}

#[inline(always)]
pub fn set_executed_benchmark(&self, uri: &str) -> Result<(), u8> {
let pid = std::process::id() as i32;
let c_uri = CString::new(uri).map_err(|_| 1u8)?;
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 start_benchmark(&self) -> Result<(), u8> {
let result = unsafe { ffi::instrument_hooks_start_benchmark(self.0) };
if result == 0 {
Ok(())
} else {
Err(result)
}
}
}

#[inline(always)]
pub fn set_integration(&self, name: &str, version: &str) -> Result<(), u8> {
let c_name = CString::new(name).map_err(|_| 1u8)?;
let c_version = CString::new(version).map_err(|_| 1u8)?;
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 stop_benchmark(&self) -> Result<(), u8> {
let result = unsafe { ffi::instrument_hooks_stop_benchmark(self.0) };
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 set_executed_benchmark(&self, uri: &str) -> Result<(), u8> {
let pid = std::process::id() as i32;
let c_uri = CString::new(uri).map_err(|_| 1u8)?;
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 current_timestamp() -> u64 {
#[cfg(not(target_os = "linux"))]
{
unsafe { ffi::instrument_hooks_current_timestamp() }
#[inline(always)]
pub fn set_integration(&self, name: &str, version: &str) -> Result<(), u8> {
let c_name = CString::new(name).map_err(|_| 1u8)?;
let c_version = CString::new(version).map_err(|_| 1u8)?;
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)
}
}

// Custom implementation to avoid the extra FFI call
#[cfg(target_os = "linux")]
{
use nix::sys::time::TimeValLike;
#[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) };
}
}
}
}

impl Drop for InstrumentHooks {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { ffi::instrument_hooks_deinit(self.0) };
#[cfg(not(target_os = "linux"))]
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<(), u8> {
Ok(())
}

pub fn stop_benchmark(&self) -> Result<(), u8> {
Ok(())
}

pub fn set_executed_benchmark(&self, _uri: &str) -> Result<(), u8> {
Ok(())
}

pub fn set_integration(&self, _name: &str, _version: &str) -> Result<(), u8> {
Ok(())
}

pub fn add_benchmark_timestamps(&self, _start: u64, _end: u64) {}

pub fn current_timestamp() -> u64 {
0
}
}
}

#[cfg(target_os = "linux")]
pub use linux_impl::InstrumentHooks;

#[cfg(not(target_os = "linux"))]
pub use other_impl::InstrumentHooks;

#[cfg(test)]
mod tests {
use super::InstrumentHooks;
Expand Down
Loading