global_duration/
lib.rs

1mod checkpoint;
2
3use checkpoint::Checkpoint;
4use lazy_static::lazy_static;
5use std::fs::OpenOptions;
6use std::sync::Mutex;
7use std::io::Write;
8
9#[derive(Clone)]
10enum To {
11    Stderr,
12    File(String),
13}
14
15// The main point of this crate is to make it easy to measure the duration between
16// two arbitrary code locations. Globals are generally evil, but in this case it is
17// exactly what we want, since passing a struct around from the first place
18// to the second place often would be prohibitively much work.
19lazy_static! {
20    static ref LAST_CHECKPOINT: Mutex<Option<Checkpoint>> = Mutex::from(None);
21    static ref TO: Mutex<To> = Mutex::from(To::Stderr);
22}
23
24pub fn to_file(path: &str) {
25    let mut to = TO.lock().unwrap();
26    *to = To::File(String::from(path));
27}
28
29pub fn checkpoint(name: &str) {
30    let output = update_checkpoint(name);
31    if let Some(output) = output {
32        print(&output);
33    }
34}
35
36fn update_checkpoint(new_name: &str) -> Option<String> {
37    let mut last_checkpoint = LAST_CHECKPOINT.lock().unwrap();
38
39    let output = match &*last_checkpoint {
40        // No ouput first checkpoint to minimize effects on duration measurements
41        None => None,
42        Some(checkpoint) => Some(format!(
43            "{:?} from '{}' to '{}'\n",
44            checkpoint.instant.elapsed(),
45            checkpoint.name,
46            new_name,
47        )),
48    };
49
50    *last_checkpoint = Some(Checkpoint::new(new_name));
51
52    output
53}
54
55fn print(output: &str) {
56    let to;
57    {
58        // Hold lock for minimal amount of time for minimal risk of e.g. lock poisoning
59        to = TO.lock().unwrap().clone();
60    }
61
62    match to {
63        To::Stderr => {
64            eprint!("{}", output);
65        }
66        To::File(path) => {
67            let mut file = OpenOptions::new().create(true).append(true).open(&path).unwrap();
68            if let Err(e) = write!(file, "{}", output) {
69                eprint!("Error while writing to {}: {:?}", &path, e)
70            }
71        }
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    use std::thread::sleep;
80    use std::time::Duration;
81
82    // TODO: Port this test to and write more tests using assert_cmd
83    #[test]
84    fn basic_to_file() {
85        // Do the test
86        to_file("/tmp/com.setofskills.global_duration.test-output.txt");
87        checkpoint("checkpoint 1");
88        sleep(Duration::from_millis(1000));
89        checkpoint("checkpoint 2");
90        sleep(Duration::from_millis(2000));
91        checkpoint("checkpoint 3");
92
93        // TODO: Assert result
94        // let actual_contents = std::fs::read_to_string("/tmp/global-duration-to-file.test-output.txt").unwrap();
95    }
96}