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
14macro_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(®ex_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(®ex_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(®ex_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(®ex_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}