scuffle_pprof/
lib.rs

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