blueprint_benchmarking/
lib.rs

1pub use tokio;
2
3/// The runtime trait that all runtimes must implement.
4pub trait Runtime {
5    /// Runs the given future to completion on the runtime.
6    fn block_on<F>(&self, future: F) -> F::Output
7    where
8        F: blueprint_std::future::Future;
9}
10
11/// The [`tokio`](https://crates.io/crates/tokio) runtime.
12///
13/// This will execute the benchmark using the `tokio` runtime.
14#[derive(Debug, Clone, Copy)]
15pub struct TokioRuntime;
16
17impl Runtime for TokioRuntime {
18    fn block_on<F>(&self, future: F) -> F::Output
19    where
20        F: blueprint_std::future::Future,
21    {
22        let rt = tokio::runtime::Handle::current();
23        rt.block_on(future)
24    }
25}
26
27/// A benchmarking harness.
28#[derive(Debug)]
29#[allow(dead_code)]
30pub struct Bencher<R> {
31    /// The runtime to use for running benchmarks.
32    runtime: R,
33    /// The time at which the benchmark started.
34    started_at: blueprint_std::time::Instant,
35    /// The max number of cores for this benchmark.
36    cores: usize,
37}
38
39/// The results of a benchmark.
40///
41/// This implements [`Display`] to provide a human-readable summary of the benchmark.
42///
43/// [`Display`]: core::fmt::Display
44#[derive(Debug, Clone)]
45pub struct BenchmarkSummary {
46    /// The name of the benchmark.
47    pub name: String,
48    /// The job identifier.
49    pub job_id: u8,
50    /// The duration of the benchmark.
51    pub elapsed: blueprint_std::time::Duration,
52    /// The number of cores the benchmark was run with.
53    pub cores: usize,
54    /// The amount of memory used by the benchmark (in bytes).
55    pub ram_usage: u64,
56}
57
58impl<R: Runtime> Bencher<R> {
59    /// Create a new benchmark harness.
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// use blueprint_benchmarking::{Bencher, TokioRuntime};
65    ///
66    /// const THREADS: usize = 4;
67    ///
68    /// let bencher = Bencher::new(THREADS, TokioRuntime);
69    /// ```
70    pub fn new(threads: usize, runtime: R) -> Self {
71        Self {
72            runtime,
73            started_at: blueprint_std::time::Instant::now(),
74            cores: threads,
75        }
76    }
77
78    /// Runs the given future on the [`Runtime`].
79    ///
80    /// # Examples
81    ///
82    /// ```no_run
83    /// use blueprint_benchmarking::{Bencher, TokioRuntime};
84    ///
85    /// const THREADS: usize = 4;
86    ///
87    /// let bencher = Bencher::new(THREADS, TokioRuntime);
88    /// bencher.block_on(async {
89    ///     // Do some work...
90    /// });
91    /// ```
92    pub fn block_on<F>(&self, future: F) -> F::Output
93    where
94        F: blueprint_std::future::Future,
95    {
96        self.runtime.block_on(future)
97    }
98
99    /// Ends the benchmark and returns a summary.
100    ///
101    /// # Panics
102    ///
103    /// This will panic in the event it cannot determine the process ID.
104    ///
105    /// # Examples
106    ///
107    /// ```no_run
108    /// use blueprint_benchmarking::{Bencher, TokioRuntime};
109    /// const THREADS: usize = 4;
110    ///
111    /// let bencher = Bencher::new(THREADS, TokioRuntime);
112    /// bencher.block_on(async {
113    ///     // Do some work...
114    /// });
115    ///
116    /// let summary = bencher.stop("my_benchmark", 0);
117    /// println!("{}", summary);
118    /// ```
119    #[cfg(feature = "std")] // TODO: Benchmark execution time for WASM?
120    #[allow(clippy::needless_pass_by_value)]
121    pub fn stop<N: ToString>(&self, name: N, job_id: u8) -> BenchmarkSummary {
122        let pid = sysinfo::get_current_pid().expect("Failed to get current process ID");
123        let s = sysinfo::System::new_all();
124        let process = s
125            .process(pid)
126            .expect("Failed to get current process from the system");
127        let ram_usage = process.memory();
128        BenchmarkSummary {
129            name: name.to_string(),
130            job_id,
131            elapsed: self.started_at.elapsed(),
132            cores: self.cores,
133            ram_usage,
134        }
135    }
136}
137
138impl blueprint_std::fmt::Display for BenchmarkSummary {
139    #[allow(clippy::cast_precision_loss)]
140    fn fmt(&self, f: &mut blueprint_std::fmt::Formatter<'_>) -> blueprint_std::fmt::Result {
141        const KB: f32 = 1024.00;
142        const MB: f32 = 1024.00 * KB;
143        const GB: f32 = 1024.00 * MB;
144        let ram_usage = self.ram_usage as f32;
145        let (ram_usage, unit) = if ram_usage < KB {
146            (ram_usage, "B")
147        } else if ram_usage < MB {
148            (ram_usage / KB, "KB")
149        } else if ram_usage < GB {
150            (ram_usage / MB, "MB")
151        } else {
152            (ram_usage / GB, "GB")
153        };
154
155        write!(
156            f,
157            "Benchmark: {}\nJob ID: {}\nElapsed: {:?}\nvCPU: {}\nRAM Usage: {ram_usage:.2} {unit}\n",
158            self.name, self.job_id, self.elapsed, self.cores,
159        )
160    }
161}