Skip to content

Commit

Permalink
Auto merge of #262 - lennart/CLOUD-11, r=TroyKomodo
Browse files Browse the repository at this point in the history
pprof: add test and docs
- Adds a simple test
- Improves docs

CLOUD-11
<!--
Thank you for your Pull Request. Please provide a short description of your changes above.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/ScuffleCloud/.github/blob/main/CONTRIBUTING.md
-->

Requested-by: TroyKomodo <[email protected]>
Reviewed-by: TroyKomodo <[email protected]>
  • Loading branch information
scuffle-brawl[bot] authored Jan 23, 2025
2 parents c06e35d + 3c9671c commit b5bcf37
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 3 deletions.
3 changes: 3 additions & 0 deletions crates/pprof/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ license = "MIT OR Apache-2.0"
description = "Helper crate for adding pprof support to your application."
keywords = ["pprof", "cpu", "profiling", "flamegraphs"]

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] }

[dependencies]
flate2 = "1.0"
pprof = { version = "0.14", features = ["prost-codec"] }
Expand Down
26 changes: 25 additions & 1 deletion crates/pprof/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,31 @@

---

A crate designed to provide a more ergonomic interface to the pprof crate.
A crate designed to provide a more ergonomic interface to the `pprof` crate.

## Example

```rust,no_run
// Create a new CPU profiler with a sampling frequency of 1000 Hz and an empty ignore list.
let cpu = scuffle_pprof::Cpu::new::<String>(1000, &[]);
// Capture a pprof profile for 10 seconds.
// This call is blocking. It is recommended to run it in a separate thread.
let capture = cpu.capture(std::time::Duration::from_secs(10)).unwrap();
// Write the profile to a file.
std::fs::write("capture.pprof", capture).unwrap();
```

## Analyzing the profile

The resulting profile can be analyzed using the [`pprof`](https://github.com/google/pprof) tool.

For example, to generate a flamegraph:

```sh
pprof -svg capture.pprof
```

## Status

Expand Down
8 changes: 8 additions & 0 deletions crates/pprof/src/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ use pprof::protos::Message;
use crate::PprofError;

/// A CPU profiler.
///
/// Call [`Cpu::capture`] to capture a pprof profile for the given duration.
pub struct Cpu(pprof::ProfilerGuardBuilder);

impl Cpu {
/// Create a new CPU profiler.
///
/// - `frequency` is the sampling frequency in Hz.
/// - `blocklist` is a list of functions to exclude from the profile.
pub fn new<S: AsRef<str>>(frequency: i32, blocklist: &[S]) -> Self {
Expand All @@ -22,10 +25,15 @@ impl Cpu {
}

/// Capture a pprof profile for the given duration.
///
/// The profile is compressed using gzip.
/// The profile can be analyzed using the `pprof` tool.
///
/// <div class="warning">
/// Warning: This method is blocking and may take a long time to complete.
///
/// It is recommended to run it in a separate thread.
/// </div>
pub fn capture(&self, duration: std::time::Duration) -> Result<Vec<u8>, PprofError> {
let profiler = self.0.clone().build()?;

Expand Down
89 changes: 88 additions & 1 deletion crates/pprof/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,47 @@
//! A crate designed to provide a more ergonomic interface to the `pprof` crate.
//!
//! ## Example
//!
//! ```rust,no_run
//! // Create a new CPU profiler with a sampling frequency of 1000 Hz and an empty ignore list.
//! let cpu = scuffle_pprof::Cpu::new::<String>(1000, &[]);
//!
//! // Capture a pprof profile for 10 seconds.
//! // This call is blocking. It is recommended to run it in a separate thread.
//! let capture = cpu.capture(std::time::Duration::from_secs(10)).unwrap();
//!
//! // Write the profile to a file.
//! std::fs::write("capture.pprof", capture).unwrap();
//! ```
//!
//! ## Analyzing the profile
//!
//! The resulting profile can be analyzed using the [`pprof`](https://github.com/google/pprof) tool.
//!
//! For example, to generate a flamegraph:
//! ```sh
//! pprof -svg capture.pprof
//! ```
//!
//! ## Status
//!
//! This crate is currently under development and is not yet stable.
//!
//! Unit tests are not yet fully implemented. Use at your own risk.
//!
//! ## License
//!
//! This project is licensed under the [MIT](./LICENSE.MIT) or [Apache-2.0](./LICENSE.Apache-2.0) license.
//! You can choose between one of them if you use this work.
//!
//! `SPDX-License-Identifier: MIT OR Apache-2.0`
#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))]

mod cpu;

pub use cpu::Cpu;

/// An error that can occur while profiling.
#[derive(Debug, thiserror::Error)]
pub enum PprofError {
#[error(transparent)]
Expand All @@ -8,4 +50,49 @@ pub enum PprofError {
Pprof(#[from] pprof::Error),
}

pub use cpu::Cpu;
#[cfg(test)]
#[cfg_attr(all(coverage_nightly, test), coverage(off))]
mod tests {
use std::io::Read;
use std::time::SystemTime;

use flate2::read::GzDecoder;
use pprof::protos::Message;

use crate::Cpu;

#[test]
fn empty_profile() {
let before_nanos = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos() as i64;

let cpu = Cpu::new::<String>(1000, &[]);
let profile = cpu.capture(std::time::Duration::from_secs(1)).unwrap();

// Decode the profile
let mut reader = GzDecoder::new(profile.as_slice());
let mut buf = Vec::new();
reader.read_to_end(&mut buf).unwrap();
let profile = pprof::protos::Profile::decode(buf.as_slice()).unwrap();

assert!(profile.duration_nanos > 1_000_000_000);
assert!(profile.time_nanos > before_nanos);
let now_nanos = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos() as i64;
assert!(profile.time_nanos < now_nanos);

assert_eq!(profile.string_table[profile.drop_frames as usize], "");
assert_eq!(profile.string_table[profile.keep_frames as usize], "");

let Some(period_type) = profile.period_type else {
panic!("missing period type");
};
assert_eq!(profile.string_table[period_type.ty as usize], "cpu");
assert_eq!(profile.string_table[period_type.unit as usize], "nanoseconds");

assert_eq!(profile.period, 1_000_000);

assert!(profile.sample.is_empty());
assert!(profile.mapping.is_empty());
assert!(profile.location.is_empty());
assert!(profile.function.is_empty());
}
}
2 changes: 1 addition & 1 deletion crates/signal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,8 @@ impl std::future::Future for SignalHandler {
}
}

#[cfg_attr(all(coverage_nightly, test), coverage(off))]
#[cfg(test)]
#[cfg_attr(all(coverage_nightly, test), coverage(off))]
mod tests {
use std::time::Duration;

Expand Down

0 comments on commit b5bcf37

Please sign in to comment.