pibench_parser/
lib.rs

1use regex::{self, Regex};
2use serde::Serialize;
3use serde_json;
4use std::error;
5use wasm_bindgen::prelude::*;
6
7mod utils;
8
9#[cfg(target_arch = "wasm32")]
10extern crate console_error_panic_hook;
11#[cfg(target_arch = "wasm32")]
12extern crate web_sys;
13
14// A macro to provide `println!(..)`-style syntax for `console.log` logging.
15macro_rules! log {
16    ( $( $t:tt )* ) => {
17        #[cfg(target_arch = "wasm32")]
18        web_sys::console::log_1(&format!( $( $t )* ).into());
19    }
20}
21
22#[cfg(not(target_arch = "wasm32"))]
23mod c_ffi;
24
25#[cfg(not(target_arch = "wasm32"))]
26pub use c_ffi::*;
27
28#[wasm_bindgen]
29#[derive(PartialOrd, PartialEq, Debug, Serialize, Copy, Clone)]
30pub enum KeyDistribution {
31    UNIFORM = 0,
32    ZIPFAN = 1,
33}
34
35#[wasm_bindgen]
36#[derive(PartialEq, PartialOrd, Debug, Serialize, Copy, Clone)]
37pub struct BenchmarkOptions {
38    sampling: i32,
39    latency: f32,
40    key_size: i32,
41    value_size: i32,
42    random_seed: i32,
43    read: f32,
44    insert: f32,
45    update: f32,
46    delete: f32,
47    scan: f32,
48    key_distribution: KeyDistribution,
49    records: i32,
50    operations: i32,
51    threads: i32,
52}
53
54impl BenchmarkOptions {
55    pub fn from_text(text: &str) -> Result<BenchmarkOptions, Box<dyn error::Error>> {
56        let re = Regex::new(
57            "# Records: (?P<records>\\d+)\\s*\n\
58                \\s*# Operations: (?P<operations>\\d+)\\s*\n\
59               \\s*# Threads: (?P<threads>\\d+)\\s*\n\
60               \\s*Sampling: (?P<sampling>\\d+) ms\\s*\n\
61               \\s*Latency: (?P<latency>\\d*\\.?\\d*)\\s*\n\
62               .*\n\
63               \\s*Key size: (?P<key_size>\\d+)\\s*\n\
64               \\s*Value size: (?P<value_size>\\d+)\\s*\n\
65               \\s*Random seed: (?P<random_seed>\\d+)\\s*\n\
66               \\s*Key distribution: (?P<key_distribution>\\w+)\\s*\n\
67               \\s*Scan size: (?P<scan_size>\\d+)\\s*\n\
68               .*\n\
69               \\s*Read: (?P<read>\\d*\\.?\\d*)\\s*\n\
70               \\s*Insert: (?P<insert>\\d*\\.?\\d*)\\s*\n\
71               \\s*Update: (?P<update>\\d*\\.?\\d*)\\s*\n\
72               \\s*Delete: (?P<delete>\\d*\\.?\\d*)\\s*\n\
73               \\s*Scan: (?P<scan>\\d*\\.?\\d*)\\s*",
74        )
75        .unwrap();
76        let caps = re.captures(text).unwrap();
77
78        Ok(BenchmarkOptions {
79            sampling: caps["sampling"].parse::<i32>()?,
80            records: caps["records"].parse::<i32>()?,
81            threads: caps["threads"].parse::<i32>()?,
82            operations: caps["operations"].parse::<i32>()?,
83            latency: caps["latency"].parse::<f32>()?,
84            key_size: caps["key_size"].parse::<i32>()?,
85            key_distribution: KeyDistribution::UNIFORM,
86            value_size: caps["value_size"].parse::<i32>()?,
87            random_seed: caps["random_seed"].parse::<i32>()?,
88            read: caps["read"].parse::<f32>()?,
89            insert: caps["insert"].parse::<f32>()?,
90            delete: caps["delete"].parse::<f32>()?,
91            update: caps["update"].parse::<f32>()?,
92            scan: caps["scan"].parse::<f32>()?,
93        })
94    }
95}
96
97#[wasm_bindgen]
98#[derive(Eq, PartialEq, PartialOrd, Debug, Serialize, Copy, Clone)]
99pub struct LatencyResults {
100    min: i32,
101    p_50: i32,
102    p_90: i32,
103    p_99: i32,
104    p_99_9: i32,
105    p_99_99: i32,
106    p_99_999: i32,
107    max: i32,
108}
109
110impl LatencyResults {
111    pub fn from_text(text: &str) -> Result<Option<LatencyResults>, Box<dyn error::Error>> {
112        let re = Regex::new(
113            "Latencies .*\n\
114            \\s*min: (?P<min>\\d+)\\s*\n\
115            \\s*50%: (?P<p_50>\\d+)\\s*\n\
116            \\s*90%: (?P<p_90>\\d+)\\s*\n\
117            \\s*99%: (?P<p_99>\\d+)\\s*\n\
118            \\s*99.9%: (?P<p_99_9>\\d+)\\s*\n\
119            \\s*99.99%: (?P<p_99_99>\\d+)\\s*\n\
120            \\s*99.999%: (?P<p_99_999>\\d+)\\s*\n\
121            \\s*max: (?P<max>\\d+)\\s*",
122        )?;
123        let caps = match re.captures(text) {
124            Some(caps) => caps,
125            None => return Ok(None),
126        };
127        Ok(Some(LatencyResults {
128            min: caps["min"].parse::<i32>()?,
129            p_50: caps["p_50"].parse::<i32>()?,
130            p_90: caps["p_90"].parse::<i32>()?,
131            p_99: caps["p_99"].parse::<i32>()?,
132            p_99_9: caps["p_99_9"].parse::<i32>()?,
133            p_99_99: caps["p_99_99"].parse::<i32>()?,
134            p_99_999: caps["p_99_999"].parse::<i32>()?,
135            max: caps["max"].parse::<i32>()?,
136        }))
137    }
138}
139
140#[wasm_bindgen]
141#[derive(PartialOrd, Eq, PartialEq, Debug, Clone, Serialize)]
142pub struct PCMResults {
143    l3_misses: u64,
144    dram_reads: u64,
145    dram_writes: u64,
146    nvm_reads: u64,
147    nvm_writes: u64,
148}
149
150impl PCMResults {
151    pub fn from_text(text: &str) -> Result<Option<PCMResults>, Box<dyn error::Error>> {
152        let regex_raw = "\\s*L3 misses: (?P<l3_misses>\\d+)\\s*\n\
153            \\s*DRAM Reads \\(bytes\\): (?P<dram_reads>\\d+)\\s*\n\
154            \\s*DRAM Writes \\(bytes\\): (?P<dram_writes>\\d+)\\s*\n\
155            \\s*NVM Reads \\(bytes\\): (?P<nvm_reads>\\d+)\\s*\n\
156            \\s*NVM Writes \\(bytes\\): (?P<nvm_writes>\\d+)\\s*";
157        let caps = match Regex::new(&regex_raw)?.captures(text) {
158            Some(caps) => caps,
159            None => return Ok(None),
160        };
161
162        Ok(Some(PCMResults {
163            l3_misses: caps["l3_misses"].parse::<u64>()?,
164            dram_reads: caps["dram_reads"].parse::<u64>()?,
165            dram_writes: caps["dram_writes"].parse::<u64>()?,
166            nvm_reads: caps["nvm_reads"].parse::<u64>()?,
167            nvm_writes: caps["nvm_writes"].parse::<u64>()?,
168        }))
169    }
170}
171
172#[wasm_bindgen]
173#[derive(PartialEq, PartialOrd, Debug, Serialize, Clone)]
174pub struct BenchmarkResults {
175    load_time: f32,
176    run_time: f32,
177    throughput: f32,
178    pcm: Option<PCMResults>,
179    latency: Option<LatencyResults>,
180    samples: Option<Vec<u32>>,
181}
182
183impl BenchmarkResults {
184    pub fn from_text(text: &str) -> Result<BenchmarkResults, Box<dyn error::Error>> {
185        const FLOATING_REGEX: &str = "[+\\-]?(?:0|[1-9]\\d*)(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?";
186        let regex_raw = format!(
187            "Load time: (?P<load_time>{floating}) milliseconds\\s*\n\
188            \\s*Run time: (?P<run_time>{floating}) milliseconds\\s*\n\
189            \\s*Throughput: (?P<throughput>{floating}) ops/s",
190            floating = FLOATING_REGEX
191        );
192        let re = Regex::new(&regex_raw)?;
193        let caps = re.captures(text).unwrap();
194
195        let latency_results = LatencyResults::from_text(text)?;
196        let pcm_results = PCMResults::from_text(text)?;
197        let samples = BenchmarkResults::capture_samples(text)?;
198
199        Ok(BenchmarkResults {
200            load_time: caps["load_time"].parse::<f32>()?,
201            run_time: caps["run_time"].parse::<f32>()?,
202            throughput: caps["throughput"].parse::<f32>()?,
203            pcm: pcm_results,
204            latency: latency_results,
205            samples,
206        })
207    }
208
209    pub fn capture_samples(text: &str) -> Result<Option<Vec<u32>>, Box<dyn error::Error>> {
210        let regex_raw = r"Samples:\s*(?P<samples>(\s*\d+\n*)*)";
211        let caps = match Regex::new(&regex_raw)?.captures(text) {
212            Some(caps) => caps,
213            None => {
214                return Ok(None);
215            }
216        };
217        let samples = caps["samples"]
218            .split("\n")
219            .map(|item| item.trim())
220            .filter(|item| !item.is_empty())
221            .map(|item| item.parse::<u32>())
222            .collect::<Result<Vec<_>, _>>();
223        Ok(Some(samples?))
224    }
225}
226
227#[wasm_bindgen]
228#[derive(Serialize, Debug, Eq, PartialEq)]
229struct BenchmarkEnv {
230    time: String,
231    cpu: String,
232    cache: String,
233    kernel: String,
234}
235
236impl BenchmarkEnv {
237    pub fn from_text(text: &str) -> Result<Self, Box<dyn error::Error>> {
238        let regex_raw = "\\s*Time: (?P<time>.*)\\s*\n\
239                                \\s*CPU: (?P<cpu>.*)\\s*\n\
240                                \\s*CPU Cache: (?P<cache>.*)\\s*\n\
241                                \\s*Kernel: (?P<kernel>.*)\\s*\n";
242        let caps = Regex::new(&regex_raw)?.captures(text).unwrap();
243
244        Ok(BenchmarkEnv {
245            time: caps["time"].to_string(),
246            cpu: caps["cpu"].to_string(),
247            cache: caps["cache"].to_string(),
248            kernel: caps["kernel"].to_string(),
249        })
250    }
251}
252
253#[wasm_bindgen]
254#[derive(Serialize)]
255pub struct PiBenchData {
256    options: BenchmarkOptions,
257    results: BenchmarkResults,
258    env: BenchmarkEnv,
259}
260
261impl PiBenchData {
262    pub fn to_json(&self) -> String {
263        serde_json::to_string(&self).unwrap()
264    }
265}
266
267#[wasm_bindgen]
268impl PiBenchData {
269    pub fn from_text(input: &str) -> Option<PiBenchData> {
270        utils::set_panic_hook();
271        let benchmark_options = BenchmarkOptions::from_text(input);
272        let benchmark_results = BenchmarkResults::from_text(input);
273        let benchmark_env = BenchmarkEnv::from_text(input);
274
275        if benchmark_options.is_err() || benchmark_results.is_err() || benchmark_env.is_err() {
276            return None;
277        }
278
279        return Some(PiBenchData {
280            options: benchmark_options.unwrap(),
281            results: benchmark_results.unwrap(),
282            env: benchmark_env.unwrap(),
283        });
284    }
285    pub fn to_js_value(&self) -> JsValue {
286        JsValue::from_serde(&self).unwrap()
287    }
288}
289
290#[cfg(test)]
291mod tests {
292    use super::*;
293
294    #[test]
295    fn parse_benchmark_results() {
296        let sample_string = "Overview:
297                                    Load time: 90801.3 milliseconds
298                                    Run time: 79192.3672 milliseconds
299                                    Throughput: 126274.7969 ops/s
300                                PCM Metrics:
301                                    L3 misses: 133342466
302                                    DRAM Reads (bytes): 4197345472
303                                    DRAM Writes (bytes): 3685394624
304                                    NVM Reads (bytes): 60347831872
305                                    NVM Writes (bytes): 11408209856
306                                Samples:
307                                	135452
308	                                126077
309                                    109243
310                                Latencies (994788 operations
311                                ";
312        let gt = BenchmarkResults {
313            load_time: 90801.3,
314            run_time: 79192.3672,
315            throughput: 126274.7969,
316            pcm: Some(PCMResults {
317                l3_misses: 133342466,
318                dram_reads: 4197345472,
319                dram_writes: 3685394624,
320                nvm_reads: 60347831872,
321                nvm_writes: 11408209856,
322            }),
323            latency: None,
324            samples: Some(vec![135452, 126077, 109243]),
325        };
326        let result = BenchmarkResults::from_text(sample_string);
327        assert!(result.is_ok());
328        assert_eq!(result.unwrap(), gt);
329    }
330
331    #[test]
332    fn parse_latency_results() {
333        let sample_string = "Latencies (998141 operations observed):
334                                    min: 882
335                                    50%: 7481
336                                    90%: 9121
337                                    99%: 43233
338                                    99.9%: 51150
339                                    99.99%: 69460
340                                    99.999%: 16985300
341                                    max: 22247728
342                                ";
343        let gt = LatencyResults {
344            min: 882,
345            p_50: 7481,
346            p_90: 9121,
347            p_99: 43233,
348            p_99_9: 51150,
349            p_99_99: 69460,
350            p_99_999: 16985300,
351            max: 22247728,
352        };
353        let latency = LatencyResults::from_text(sample_string);
354        assert!(latency.is_ok());
355        assert_eq!(latency.unwrap().unwrap(), gt);
356    }
357
358    #[test]
359    fn parse_benchmark_options() {
360        let sample_string = " Environment:
361                                    Time: Sat May  9 13:39:56 2020
362                                    CPU: 96 * Intel(R) Xeon(R) Gold 6252 CPU @ 2.10GHz
363                                    CPU Cache: 36608 KB
364                                    Kernel: Linux 5.5.4-arch1-1
365                                    Benchmark Options:
366                                    Target: /home/hao/coding/bztree/release/libbztree_pibench_wrapper.so
367                                    # Records: 10000000
368                                    # Operations: 10000000
369                                    # Threads: 1
370                                    Sampling: 1000 ms
371                                    Latency: 0.1
372                                    Key prefix: 
373                                    Key size: 8
374                                    Value size: 8
375                                    Random seed: 1729
376                                    Key distribution: UNIFORM
377                                    Scan size: 100
378                                    Operations ratio:
379                                        Read: 0.2
380                                        Insert: 0.8
381                                        Update: 0
382                                        Delete: 0
383                                        Scan: 0
384                                creating new tree on pool.
385                                ";
386        let gt = BenchmarkOptions {
387            records: 10000000,
388            operations: 10000000,
389            threads: 1,
390            sampling: 1000,
391            latency: 0.1,
392            key_size: 8,
393            value_size: 8,
394            random_seed: 1729,
395            key_distribution: KeyDistribution::UNIFORM,
396            scan: 0.,
397            read: 0.2,
398            insert: 0.8,
399            update: 0.,
400            delete: 0.,
401        };
402        let options = BenchmarkOptions::from_text(sample_string);
403        assert!(options.is_ok());
404        assert_eq!(options.unwrap(), gt);
405    }
406
407    #[test]
408    fn parse_benchmark_env() {
409        let sample_string=" Environment:
410                                    Time: Sat May  9 13:39:56 2020
411                                    CPU: 96 * Intel(R) Xeon(R) Gold 6252 CPU @ 2.10GHz
412                                    CPU Cache: 36608 KB
413                                    Kernel: Linux 5.5.4-arch1-1
414                                    Benchmark Options:
415                                    Target: /home/hao/coding/bztree/release/libbztree_pibench_wrapper.so
416                                    # Records: 10000000
417                                    # Operations: 10000000
418                                    # Threads: 1
419                                    Sampling: 1000 ms";
420
421        let gt = BenchmarkEnv {
422            time: "Sat May  9 13:39:56 2020".to_string(),
423            cpu: "96 * Intel(R) Xeon(R) Gold 6252 CPU @ 2.10GHz".to_string(),
424            cache: "36608 KB".to_string(),
425            kernel: "Linux 5.5.4-arch1-1".to_string(),
426        };
427
428        let env = BenchmarkEnv::from_text(sample_string);
429        assert!(env.is_ok());
430        assert_eq!(env.unwrap(), gt);
431    }
432}