scuffle_pprof/
lib.rs

1//! A crate designed to provide a more ergonomic interface to the `pprof` crate.
2//!
3//! ## Example
4//!
5//! ```rust,no_run
6//! // Create a new CPU profiler with a sampling frequency of 1000 Hz and an empty ignore list.
7//! let cpu = scuffle_pprof::Cpu::new::<String>(1000, &[]);
8//!
9//! // Capture a pprof profile for 10 seconds.
10//! // This call is blocking. It is recommended to run it in a separate thread.
11//! let capture = cpu.capture(std::time::Duration::from_secs(10)).unwrap();
12//!
13//! // Write the profile to a file.
14//! std::fs::write("capture.pprof", capture).unwrap();
15//! ```
16//!
17//! ## Analyzing the profile
18//!
19//! The resulting profile can be analyzed using the [`pprof`](https://github.com/google/pprof) tool.
20//!
21//! For example, to generate a flamegraph:
22//! ```sh
23//! pprof -svg capture.pprof
24//! ```
25//!
26//! ## Status
27//!
28//! This crate is currently under development and is not yet stable.
29//!
30//! Unit tests are not yet fully implemented. Use at your own risk.
31//!
32//! ## License
33//!
34//! This project is licensed under the [MIT](./LICENSE.MIT) or [Apache-2.0](./LICENSE.Apache-2.0) license.
35//! You can choose between one of them if you use this work.
36//!
37//! `SPDX-License-Identifier: MIT OR Apache-2.0`
38#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))]
39
40mod cpu;
41
42pub use cpu::Cpu;
43
44/// An error that can occur while profiling.
45#[derive(Debug, thiserror::Error)]
46pub enum PprofError {
47    #[error(transparent)]
48    Io(#[from] std::io::Error),
49    #[error(transparent)]
50    Pprof(#[from] pprof::Error),
51}
52
53#[cfg(test)]
54#[cfg_attr(all(coverage_nightly, test), coverage(off))]
55mod tests {
56    use std::io::Read;
57    use std::time::SystemTime;
58
59    use flate2::read::GzDecoder;
60    use pprof::protos::Message;
61
62    use crate::Cpu;
63
64    #[test]
65    fn empty_profile() {
66        let before_nanos = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos() as i64;
67
68        let cpu = Cpu::new::<String>(1000, &[]);
69        let profile = cpu.capture(std::time::Duration::from_secs(1)).unwrap();
70
71        // Decode the profile
72        let mut reader = GzDecoder::new(profile.as_slice());
73        let mut buf = Vec::new();
74        reader.read_to_end(&mut buf).unwrap();
75        let profile = pprof::protos::Profile::decode(buf.as_slice()).unwrap();
76
77        assert!(profile.duration_nanos > 1_000_000_000);
78        assert!(profile.time_nanos > before_nanos);
79        let now_nanos = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos() as i64;
80        assert!(profile.time_nanos < now_nanos);
81
82        assert_eq!(profile.string_table[profile.drop_frames as usize], "");
83        assert_eq!(profile.string_table[profile.keep_frames as usize], "");
84
85        let Some(period_type) = profile.period_type else {
86            panic!("missing period type");
87        };
88        assert_eq!(profile.string_table[period_type.ty as usize], "cpu");
89        assert_eq!(profile.string_table[period_type.unit as usize], "nanoseconds");
90
91        assert_eq!(profile.period, 1_000_000);
92    }
93}