1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
//! benchman is a RAII-style benchmark tool that
//! focuses on old fashioned one-shot benchmark rather than statistical benchmark.

use colored::*;
use indexmap::{IndexMap, IndexSet};
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use std::sync::RwLock;
use std::time::{Duration, Instant};

#[derive(Clone, Debug)]
struct BenchResult {
    list: Vec<Duration>,
}
impl BenchResult {
    fn new() -> Self {
        Self { list: vec![] }
    }
    fn n(&self) -> usize {
        self.list.len()
    }
    fn add_result(&mut self, du: Duration) {
        self.list.push(du);
    }
    fn average(&self) -> Duration {
        let n = self.list.len();
        let mut sum = Duration::from_secs(0);
        for &du in &self.list {
            sum += du;
        }
        sum / (n as u32)
    }
    fn percentile(&self, p: u64) -> Duration {
        assert!(p > 0);
        let mut list = self.list.clone();
        list.sort();
        let p = p as f64 / 100.;
        let n = self.list.len() as f64;
        let i = f64::ceil(p * n) as usize;
        list[i - 1]
    }
}
impl fmt::Display for BenchResult {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let p50 = self.percentile(50);
        let p95 = self.percentile(95);
        let p99 = self.percentile(99);
        writeln!(f, "[ave.] {:?}", self.average())?;
        writeln!(f, "{:?} (>50%), {:?} (>95%), {:?} (>99%)", p50, p95, p99)?;
        Ok(())
    }
}
#[derive(Debug)]
struct ResultSet {
    tag_indices: IndexSet<String>,
    h: HashMap<String, BenchResult>,
}
impl ResultSet {
    fn new() -> Self {
        Self {
            tag_indices: IndexSet::new(),
            h: HashMap::new(),
        }
    }
    fn reserve_tag(&mut self, tag: String) {
        self.tag_indices.insert(tag);
    }
    fn add_result(&mut self, tag: String, du: Duration) {
        self.h
            .entry(tag)
            .or_insert(BenchResult::new())
            .add_result(du);
    }
}
/// Benchman who collects the result from stopwatches.
///
/// ```rust
/// use benchman::*;
/// let bm = BenchMan::new("bm_tag");
/// let sw = bm.get_stopwatch("sw_tag");
/// let mut sum = 0;
/// for i in 1..10 { sum += i; }
/// drop(sw);
/// eprintln!("{}", bm);
/// ```
#[derive(Clone)]
pub struct BenchMan {
    tag: Arc<String>,
    result_set: Arc<RwLock<ResultSet>>,
}
impl BenchMan {
    /// Create a benchman.
    pub fn new(tag: &str) -> Self {
        let result_set = Arc::new(RwLock::new(ResultSet::new()));
        Self {
            tag: Arc::new(tag.to_owned()),
            result_set,
        }
    }
    /// Get a stopwatch from benchman.
    pub fn get_stopwatch(&self, tag: &str) -> Stopwatch {
        self.result_set.write().unwrap().reserve_tag(tag.to_owned());
        Stopwatch::new(tag.to_owned(), self.result_set.clone())
    }
    /// Get an immutable view of the benchman.
    ///
    /// If a tag in the list isn't found in the current result, the tag is ignored.
    pub fn slice<'a>(&'a self, sw_tags: impl IntoIterator<Item = &'a str>) -> BenchManSlice<'a> {
        let result_set_reader = &self.result_set.read().unwrap();
        let mut m = IndexMap::new();
        for sw_tag in sw_tags {
            if let Some(br) = result_set_reader.h.get(sw_tag) {
                m.insert(sw_tag, br.clone());
            }
        }
        BenchManSlice {
            bm_tag: &self.tag,
            slices: m,
        }
    }
}
impl fmt::Display for BenchMan {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let bench_tag = &self.tag;
        writeln!(f, "{}", bench_tag.blue())?;
        let result_set_reader = &self.result_set.read().unwrap();
        for sw_tag in &result_set_reader.tag_indices {
            if let Some(v) = result_set_reader.h.get(sw_tag) {
                let tag = format!("{} ({} samples)", sw_tag, v.n());
                writeln!(f, "{}", tag.yellow())?;
                writeln!(f, "{}", v)?;
            }
        }
        Ok(())
    }
}
/// Immutable view of the benchman.
pub struct BenchManSlice<'a> {
    bm_tag: &'a str,
    slices: IndexMap<&'a str, BenchResult>,
}
impl<'a> fmt::Display for BenchManSlice<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let bench_tag = &self.bm_tag;
        writeln!(f, "{}", bench_tag.blue())?;
        for (sw_tag, br) in &self.slices {
            let tag = format!("{} ({} samples)", sw_tag, br.n());
            writeln!(f, "{}", tag.yellow())?;
            writeln!(f, "{}", br)?;
        }
        Ok(())
    }
}

/// On drop, it sends a result to the benchman.
pub struct Stopwatch {
    tag: Option<String>,
    t: Instant,
    result_set: Arc<RwLock<ResultSet>>,
}
impl Stopwatch {
    fn new(tag: String, result_set: Arc<RwLock<ResultSet>>) -> Self {
        Self {
            tag: Some(tag),
            t: Instant::now(),
            result_set,
        }
    }
}
impl Drop for Stopwatch {
    fn drop(&mut self) {
        let elapsed = self.t.elapsed();
        let sw_tag = self.tag.take().unwrap();
        self.result_set.write().unwrap().add_result(sw_tag, elapsed);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_benchman_spawn() {
        let benchman = BenchMan::new("spawn");
        for _ in 0..1 {
            let bm = benchman.clone();
            std::thread::spawn(move || {
                let _sw = bm.get_stopwatch("loop1");
                let mut _sum: u64 = 0;
                for i in 0..1000000 {
                    _sum += i;
                }
            });
        }
        for _ in 0..100 {
            let bm = benchman.clone();
            std::thread::spawn(move || {
                let _sw = bm.get_stopwatch("loop2");
                let mut _sum: u64 = 0;
                for i in 0..1000000 {
                    _sum += i;
                }
            });
        }
        std::thread::sleep(Duration::from_secs(1));
        println!("{}", benchman);
    }

    #[test]
    fn test_benchman_nested() {
        let benchman = BenchMan::new("nested");
        let mut _sum: u64 = 0;
        let sw1 = benchman.get_stopwatch("outer");
        for i in 0..1000 {
            let _sw2 = benchman.get_stopwatch("inner");
            for j in 0..100000 {
                _sum += i * j;
            }
        }
        println!("{}", benchman.slice(["inner", "outer"]));
        drop(sw1);
        println!("{}", benchman.slice(["inner", "outer"]));
        println!("{}", benchman);
    }
}