cfpyo3_core/toolkit/
misc.rs

1#[cfg(feature = "tokio")]
2use anyhow::Result;
3use md5::{Digest, Md5};
4use std::{collections::HashMap, fmt, sync::RwLock, time::Instant};
5#[cfg(feature = "tokio")]
6use tokio::runtime::{Builder, Runtime};
7
8pub fn hash_code(code: &str) -> String {
9    let mut hasher = Md5::new();
10    hasher.update(code.as_bytes());
11    format!("{:x}", hasher.finalize())
12}
13
14/// A simple tracker to record the time of each event.
15#[derive(Clone)]
16pub struct Tracker {
17    history: Vec<f64>,
18    tracking: Option<Instant>,
19}
20pub struct Stats {
21    n: usize,
22    mean: f64,
23    std: f64,
24    is_fast_path: bool,
25    is_bottleneck: bool,
26}
27impl Tracker {
28    pub const fn new() -> Self {
29        Self {
30            history: Vec::new(),
31            tracking: None,
32        }
33    }
34
35    pub fn track(&mut self, time: f64) {
36        self.history.push(time);
37    }
38
39    pub fn track_start(&mut self) {
40        self.tracking = Some(Instant::now());
41    }
42
43    pub fn track_end(&mut self) {
44        let time = self
45            .tracking
46            .expect("please call `track_start` before `track_end`")
47            .elapsed()
48            .as_secs_f64();
49        self.history.push(time);
50        self.tracking = None;
51    }
52
53    pub fn reset(&mut self) {
54        self.history.clear();
55        self.tracking = None;
56    }
57
58    pub fn get_stats(&self) -> Stats {
59        let n = self.history.len();
60        let mean = self.history.iter().sum::<f64>() / n as f64;
61        let variance = self.history.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / n as f64;
62        let std = variance.sqrt();
63        Stats {
64            n,
65            mean,
66            std,
67            is_fast_path: false,
68            is_bottleneck: false,
69        }
70    }
71}
72impl Default for Tracker {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77impl fmt::Debug for Stats {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        let sum = self.n as f64 * self.mean;
80        let prefix = if self.is_bottleneck {
81            format!("[🚨 {:.8}] ", sum)
82        } else if self.is_fast_path {
83            format!("[⚡️ {:.8}] ", sum)
84        } else {
85            format!("[   {:.8}] ", sum)
86        };
87        write!(
88            f,
89            "{}{:.8} ± {:.8} ({})",
90            prefix, self.mean, self.std, self.n
91        )
92    }
93}
94
95/// A simple container of `Tracker`s, useful if you want to inspect concurrent events.
96pub struct Trackers(pub Vec<RwLock<Tracker>>);
97impl Trackers {
98    pub fn new(n: usize) -> Self {
99        Self((0..n).map(|_| RwLock::new(Tracker::new())).collect())
100    }
101
102    pub fn track(&self, idx: usize, time: f64) {
103        self.0[idx].write().unwrap().track(time);
104    }
105
106    pub fn track_start(&self, idx: usize) {
107        self.0[idx].write().unwrap().track_start();
108    }
109
110    pub fn track_end(&self, idx: usize) {
111        self.0[idx].write().unwrap().track_end();
112    }
113
114    pub fn reset(&self) {
115        self.0
116            .iter()
117            .for_each(|tracker| tracker.write().unwrap().reset());
118    }
119
120    pub fn get_stats(&self) -> Vec<Stats> {
121        let mut stats: Vec<Stats> = self
122            .0
123            .iter()
124            .map(|tracker| tracker.read().unwrap().get_stats())
125            .collect();
126        let mut fast_path_idx = 0;
127        let mut bottleneck_idx = 0;
128        let mut fast_path_t = stats[0].n as f64 * stats[0].mean;
129        let mut bottleneck_t = fast_path_t;
130        for (idx, s) in stats.iter().enumerate() {
131            let new_t = s.n as f64 * s.mean;
132            if new_t > bottleneck_t {
133                bottleneck_idx = idx;
134                bottleneck_t = new_t;
135            } else if new_t < fast_path_t {
136                fast_path_idx = idx;
137                fast_path_t = new_t;
138            }
139        }
140        stats[fast_path_idx].is_fast_path = true;
141        stats[bottleneck_idx].is_bottleneck = true;
142        stats
143    }
144}
145
146/// A simple, named container of `Tracker`s, useful if you want to inspect different events and compare them.
147pub struct NamedTrackers(pub HashMap<String, RwLock<Tracker>>);
148impl NamedTrackers {
149    pub fn new(names: Vec<String>) -> Self {
150        Self(
151            names
152                .into_iter()
153                .map(|name| (name, RwLock::new(Tracker::new())))
154                .collect(),
155        )
156    }
157
158    fn get(&self, name: &str) -> &RwLock<Tracker> {
159        self.0
160            .get(name)
161            .unwrap_or_else(|| panic!("'{}' not found in current trackers", name))
162    }
163
164    pub fn track(&self, name: &str, time: f64) {
165        self.get(name).write().unwrap().track(time);
166    }
167
168    pub fn track_start(&self, name: &str) {
169        self.get(name).write().unwrap().track_start();
170    }
171
172    pub fn track_end(&self, name: &str) {
173        self.get(name).write().unwrap().track_end();
174    }
175
176    pub fn reset(&self) {
177        self.0
178            .iter()
179            .for_each(|(_, tracker)| tracker.write().unwrap().reset());
180    }
181
182    pub fn get_stats(&self) -> HashMap<String, Stats> {
183        self.0
184            .iter()
185            .map(|(name, tracker)| (name.clone(), tracker.read().unwrap().get_stats()))
186            .collect()
187    }
188}
189
190// tokio utils
191
192#[cfg(feature = "tokio")]
193pub fn init_rt(num_threads: usize) -> Result<Runtime> {
194    let rt = if num_threads <= 1 {
195        Builder::new_current_thread().enable_all().build()?
196    } else {
197        Builder::new_multi_thread()
198            .worker_threads(num_threads)
199            .enable_all()
200            .build()?
201    };
202    Ok(rt)
203}