OLD | NEW |
1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """Scans the Chromium source for histograms that are absent from histograms.xml. | 5 """Scans the Chromium source for histograms that are absent from histograms.xml. |
6 | 6 |
7 This is a heuristic scan, so a clean run of this script does not guarantee that | 7 This is a heuristic scan, so a clean run of this script does not guarantee that |
8 all histograms in the Chromium source are properly mapped. Notably, field | 8 all histograms in the Chromium source are properly mapped. Notably, field |
9 trials are entirely ignored by this script. | 9 trials are entirely ignored by this script. |
10 | 10 |
11 """ | 11 """ |
12 | 12 |
13 import commands | |
14 import extract_histograms | |
15 import hashlib | 13 import hashlib |
16 import logging | 14 import logging |
17 import optparse | 15 import optparse |
18 import os | 16 import os |
19 import re | 17 import re |
| 18 import subprocess |
20 import sys | 19 import sys |
21 | 20 |
| 21 sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common')) |
| 22 import path_util |
| 23 |
| 24 import extract_histograms |
| 25 |
22 | 26 |
23 ADJACENT_C_STRING_REGEX = re.compile(r""" | 27 ADJACENT_C_STRING_REGEX = re.compile(r""" |
24 (" # Opening quotation mark | 28 (" # Opening quotation mark |
25 [^"]*) # Literal string contents | 29 [^"]*) # Literal string contents |
26 " # Closing quotation mark | 30 " # Closing quotation mark |
27 \s* # Any number of spaces | 31 \s* # Any number of spaces |
28 " # Another opening quotation mark | 32 " # Another opening quotation mark |
29 """, re.VERBOSE) | 33 """, re.VERBOSE) |
30 CONSTANT_REGEX = re.compile(r""" | 34 CONSTANT_REGEX = re.compile(r""" |
31 (\w*::)? # Optional namespace | 35 (\w*::)? # Optional namespace |
32 k[A-Z] # Match a constant identifier: 'k' followed by an uppercase letter | 36 k[A-Z] # Match a constant identifier: 'k' followed by an uppercase letter |
33 \w* # Match the rest of the constant identifier | 37 \w* # Match the rest of the constant identifier |
34 $ # Make sure there's only the identifier, nothing else | 38 $ # Make sure there's only the identifier, nothing else |
35 """, re.VERBOSE) | 39 """, re.VERBOSE) |
36 HISTOGRAM_REGEX = re.compile(r""" | 40 HISTOGRAM_REGEX = re.compile(r""" |
37 UMA_HISTOGRAM # Match the shared prefix for standard UMA histogram macros | 41 UMA_HISTOGRAM # Match the shared prefix for standard UMA histogram macros |
38 \w* # Match the rest of the macro name, e.g. '_ENUMERATION' | 42 \w* # Match the rest of the macro name, e.g. '_ENUMERATION' |
39 \( # Match the opening parenthesis for the macro | 43 \( # Match the opening parenthesis for the macro |
40 \s* # Match any whitespace -- especially, any newlines | 44 \s* # Match any whitespace -- especially, any newlines |
41 ([^,)]*) # Capture the first parameter to the macro | 45 ([^,)]*) # Capture the first parameter to the macro |
42 [,)] # Match the comma/paren that delineates the first parameter | 46 [,)] # Match the comma/paren that delineates the first parameter |
43 """, re.VERBOSE) | 47 """, re.VERBOSE) |
44 | 48 |
45 | 49 |
| 50 def RunGit(command): |
| 51 """Run a git subcommand, returning its output.""" |
| 52 # On Windows, use shell=True to get PATH interpretation. |
| 53 command = ['git'] + command |
| 54 logging.info(' '.join(command)) |
| 55 shell = (os.name == 'nt') |
| 56 proc = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE) |
| 57 out = proc.communicate()[0].strip() |
| 58 return out |
| 59 |
| 60 |
46 class DirectoryNotFoundException(Exception): | 61 class DirectoryNotFoundException(Exception): |
47 """Base class to distinguish locally defined exceptions from standard ones.""" | 62 """Base class to distinguish locally defined exceptions from standard ones.""" |
48 def __init__(self, msg): | 63 def __init__(self, msg): |
49 self.msg = msg | 64 self.msg = msg |
50 | 65 |
51 def __str__(self): | 66 def __str__(self): |
52 return self.msg | 67 return self.msg |
53 | 68 |
54 | 69 |
55 def findDefaultRoot(): | |
56 """Find the root of the chromium repo, in case the script is run from the | |
57 histograms dir. | |
58 | |
59 Returns: | |
60 string: path to the src dir of the repo. | |
61 | |
62 Raises: | |
63 DirectoryNotFoundException if the target directory cannot be found. | |
64 """ | |
65 path = os.getcwd() | |
66 while path: | |
67 head, tail = os.path.split(path) | |
68 if tail == 'src': | |
69 return path | |
70 if path == head: | |
71 break | |
72 path = head | |
73 raise DirectoryNotFoundException('Could not find src/ dir') | |
74 | |
75 | |
76 def collapseAdjacentCStrings(string): | 70 def collapseAdjacentCStrings(string): |
77 """Collapses any adjacent C strings into a single string. | 71 """Collapses any adjacent C strings into a single string. |
78 | 72 |
79 Useful to re-combine strings that were split across multiple lines to satisfy | 73 Useful to re-combine strings that were split across multiple lines to satisfy |
80 the 80-col restriction. | 74 the 80-col restriction. |
81 | 75 |
82 Args: | 76 Args: |
83 string: The string to recombine, e.g. '"Foo"\n "bar"' | 77 string: The string to recombine, e.g. '"Foo"\n "bar"' |
84 | 78 |
85 Returns: | 79 Returns: |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
123 histogram) | 117 histogram) |
124 | 118 |
125 | 119 |
126 def readChromiumHistograms(): | 120 def readChromiumHistograms(): |
127 """Searches the Chromium source for all histogram names. | 121 """Searches the Chromium source for all histogram names. |
128 | 122 |
129 Also prints warnings for any invocations of the UMA_HISTOGRAM_* macros with | 123 Also prints warnings for any invocations of the UMA_HISTOGRAM_* macros with |
130 names that might vary during a single run of the app. | 124 names that might vary during a single run of the app. |
131 | 125 |
132 Returns: | 126 Returns: |
133 A set cotaining any found literal histogram names. | 127 A set containing any found literal histogram names. |
134 """ | 128 """ |
135 logging.info('Scanning Chromium source for histograms...') | 129 logging.info('Scanning Chromium source for histograms...') |
136 | 130 |
137 # Use git grep to find all invocations of the UMA_HISTOGRAM_* macros. | 131 # Use git grep to find all invocations of the UMA_HISTOGRAM_* macros. |
138 # Examples: | 132 # Examples: |
139 # 'path/to/foo.cc:420: UMA_HISTOGRAM_COUNTS_100("FooGroup.FooName",' | 133 # 'path/to/foo.cc:420: UMA_HISTOGRAM_COUNTS_100("FooGroup.FooName",' |
140 # 'path/to/bar.cc:632: UMA_HISTOGRAM_ENUMERATION(' | 134 # 'path/to/bar.cc:632: UMA_HISTOGRAM_ENUMERATION(' |
141 locations = commands.getoutput('git gs UMA_HISTOGRAM').split('\n') | 135 locations = RunGit(['gs', 'UMA_HISTOGRAM']).split('\n') |
142 filenames = set([location.split(':')[0] for location in locations]) | 136 filenames = set([location.split(':')[0] for location in locations]) |
143 | 137 |
144 histograms = set() | 138 histograms = set() |
145 for filename in filenames: | 139 for filename in filenames: |
146 contents = '' | 140 contents = '' |
147 with open(filename, 'r') as f: | 141 with open(filename, 'r') as f: |
148 contents = f.read() | 142 contents = f.read() |
149 | 143 |
150 matches = set(HISTOGRAM_REGEX.findall(contents)) | 144 matches = set(HISTOGRAM_REGEX.findall(contents)) |
151 for histogram in matches: | 145 for histogram in matches: |
152 histogram = collapseAdjacentCStrings(histogram) | 146 histogram = collapseAdjacentCStrings(histogram) |
153 | 147 |
154 # Must begin and end with a quotation mark. | 148 # Must begin and end with a quotation mark. |
155 if histogram[0] != '"' or histogram[-1] != '"': | 149 if not histogram or histogram[0] != '"' or histogram[-1] != '"': |
156 logNonLiteralHistogram(filename, histogram) | 150 logNonLiteralHistogram(filename, histogram) |
157 continue | 151 continue |
158 | 152 |
159 # Must not include any quotation marks other than at the beginning or end. | 153 # Must not include any quotation marks other than at the beginning or end. |
160 histogram_stripped = histogram.strip('"') | 154 histogram_stripped = histogram.strip('"') |
161 if '"' in histogram_stripped: | 155 if '"' in histogram_stripped: |
162 logNonLiteralHistogram(filename, histogram) | 156 logNonLiteralHistogram(filename, histogram) |
163 continue | 157 continue |
164 | 158 |
165 histograms.add(histogram_stripped) | 159 histograms.add(histogram_stripped) |
(...skipping 19 matching lines...) Expand all Loading... |
185 name: The string to hash (a histogram name). | 179 name: The string to hash (a histogram name). |
186 | 180 |
187 Returns: | 181 Returns: |
188 Histogram hash as a string representing a hex number (with leading 0x). | 182 Histogram hash as a string representing a hex number (with leading 0x). |
189 """ | 183 """ |
190 return '0x' + hashlib.md5(name).hexdigest()[:16] | 184 return '0x' + hashlib.md5(name).hexdigest()[:16] |
191 | 185 |
192 | 186 |
193 def main(): | 187 def main(): |
194 # Find default paths. | 188 # Find default paths. |
195 default_root = findDefaultRoot() | 189 default_root = path_util.GetInputFile('/') |
196 default_histograms_path = os.path.join( | 190 default_histograms_path = path_util.GetInputFile( |
197 default_root, 'tools/metrics/histograms/histograms.xml') | 191 'tools/metrics/histograms/histograms.xml') |
198 default_extra_histograms_path = os.path.join( | 192 default_extra_histograms_path = path_util.GetInputFile( |
199 default_root, 'tools/histograms/histograms.xml') | 193 'tools/histograms/histograms.xml') |
200 | 194 |
201 # Parse command line options | 195 # Parse command line options |
202 parser = optparse.OptionParser() | 196 parser = optparse.OptionParser() |
203 parser.add_option( | 197 parser.add_option( |
204 '--root-directory', dest='root_directory', default=default_root, | 198 '--root-directory', dest='root_directory', default=default_root, |
205 help='scan within DIRECTORY for histograms [optional, defaults to "%s"]' % | 199 help='scan within DIRECTORY for histograms [optional, defaults to "%s"]' % |
206 default_root, | 200 default_root, |
207 metavar='DIRECTORY') | 201 metavar='DIRECTORY') |
208 parser.add_option( | 202 parser.add_option( |
209 '--histograms-file', dest='histograms_file_location', | 203 '--histograms-file', dest='histograms_file_location', |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
247 logging.info('Histograms in Chromium but not in XML files:') | 241 logging.info('Histograms in Chromium but not in XML files:') |
248 logging.info('-------------------------------------------------') | 242 logging.info('-------------------------------------------------') |
249 for histogram in sorted(unmapped_histograms): | 243 for histogram in sorted(unmapped_histograms): |
250 logging.info(' %s - %s', histogram, hashHistogramName(histogram)) | 244 logging.info(' %s - %s', histogram, hashHistogramName(histogram)) |
251 else: | 245 else: |
252 logging.info('Success! No unmapped histograms found.') | 246 logging.info('Success! No unmapped histograms found.') |
253 | 247 |
254 | 248 |
255 if __name__ == '__main__': | 249 if __name__ == '__main__': |
256 main() | 250 main() |
OLD | NEW |