OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 # drmemory_analyze.py | 6 # drmemory_analyze.py |
7 | 7 |
8 ''' Given a Dr. Memory output file, parses errors and uniques them.''' | 8 ''' Given a Dr. Memory output file, parses errors and uniques them.''' |
9 | 9 |
| 10 from collections import defaultdict |
| 11 import common |
10 import logging | 12 import logging |
11 import optparse | 13 import optparse |
12 import os | 14 import os |
13 import re | 15 import re |
14 import subprocess | 16 import subprocess |
15 import sys | 17 import sys |
16 import time | 18 import time |
17 | 19 |
18 class _StackTraceLine(object): | |
19 def __init__(self, line, address, binary): | |
20 self.raw_line_ = line | |
21 self.address = address | |
22 self.binary = binary | |
23 def __str__(self): | |
24 return self.raw_line_ | |
25 | 20 |
26 class DrMemoryAnalyze: | 21 class DrMemoryAnalyzer: |
27 ''' Given a set of Dr.Memory output files, parse all the errors out of | 22 ''' Given a set of Dr.Memory output files, parse all the errors out of |
28 them, unique them and output the results.''' | 23 them, unique them and output the results.''' |
29 | 24 |
30 def __init__(self, source_dir, files): | 25 def __init__(self): |
31 '''Reads in a set of files. | 26 self.known_errors = set() |
32 | |
33 Args: | |
34 source_dir: Path to top of source tree for this build | |
35 files: A list of filenames. | |
36 ''' | |
37 | |
38 self.reports = [] | |
39 self.used_suppressions = [] | |
40 for file in files: | |
41 self.ParseReportFile(file) | |
42 | 27 |
43 def ReadLine(self): | 28 def ReadLine(self): |
44 self.line_ = self.cur_fd_.readline() | 29 self.line_ = self.cur_fd_.readline() |
45 | 30 |
46 def ReadSection(self): | 31 def ReadSection(self): |
47 result = [self.line_] | 32 result = [self.line_] |
48 self.ReadLine() | 33 self.ReadLine() |
49 while len(self.line_.strip()) > 0: | 34 while len(self.line_.strip()) > 0: |
50 result.append(self.line_) | 35 result.append(self.line_) |
51 self.ReadLine() | 36 self.ReadLine() |
52 return result | 37 return result |
53 | 38 |
54 def ParseReportFile(self, filename): | 39 def ParseReportFile(self, filename): |
| 40 ret = [] |
| 41 |
55 self.cur_fd_ = open(filename, 'r') | 42 self.cur_fd_ = open(filename, 'r') |
56 | |
57 while True: | 43 while True: |
58 self.ReadLine() | 44 self.ReadLine() |
59 if (self.line_ == ''): break | 45 if (self.line_ == ''): break |
60 | 46 |
61 match = re.search("^Error #[0-9]+: (.*)", self.line_) | 47 match = re.search("^Error #[0-9]+: (.*)", self.line_) |
62 if match: | 48 if match: |
63 self.line_ = match.groups()[0].strip() + "\n" | 49 self.line_ = match.groups()[0].strip() + "\n" |
64 tmp = self.ReadSection() | 50 tmp = self.ReadSection() |
65 self.reports.append(tmp) | 51 ret.append("".join(tmp).strip()) |
66 | 52 |
67 if re.search("SUPPRESSIONS USED:", self.line_): | 53 if re.search("SUPPRESSIONS USED:", self.line_): |
68 self.ReadLine() | 54 self.ReadLine() |
69 while self.line_.strip() != "": | 55 while self.line_.strip() != "": |
70 line = self.line_.strip() | 56 line = self.line_.strip() |
71 (count, name) = re.match(" *([0-9]+)x(?: \(leaked .*\))?: (.*)", | 57 (count, name) = re.match(" *([0-9]+)x(?: \(leaked .*\))?: (.*)", |
72 line).groups() | 58 line).groups() |
73 self.used_suppressions.append("%7s %s" % (count, name)) | 59 count = int(count) |
| 60 self.used_suppressions[name] += count |
74 self.ReadLine() | 61 self.ReadLine() |
75 | 62 |
76 if self.line_.startswith("ASSERT FAILURE"): | 63 if self.line_.startswith("ASSERT FAILURE"): |
77 self.reports.append(self.line_.strip()) | 64 ret.append(self.line_.strip()) |
78 | 65 |
79 self.cur_fd_.close() | 66 self.cur_fd_.close() |
| 67 return ret |
80 | 68 |
81 def Report(self, check_sanity): | 69 def Report(self, filenames, testcase, check_sanity): |
82 sys.stdout.flush() | 70 sys.stdout.flush() |
83 #TODO(timurrrr): support positive tests / check_sanity==True | 71 # TODO(timurrrr): support positive tests / check_sanity==True |
84 | 72 |
85 if self.used_suppressions: | 73 to_report = [] |
86 print "-----------------------------------------------------" | 74 self.used_suppressions = defaultdict(int) |
87 # TODO(timurrrr): sum up the counts from different wrappers (e.g. ui_tests
) | 75 for f in filenames: |
88 # or does it work now already? Or add the memcheck-like per-test printing. | 76 cur_reports = self.ParseReportFile(f) |
89 print "Suppressions used:\n count name\n%s" % ( | |
90 "\n".join(self.used_suppressions)) | |
91 print "-----------------------------------------------------" | |
92 sys.stdout.flush() | |
93 | 77 |
94 if len(self.reports) > 0: | 78 # Filter out the reports that were there in previous tests. |
95 logging.error("Found %i error reports" % len(self.reports)) | 79 for r in cur_reports: |
96 for report_list in self.reports: | 80 if r in self.known_errors: |
97 report = '' | 81 pass # TODO: print out a hash once we add hashes to the reports. |
98 for line in report_list: | 82 else: |
99 report += str(line) | 83 self.known_errors.add(r) |
100 logging.error('\n' + report) | 84 to_report.append(r) |
101 logging.error("Total: %i error reports" % len(self.reports)) | 85 |
102 return -1 | 86 common.PrintUsedSuppressionsList(self.used_suppressions) |
103 logging.info("PASS: No error reports found") | 87 |
104 return 0 | 88 if not to_report: |
| 89 logging.info("PASS: No error reports found") |
| 90 return 0 |
| 91 |
| 92 logging.error("Found %i error reports" % len(to_report)) |
| 93 for report in to_report: |
| 94 if testcase: |
| 95 logging.error("\n%s\nNote: observed on `%s`\n" % |
| 96 (report, testcase)) |
| 97 else: |
| 98 logging.error("\n%s\n" % report) |
| 99 logging.error("Total: %i error reports" % len(to_report)) |
| 100 return -1 |
105 | 101 |
106 if __name__ == '__main__': | 102 if __name__ == '__main__': |
107 '''For testing only. The DrMemoryAnalyze class should be imported instead.''' | 103 '''For testing only. The DrMemoryAnalyzer class should be imported instead.''' |
108 retcode = 0 | 104 retcode = 0 |
109 parser = optparse.OptionParser("usage: %prog [options] <files to analyze>") | 105 parser = optparse.OptionParser("usage: %prog <files to analyze>") |
110 parser.add_option("", "--source_dir", | |
111 help="path to top of source tree for this build" | |
112 "(used to normalize source paths in baseline)") | |
113 | |
114 (options, args) = parser.parse_args() | 106 (options, args) = parser.parse_args() |
115 if len(args) == 0: | 107 if len(args) == 0: |
116 parser.error("no filename specified") | 108 parser.error("no filename specified") |
117 filenames = args | 109 filenames = args |
118 | 110 |
119 analyzer = DrMemoryAnalyze(options.source_dir, filenames) | 111 analyzer = DrMemoryAnalyzer() |
120 retcode = analyzer.Report(False) | 112 retcode = analyzer.Report(filenames, None, False) |
121 | 113 |
122 sys.exit(retcode) | 114 sys.exit(retcode) |
OLD | NEW |