profiler/parse/
cachegrind.rs

1extern crate ndarray;
2extern crate regex;
3
4use std::process::Command;
5use self::ndarray::{Axis, stack, OwnedArray, ArrayView, Ix};
6use profiler::Profiler;
7use std::cmp::Ordering::Less;
8use err::ProfError;
9use regex::Regex;
10use std::ffi::OsStr;
11
12/// initialize matrix object
13pub type Mat<A> = OwnedArray<A, (Ix, Ix)>;
14
15/// define cachegrind metrics
16pub enum Metric {
17    Ir,
18    I1mr,
19    ILmr,
20    Dr,
21    D1mr,
22    DLmr,
23    Dw,
24    D1mw,
25    DLmw,
26    NAN,
27}
28
29
30/// Utility function for sorting a matrix. used to sort cachegrind data by particular metric (descending)
31pub fn sort_matrix(mat: &Mat<f64>, sort_col: ArrayView<f64, Ix>) -> (Mat<f64>, Vec<usize>) {
32    let mut enum_col = sort_col.iter().enumerate().collect::<Vec<(usize, &f64)>>();
33    enum_col.sort_by(|a, &b| a.1.partial_cmp(b.1).unwrap_or(Less).reverse());
34    let indices = enum_col.iter().map(|x| x.0).collect::<Vec<usize>>();
35    (mat.select(Axis(0), indices.as_slice()), indices)
36}
37
38
39/// Parser trait. To parse the output of Profilers, we first have to get their output from
40/// the command line, and then parse the output into respective structs.
41pub trait CacheGrindParser {
42    fn cachegrind_cli(&self, binary: &str, binargs: &[&OsStr]) -> Result<String, ProfError>;
43    fn cachegrind_parse<'b>(&'b self,
44                            output: &'b str,
45                            num: usize,
46                            sort_metric: Metric)
47                            -> Result<Profiler, ProfError>;
48}
49
50
51
52
53
54impl CacheGrindParser for Profiler {
55    /// Get profiler output from stdout.
56    fn cachegrind_cli(&self, binary: &str, binargs: &[&OsStr]) -> Result<String, ProfError> {
57
58        // get cachegrind cli output from stdout
59        let _ = Command::new("valgrind")
60                    .arg("--tool=cachegrind")
61                    .arg("--cachegrind-out-file=cachegrind.out")
62                    .arg(binary)
63                    .args(binargs)
64                    .output()
65                    .or(Err(ProfError::CliError));
66
67        let cachegrind_output = Command::new("cg_annotate")
68                                    .arg("cachegrind.out")
69                                    .arg(binary)
70                                    .output()
71                                    .or(Err(ProfError::CliError));
72
73        cachegrind_output.and_then(|x| String::from_utf8(x.stdout).or(Err(ProfError::UTF8Error)))
74                         .or(Err(ProfError::CliError))
75
76
77    }
78    // Get parse the profiler output into respective structs.
79    fn cachegrind_parse<'b>(&'b self,
80                            output: &'b str,
81                            num: usize,
82                            sort_metric: Metric)
83                            -> Result<Profiler, ProfError> {
84        // split output line-by-line
85        let mut out_split: Vec<&'b str> = output.split("\n").collect();
86
87        // regex identifies lines that start with digits and have characters that commonly
88        // show up in file paths
89        lazy_static! {
90           static ref CACHEGRIND_REGEX : Regex = Regex::new(r"\d+\s*[a-zA-Z]*$*_*:*/+\.*@*-*|\d+\s*[a-zA-Z]*$*_*\?+:*/*\.*-*@*-*").unwrap();
91           static ref COMPILER_TRASH: Regex = Regex::new(r"\$\w{2}\$|\$\w{3}\$").unwrap();
92           static ref ERROR_REGEX : Regex = Regex::new(r"Valgrind's memory management: out of memory").unwrap();
93       }
94
95        let errs = out_split.to_owned()
96                            .into_iter()
97                            .filter(|x| ERROR_REGEX.is_match(x))
98                            .collect::<Vec<_>>();
99
100        if errs.len() > 0 {
101            return Err(ProfError::OutOfMemoryError);
102        }
103
104        out_split.retain(|x| CACHEGRIND_REGEX.is_match(x));
105
106        let mut funcs: Vec<String> = Vec::new();
107        let mut data_vec: Vec<Mat<f64>> = Vec::new();
108
109        // loop through each line and get numbers + func
110        for sample in out_split.iter() {
111
112            // trim the sample, split by whitespace to separate out each data point
113            // (numbers + func)
114            let mut elems = sample.trim()
115                                  .split(" ")
116                                  .collect::<Vec<&'b str>>();
117            // remove any empty strings
118            elems.retain(|x| x.to_string() != "");
119
120            // for each number, remove any commas and parse into f64. the last element in
121            // data_elems is the function file path.
122            let mut numbers = Vec::new();
123
124            for elem in elems[0..elems.len() - 1].iter() {
125                let number = match elem.trim().replace(",", "").parse::<f64>() {
126                    Ok(n) => n,
127                    Err(_) => return Err(ProfError::RegexError),
128                };
129
130                numbers.push(number);
131            }
132
133
134            // reshape the vector of parsed numbers into a 1 x 9 matrix, and push the
135            // matrix to our vector of 1 x 9 matrices.
136            if let Ok(data_col) = OwnedArray::from_shape_vec((numbers.len(), 1), numbers) {
137                data_vec.push(data_col);
138            }
139            // the last element in data_elems is the function file path.
140            // get the file in the file-path (which includes the function) and push that to
141            // the funcs vector.
142            let path = elems[elems.len() - 1].split("/").collect::<Vec<&'b str>>();
143            let func = path[path.len() - 1];
144
145            let mut func = COMPILER_TRASH.replace_all(func, "");
146            let idx = func.rfind("::").unwrap_or(func.len());
147            func.drain(idx..).collect::<String>();
148            funcs.push(func);
149
150        }
151
152
153
154
155
156
157        // stack all the 1 x 9 matrices in data to a n x 9  matrix.
158        let data_matrix = match stack(Axis(1),
159                                      &data_vec.iter()
160                                               .map(|x| x.view())
161                                               .collect::<Vec<_>>()
162                                               .as_slice()) {
163            Ok(m) => m.t().to_owned(),
164            Err(_) => return Err(ProfError::MisalignedData),
165
166        };
167
168
169        // match the sort argument to a column of the matrix that we will sort on.
170        // default sorting -> first column (total instructions).
171        let sort_col = match sort_metric {
172            Metric::Ir => data_matrix.column(0),
173            Metric::I1mr => data_matrix.column(1),
174            Metric::ILmr => data_matrix.column(2),
175            Metric::Dr => data_matrix.column(3),
176            Metric::D1mr => data_matrix.column(4),
177            Metric::DLmr => data_matrix.column(5),
178            Metric::Dw => data_matrix.column(6),
179            Metric::D1mw => data_matrix.column(7),
180            Metric::DLmw => data_matrix.column(8),
181            Metric::NAN => data_matrix.column(0),
182        };
183
184        // sort the matrix of data and functions by a particular column.
185        // to sort matrix, we keep track of sorted indices, and select the matrix wrt
186        // these sorted indices. to sort functions, we index the funcs vector with the
187        // sorted indices.
188        let (mut sorted_data_matrix, indices) = sort_matrix(&data_matrix, sort_col);
189
190        let mut sorted_funcs: Vec<String> = indices.iter()
191                                                   .map(|&x| (&funcs[x]).to_owned())
192                                                   .collect::<Vec<String>>();
193
194
195
196        // sum the columns of the data matrix to get total metrics.
197        let ir = sorted_data_matrix.column(0).scalar_sum();
198        let i1mr = sorted_data_matrix.column(1).scalar_sum();
199        let ilmr = sorted_data_matrix.column(2).scalar_sum();
200        let dr = sorted_data_matrix.column(3).scalar_sum();
201        let d1mr = sorted_data_matrix.column(4).scalar_sum();
202        let dlmr = sorted_data_matrix.column(5).scalar_sum();
203        let dw = sorted_data_matrix.column(6).scalar_sum();
204        let d1mw = sorted_data_matrix.column(7).scalar_sum();
205        let dlmw = sorted_data_matrix.column(8).scalar_sum();
206
207        // parse the limit argument n, and take the first n values of data matrix/funcs
208        // vector accordingly.
209        if num < sorted_data_matrix.rows() {
210            let ls = (0..num).collect::<Vec<_>>();
211            sorted_data_matrix = sorted_data_matrix.select(Axis(0), ls.as_slice());
212            sorted_funcs = sorted_funcs.iter()
213                                       .take(num)
214                                       .cloned()
215                                       .collect();
216        }
217
218
219
220        // put all data in cachegrind struct!
221        Ok(Profiler::CacheGrind {
222            ir: ir,
223            i1mr: i1mr,
224            ilmr: ilmr,
225            dr: dr,
226            d1mr: d1mr,
227            dlmr: dlmr,
228            dw: dw,
229            d1mw: d1mw,
230            dlmw: dlmw,
231            data: sorted_data_matrix,
232            functs: sorted_funcs,
233        })
234    }
235}
236
237
238#[cfg(test)]
239mod test {
240    #[test]
241    fn test_cachegrind_parse_1() {
242        assert_eq!(1, 1);
243    }
244
245    #[test]
246    fn test_cachegrind_parse_2() {
247        assert_eq!(1, 1);
248        assert_eq!(1, 1);
249    }
250
251    #[test]
252    fn test_cachegrind_parse_3() {
253        assert_eq!(1, 1);
254    }
255}