Skip to main content

codspeed/instrument_hooks/
mod.rs

1#[cfg(use_instrument_hooks)]
2mod ffi;
3
4#[cfg(use_instrument_hooks)]
5mod linux_impl {
6
7    use super::ffi;
8    use std::ffi::CString;
9    use std::sync::OnceLock;
10
11    #[derive(PartialEq)]
12    pub struct InstrumentHooks(*mut ffi::InstrumentHooks);
13
14    unsafe impl Send for InstrumentHooks {}
15    unsafe impl Sync for InstrumentHooks {}
16
17    impl InstrumentHooks {
18        #[inline(always)]
19        pub fn new() -> Option<Self> {
20            let ptr = unsafe { ffi::instrument_hooks_init() };
21            if ptr.is_null() {
22                None
23            } else {
24                Some(InstrumentHooks(ptr))
25            }
26        }
27
28        /// Returns a singleton instance of `InstrumentHooks`.
29        #[inline(always)]
30        pub fn instance() -> &'static Self {
31            static INSTANCE: OnceLock<InstrumentHooks> = OnceLock::new();
32            INSTANCE.get_or_init(|| {
33                let instance =
34                    InstrumentHooks::new().expect("Failed to initialize InstrumentHooks");
35                instance
36                    .set_integration("codspeed-rust", env!("CARGO_PKG_VERSION"))
37                    .expect("Failed to set integration");
38                instance
39            })
40        }
41
42        #[inline(always)]
43        pub fn is_instrumented(&self) -> bool {
44            unsafe { ffi::instrument_hooks_is_instrumented(self.0) }
45        }
46
47        #[inline(always)]
48        pub fn start_benchmark(&self) -> Result<(), u8> {
49            let result = unsafe { ffi::instrument_hooks_start_benchmark(self.0) };
50            if result == 0 {
51                Ok(())
52            } else {
53                Err(result)
54            }
55        }
56
57        #[inline(always)]
58        pub fn stop_benchmark(&self) -> Result<(), u8> {
59            let result = unsafe { ffi::instrument_hooks_stop_benchmark(self.0) };
60            if result == 0 {
61                Ok(())
62            } else {
63                Err(result)
64            }
65        }
66
67        #[inline(always)]
68        pub fn set_executed_benchmark(&self, uri: &str) -> Result<(), u8> {
69            let pid = std::process::id() as i32;
70            let c_uri = CString::new(uri).map_err(|_| 1u8)?;
71            let result = unsafe {
72                ffi::instrument_hooks_set_executed_benchmark(self.0, pid, c_uri.as_ptr())
73            };
74            if result == 0 {
75                Ok(())
76            } else {
77                Err(result)
78            }
79        }
80
81        #[inline(always)]
82        pub fn set_integration(&self, name: &str, version: &str) -> Result<(), u8> {
83            let c_name = CString::new(name).map_err(|_| 1u8)?;
84            let c_version = CString::new(version).map_err(|_| 1u8)?;
85            let result = unsafe {
86                ffi::instrument_hooks_set_integration(self.0, c_name.as_ptr(), c_version.as_ptr())
87            };
88            if result == 0 {
89                Ok(())
90            } else {
91                Err(result)
92            }
93        }
94
95        #[inline(always)]
96        pub fn add_benchmark_timestamps(&self, start: u64, end: u64) {
97            let pid = std::process::id();
98
99            unsafe {
100                ffi::instrument_hooks_add_marker(
101                    self.0,
102                    pid,
103                    ffi::MARKER_TYPE_BENCHMARK_START as u8,
104                    start,
105                )
106            };
107            unsafe {
108                ffi::instrument_hooks_add_marker(
109                    self.0,
110                    pid,
111                    ffi::MARKER_TYPE_BENCHMARK_END as u8,
112                    end,
113                )
114            };
115        }
116
117        #[inline(always)]
118        pub fn current_timestamp() -> u64 {
119            #[cfg(target_os = "linux")]
120            {
121                use nix::sys::time::TimeValLike;
122
123                nix::time::clock_gettime(nix::time::ClockId::CLOCK_MONOTONIC)
124                    .expect("Failed to get current time")
125                    .num_nanoseconds() as u64
126            }
127
128            #[cfg(not(target_os = "linux"))]
129            unsafe {
130                ffi::instrument_hooks_current_timestamp()
131            }
132        }
133
134        pub fn disable_callgrind_markers() {
135            unsafe {
136                ffi::instrument_hooks_set_feature(
137                    ffi::instrument_hooks_feature_t_FEATURE_DISABLE_CALLGRIND_MARKERS,
138                    true,
139                )
140            };
141        }
142    }
143
144    impl Drop for InstrumentHooks {
145        fn drop(&mut self) {
146            if !self.0.is_null() {
147                unsafe { ffi::instrument_hooks_deinit(self.0) };
148            }
149        }
150    }
151}
152
153#[cfg(not(use_instrument_hooks))]
154mod other_impl {
155    #[derive(PartialEq)]
156    pub struct InstrumentHooks;
157
158    impl InstrumentHooks {
159        pub fn instance() -> &'static Self {
160            static INSTANCE: InstrumentHooks = InstrumentHooks;
161            &INSTANCE
162        }
163
164        pub fn is_instrumented(&self) -> bool {
165            false
166        }
167
168        pub fn start_benchmark(&self) -> Result<(), u8> {
169            Ok(())
170        }
171
172        pub fn stop_benchmark(&self) -> Result<(), u8> {
173            Ok(())
174        }
175
176        pub fn set_executed_benchmark(&self, _uri: &str) -> Result<(), u8> {
177            Ok(())
178        }
179
180        pub fn set_integration(&self, _name: &str, _version: &str) -> Result<(), u8> {
181            Ok(())
182        }
183
184        pub fn add_benchmark_timestamps(&self, _start: u64, _end: u64) {}
185
186        pub fn current_timestamp() -> u64 {
187            0
188        }
189
190        pub fn disable_callgrind_markers() {}
191    }
192}
193
194#[cfg(use_instrument_hooks)]
195pub use linux_impl::InstrumentHooks;
196
197#[cfg(not(use_instrument_hooks))]
198pub use other_impl::InstrumentHooks;
199
200#[cfg(test)]
201mod tests {
202    use super::InstrumentHooks;
203
204    #[test]
205    fn test_instrument_hooks() {
206        let hooks = InstrumentHooks::instance();
207        assert!(!hooks.is_instrumented() || hooks.start_benchmark().is_ok());
208        assert!(hooks.set_executed_benchmark("test_uri").is_ok());
209        assert!(hooks.set_integration("test_integration", "1.0.0").is_ok());
210        let start = InstrumentHooks::current_timestamp();
211        let end = start + 1_000_000; // Simulate 1ms later
212        hooks.add_benchmark_timestamps(start, end);
213        assert!(!hooks.is_instrumented() || hooks.stop_benchmark().is_ok());
214    }
215}