1use colored::*;
5use indexmap::{IndexMap, IndexSet};
6use std::collections::HashMap;
7use std::fmt;
8use std::sync::Arc;
9use std::sync::RwLock;
10use std::time::{Duration, Instant};
11
12#[derive(Clone, Debug)]
13struct BenchResult {
14 list: Vec<Duration>,
15}
16impl BenchResult {
17 fn new() -> Self {
18 Self { list: vec![] }
19 }
20 fn n(&self) -> usize {
21 self.list.len()
22 }
23 fn add_result(&mut self, du: Duration) {
24 self.list.push(du);
25 }
26 fn average(&self) -> Duration {
27 let n = self.list.len();
28 let mut sum = Duration::from_secs(0);
29 for &du in &self.list {
30 sum += du;
31 }
32 sum / (n as u32)
33 }
34 fn percentile(&self, p: u64) -> Duration {
35 assert!(p > 0);
36 let mut list = self.list.clone();
37 list.sort();
38 let p = p as f64 / 100.;
39 let n = self.list.len() as f64;
40 let i = f64::ceil(p * n) as usize;
41 list[i - 1]
42 }
43}
44impl fmt::Display for BenchResult {
45 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46 let p50 = self.percentile(50);
47 let p95 = self.percentile(95);
48 let p99 = self.percentile(99);
49 writeln!(f, "[ave.] {:?}", self.average())?;
50 writeln!(f, "{:?} (>50%), {:?} (>95%), {:?} (>99%)", p50, p95, p99)?;
51 Ok(())
52 }
53}
54#[derive(Debug)]
55struct ResultSet {
56 tag_indices: IndexSet<String>,
57 h: HashMap<String, BenchResult>,
58}
59impl ResultSet {
60 fn new() -> Self {
61 Self {
62 tag_indices: IndexSet::new(),
63 h: HashMap::new(),
64 }
65 }
66 fn reserve_tag(&mut self, tag: String) {
67 self.tag_indices.insert(tag);
68 }
69 fn add_result(&mut self, tag: String, du: Duration) {
70 self.h
71 .entry(tag)
72 .or_insert(BenchResult::new())
73 .add_result(du);
74 }
75}
76#[derive(Clone)]
88pub struct BenchMan {
89 tag: Arc<String>,
90 result_set: Arc<RwLock<ResultSet>>,
91}
92impl BenchMan {
93 pub fn new(tag: &str) -> Self {
95 let result_set = Arc::new(RwLock::new(ResultSet::new()));
96 Self {
97 tag: Arc::new(tag.to_owned()),
98 result_set,
99 }
100 }
101 pub fn get_stopwatch(&self, tag: &str) -> Stopwatch {
103 self.result_set.write().unwrap().reserve_tag(tag.to_owned());
104 Stopwatch::new(tag.to_owned(), self.result_set.clone())
105 }
106 pub fn slice<'a>(&'a self, sw_tags: impl IntoIterator<Item = &'a str>) -> BenchManSlice<'a> {
110 let result_set_reader = &self.result_set.read().unwrap();
111 let mut m = IndexMap::new();
112 for sw_tag in sw_tags {
113 if let Some(br) = result_set_reader.h.get(sw_tag) {
114 m.insert(sw_tag, br.clone());
115 }
116 }
117 BenchManSlice {
118 bm_tag: &self.tag,
119 slices: m,
120 }
121 }
122}
123impl fmt::Display for BenchMan {
124 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
125 let bench_tag = &self.tag;
126 writeln!(f, "{}", bench_tag.blue())?;
127 let result_set_reader = &self.result_set.read().unwrap();
128 for sw_tag in &result_set_reader.tag_indices {
129 if let Some(v) = result_set_reader.h.get(sw_tag) {
130 let tag = format!("{} ({} samples)", sw_tag, v.n());
131 writeln!(f, "{}", tag.yellow())?;
132 writeln!(f, "{}", v)?;
133 }
134 }
135 Ok(())
136 }
137}
138pub struct BenchManSlice<'a> {
140 bm_tag: &'a str,
141 slices: IndexMap<&'a str, BenchResult>,
142}
143impl<'a> fmt::Display for BenchManSlice<'a> {
144 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
145 let bench_tag = &self.bm_tag;
146 writeln!(f, "{}", bench_tag.blue())?;
147 for (sw_tag, br) in &self.slices {
148 let tag = format!("{} ({} samples)", sw_tag, br.n());
149 writeln!(f, "{}", tag.yellow())?;
150 writeln!(f, "{}", br)?;
151 }
152 Ok(())
153 }
154}
155
156pub struct Stopwatch {
158 tag: Option<String>,
159 t: Instant,
160 result_set: Arc<RwLock<ResultSet>>,
161}
162impl Stopwatch {
163 fn new(tag: String, result_set: Arc<RwLock<ResultSet>>) -> Self {
164 Self {
165 tag: Some(tag),
166 t: Instant::now(),
167 result_set,
168 }
169 }
170}
171impl Drop for Stopwatch {
172 fn drop(&mut self) {
173 let elapsed = self.t.elapsed();
174 let sw_tag = self.tag.take().unwrap();
175 self.result_set.write().unwrap().add_result(sw_tag, elapsed);
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_benchman_spawn() {
185 let benchman = BenchMan::new("spawn");
186 for _ in 0..1 {
187 let bm = benchman.clone();
188 std::thread::spawn(move || {
189 let _sw = bm.get_stopwatch("loop1");
190 let mut _sum: u64 = 0;
191 for i in 0..1000000 {
192 _sum += i;
193 }
194 });
195 }
196 for _ in 0..100 {
197 let bm = benchman.clone();
198 std::thread::spawn(move || {
199 let _sw = bm.get_stopwatch("loop2");
200 let mut _sum: u64 = 0;
201 for i in 0..1000000 {
202 _sum += i;
203 }
204 });
205 }
206 std::thread::sleep(Duration::from_secs(1));
207 println!("{}", benchman);
208 }
209
210 #[test]
211 fn test_benchman_nested() {
212 let benchman = BenchMan::new("nested");
213 let mut _sum: u64 = 0;
214 let sw1 = benchman.get_stopwatch("outer");
215 for i in 0..1000 {
216 let _sw2 = benchman.get_stopwatch("inner");
217 for j in 0..100000 {
218 _sum += i * j;
219 }
220 }
221 println!("{}", benchman.slice(["inner", "outer"]));
222 drop(sw1);
223 println!("{}", benchman.slice(["inner", "outer"]));
224 println!("{}", benchman);
225 }
226}