profiler/parse/
callgrind.rs

1extern crate regex;
2
3use std::process::Command;
4use profiler::Profiler;
5use err::ProfError;
6use regex::Regex;
7use std::ffi::OsStr;
8
9// Parser trait. To parse the output of Profilers, we first have to get their output from
10// the command line, and then parse the output into respective structs.
11pub trait CallGrindParser {
12    fn callgrind_cli(&self, binary: &str, binargs: &[&OsStr]) -> Result<String, ProfError>;
13    fn callgrind_parse<'b>(&'b self, output: &'b str, num: usize) -> Result<Profiler, ProfError>;
14}
15
16
17impl CallGrindParser for Profiler {
18    // Get profiler output from stdout.
19    fn callgrind_cli(&self, binary: &str, binargs: &[&OsStr]) -> Result<String, ProfError> {
20
21        // get callgrind cli output from stdout
22        Command::new("valgrind")
23            .arg("--tool=callgrind")
24            .arg("--callgrind-out-file=callgrind.out")
25            .arg(binary)
26            .args(binargs)
27            .output()
28            .unwrap_or_else(|e| panic!("failed to execute process: {}", e));
29
30        let cachegrind_output = Command::new("callgrind_annotate")
31                                    .arg("callgrind.out")
32                                    .arg(binary)
33                                    .output()
34                                    .unwrap_or_else(|e| panic!("failed to execute process: {}", e));
35
36        Ok(String::from_utf8(cachegrind_output.stdout)
37               .expect("error while returning cachegrind stdout"))
38
39    }
40
41    fn callgrind_parse<'b>(&'b self, output: &'b str, num: usize) -> Result<Profiler, ProfError> {
42
43        // split output line-by-line
44        let mut out_split = output.split("\n").collect::<Vec<_>>();
45
46        // regex identifies lines that start with digits and have characters that commonly
47        // show up in file paths
48        lazy_static! {
49           static ref CALLGRIND_REGEX : Regex = Regex::new(r"\d+\s*[a-zA-Z]*$*_*:*/+\.*@*-*|\d+\s*[a-zA-Z]*$*_*\?+:*/*\.*-*@*-*").unwrap();
50           static ref COMPILER_TRASH: Regex = Regex::new(r"\$\w{2}\$|\$\w{3}\$").unwrap();
51           static ref ERROR_REGEX : Regex = Regex::new(r"out of memory").unwrap();
52
53       }
54        let errs = out_split.to_owned()
55                            .into_iter()
56                            .filter(|x| ERROR_REGEX.is_match(x))
57                            .collect::<Vec<_>>();
58        if errs.len() > 0 {
59            return Err(ProfError::OutOfMemoryError);
60        }
61
62        out_split.retain(|x| CALLGRIND_REGEX.is_match(x));
63
64
65        let mut funcs: Vec<String> = Vec::new();
66        let mut data_vec: Vec<f64> = Vec::new();
67        // loop through each line and get numbers + func
68        for sample in out_split.iter() {
69
70
71            // trim the sample, split by whitespace to separate out each data point
72            // (numbers + func)
73            let elems = sample.trim().split("  ").collect::<Vec<_>>();
74
75            // for each number, remove any commas and parse into f64. the last element in
76            // data_elems is the function file path.
77
78            let data_row = match elems[0].trim().replace(",", "").parse::<f64>() {
79                Ok(rep) => rep,
80                Err(_) => return Err(ProfError::RegexError),
81            };
82
83            data_vec.push(data_row);
84
85
86            // the function has some trailing whitespace and trash. remove that, and
87            // get the function, push to functs vector.
88            let path = elems[1].split(" ").collect::<Vec<_>>();
89            let cleaned_path = path[0].split("/").collect::<Vec<_>>();
90            let func = cleaned_path[cleaned_path.len() - 1];
91            let mut func = COMPILER_TRASH.replace_all(func, "..");
92            let idx = func.rfind("::").unwrap_or(func.len());
93            func.drain(idx..).collect::<String>();
94            funcs.push(func)
95
96        }
97
98        // get the total instructions by summing the data vector.
99        let total_instructions = data_vec.iter().fold(0.0, |a, b| a + b);
100
101        // parse the limit argument n, and take the first n values of data/funcs vectors
102        // accordingly.
103
104        if num < data_vec.len() {
105            data_vec = data_vec.iter().take(num).cloned().collect();
106            funcs = funcs.iter().take(num).cloned().collect();
107        }
108        // put all data in cachegrind struct!
109        Ok(Profiler::CallGrind {
110            total_instructions: total_instructions,
111            instructions: data_vec,
112            functs: funcs,
113        })
114    }
115}
116
117#[cfg(test)]
118mod test {
119    use profiler::Profiler;
120    use super::CallGrindParser;
121    #[test]
122    fn test_callgrind_parse_1() {
123        let output = "==6072==     Valgrind's memory management: out of memory:\n ==6072==     \
124                      Whatever the reason, Valgrind cannot continue.  Sorry.";
125        let num = 10;
126        let profiler = Profiler::new_callgrind();
127        let is_err = profiler.callgrind_parse(&output, num).is_err();
128        assert!(is_err && true)
129    }
130
131    #[test]
132    fn test_callgrind_parse_2() {
133        assert_eq!(1, 1);
134        assert_eq!(1, 1);
135    }
136
137    #[test]
138    fn test_callgrind_parse_3() {
139        assert_eq!(1, 1);
140    }
141}