Chromium Code Reviews| 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 |