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