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