Chromium Code Reviews| 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 """Extract UserMetrics "actions" strings from the Chrome source. | 6 """Extract UserMetrics "actions" strings from the Chrome source. |
| 7 | 7 |
| 8 This program generates the list of known actions we expect to see in the | 8 This program generates the list of known actions we expect to see in the |
| 9 user behavior logs. It walks the Chrome source, looking for calls to | 9 user behavior logs. It walks the Chrome source, looking for calls to |
| 10 UserMetrics functions, extracting actions and warning on improper calls, | 10 UserMetrics functions, extracting actions and warning on improper calls, |
| 11 as well as generating the lists of possible actions in situations where | 11 as well as generating the lists of possible actions in situations where |
| 12 there are many possible actions. | 12 there are many possible actions. |
| 13 | 13 |
| 14 See also: | 14 See also: |
| 15 chrome/browser/user_metrics.h | 15 chrome/browser/user_metrics.h |
| 16 http://wiki.corp.google.com/twiki/bin/view/Main/ChromeUserExperienceMetrics | 16 http://wiki.corp.google.com/twiki/bin/view/Main/ChromeUserExperienceMetrics |
| 17 | 17 |
| 18 If run with a "--hash" argument, chromeactions.txt will be updated. | 18 If run with a "--hash" argument, chromeactions.txt will be updated. |
| 19 """ | 19 """ |
| 20 | 20 |
| 21 __author__ = 'evanm (Evan Martin)' | 21 __author__ = 'evanm (Evan Martin)' |
| 22 | 22 |
| 23 import hashlib | |
| 24 from HTMLParser import HTMLParser | |
| 23 import os | 25 import os |
| 24 import re | 26 import re |
| 25 import sys | 27 import sys |
| 26 import hashlib | |
| 27 | 28 |
| 28 sys.path.insert(1, os.path.join(sys.path[0], '..', '..', 'tools', 'python')) | 29 sys.path.insert(1, os.path.join(sys.path[0], '..', '..', 'tools', 'python')) |
| 29 from google import path_utils | 30 from google import path_utils |
| 30 | 31 |
| 31 # Files that are known to use UserMetrics::RecordComputedAction(), which means | 32 # Files that are known to use UserMetrics::RecordComputedAction(), which means |
| 32 # they require special handling code in this script. | 33 # they require special handling code in this script. |
| 33 # To add a new file, add it to this list and add the appropriate logic to | 34 # To add a new file, add it to this list and add the appropriate logic to |
| 34 # generate the known actions to AddComputedActions() below. | 35 # generate the known actions to AddComputedActions() below. |
| 35 KNOWN_COMPUTED_USERS = ( | 36 KNOWN_COMPUTED_USERS = ( |
| 36 'back_forward_menu_model.cc', | 37 'back_forward_menu_model.cc', |
| 37 'options_page_view.cc', | 38 'options_page_view.cc', |
| 38 'render_view_host.cc', # called using webkit identifiers | 39 'render_view_host.cc', # called using webkit identifiers |
| 39 'user_metrics.cc', # method definition | 40 'user_metrics.cc', # method definition |
| 40 'new_tab_ui.cc', # most visited clicks 1-9 | 41 'new_tab_ui.cc', # most visited clicks 1-9 |
| 41 'extension_metrics_module.cc', # extensions hook for user metrics | 42 'extension_metrics_module.cc', # extensions hook for user metrics |
| 42 'safe_browsing_blocking_page.cc', # various interstitial types and actions | 43 'safe_browsing_blocking_page.cc', # various interstitial types and actions |
| 43 'language_options_handler.cc', # languages and input methods in chrome os | 44 'language_options_handler_common.cc', # languages and input methods in CrOS |
| 45 'cros_language_options_handler.cc', # languages and input methods in CrOS | |
| 44 'about_flags.cc', # do not generate a warning; see AddAboutFlagsActions() | 46 'about_flags.cc', # do not generate a warning; see AddAboutFlagsActions() |
| 47 'external_metrics.cc', # see AddChromeOSActions() | |
| 48 'core_options_handler.cc' # see AddWebUIActions() | |
| 45 ) | 49 ) |
| 46 | 50 |
| 47 # Language codes used in Chrome. The list should be updated when a new | 51 # Language codes used in Chrome. The list should be updated when a new |
| 48 # language is added to app/l10n_util.cc, as follows: | 52 # language is added to app/l10n_util.cc, as follows: |
| 49 # | 53 # |
| 50 # % (cat app/l10n_util.cc | \ | 54 # % (cat app/l10n_util.cc | \ |
| 51 # perl -n0e 'print $1 if /kAcceptLanguageList.*?\{(.*?)\}/s' | \ | 55 # perl -n0e 'print $1 if /kAcceptLanguageList.*?\{(.*?)\}/s' | \ |
| 52 # perl -nle 'print $1, if /"(.*)"/'; echo 'es-419') | \ | 56 # perl -nle 'print $1, if /"(.*)"/'; echo 'es-419') | \ |
| 53 # sort | perl -pe "s/(.*)\n/'\$1', /" | \ | 57 # sort | perl -pe "s/(.*)\n/'\$1', /" | \ |
| 54 # fold -w75 -s | perl -pe 's/^/ /;s/ $//'; echo | 58 # fold -w75 -s | perl -pe 's/^/ /;s/ $//'; echo |
| (...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 215 | 219 |
| 216 Arguments: | 220 Arguments: |
| 217 path: path to the file | 221 path: path to the file |
| 218 actions: set of actions to add to | 222 actions: set of actions to add to |
| 219 """ | 223 """ |
| 220 global number_of_files_total | 224 global number_of_files_total |
| 221 number_of_files_total = number_of_files_total + 1 | 225 number_of_files_total = number_of_files_total + 1 |
| 222 # we look for the UserMetricsAction structure constructor | 226 # we look for the UserMetricsAction structure constructor |
| 223 # this should be on one line | 227 # this should be on one line |
| 224 action_re = re.compile(r'UserMetricsAction\("([^"]*)') | 228 action_re = re.compile(r'UserMetricsAction\("([^"]*)') |
| 229 malformed_action_re = re.compile(r'[^a-zA-Z]UserMetricsAction\([^"]') | |
|
jar (doing other things)
2011/08/01 17:26:45
I like your definition better than the one in 228.
Ilya Sherman
2011/08/06 03:23:52
There are two differences between |action_re| and
| |
| 225 computed_action_re = re.compile(r'UserMetrics::RecordComputedAction') | 230 computed_action_re = re.compile(r'UserMetrics::RecordComputedAction') |
| 226 line_number = 0 | 231 line_number = 0 |
| 227 for line in open(path): | 232 for line in open(path): |
| 228 line_number = line_number + 1 | 233 line_number = line_number + 1 |
| 229 match = action_re.search(line) | 234 match = action_re.search(line) |
| 230 if match: # Plain call to RecordAction | 235 if match: # Plain call to RecordAction |
| 231 actions.add(match.group(1)) | 236 actions.add(match.group(1)) |
| 237 elif malformed_action_re.search(line): | |
| 238 # Warn if this line is using RecordAction incorrectly. | |
| 239 print >>sys.stderr, ('WARNING: %s has malformed call to RecordAction' | |
| 240 ' at %d' % (path, line_number)) | |
| 232 elif computed_action_re.search(line): | 241 elif computed_action_re.search(line): |
| 233 # Warn if this file shouldn't be calling RecordComputedAction. | 242 # Warn if this file shouldn't be calling RecordComputedAction. |
| 234 if os.path.basename(path) not in KNOWN_COMPUTED_USERS: | 243 if os.path.basename(path) not in KNOWN_COMPUTED_USERS: |
| 235 print >>sys.stderr, 'WARNING: {0} has RecordComputedAction at {1}'.\ | 244 print >>sys.stderr, ('WARNING: %s has RecordComputedAction at %d' % |
| 236 format(path, line_number) | 245 (path, line_number)) |
| 237 | 246 |
| 238 def WalkDirectory(root_path, actions): | 247 class WebUIActionsParser(HTMLParser): |
| 248 """Parses an HTML file, looking for all tags with a 'metric' attribute. | |
| 249 Adds user actions corresponding to any metrics found. | |
| 250 | |
| 251 Arguments: | |
| 252 actions: set of actions to add to | |
| 253 """ | |
| 254 def __init__(self, actions): | |
| 255 HTMLParser.__init__(self) | |
| 256 self.actions = actions | |
| 257 | |
| 258 def handle_starttag(self, tag, attrs): | |
| 259 # We only care to examine tags that have a 'metric' attribute. | |
| 260 attrs = dict(attrs) | |
| 261 if not 'metric' in attrs: | |
| 262 return | |
| 263 | |
| 264 # Boolean metrics have two corresponding actions. All other metrics have | |
| 265 # just one corresponding action. By default, we check the 'dataType' | |
| 266 # attribute. | |
| 267 is_boolean = ('dataType' in attrs and attrs['dataType'] == 'boolean') | |
| 268 if 'type' in attrs and attrs['type'] in ('checkbox', 'radio'): | |
| 269 if attrs['type'] == 'checkbox': | |
| 270 # Checkboxes are boolean by default. However, their 'value-type' can | |
| 271 # instead be set to 'integer'. | |
| 272 if 'value-type' not in attrs or attrs['value-type'] in ['', 'boolean']: | |
| 273 is_boolean = True | |
| 274 else: | |
| 275 # Radio buttons are boolean if and only if their values are 'true' or | |
| 276 # 'false'. | |
| 277 assert(attrs['type'] == 'radio') | |
| 278 if 'value' in attrs and attrs['value'] in ['true', 'false']: | |
| 279 is_boolean = True | |
| 280 | |
| 281 if is_boolean: | |
| 282 self.actions.add(attrs['metric'] + '_Enable') | |
| 283 self.actions.add(attrs['metric'] + '_Disable') | |
| 284 else: | |
| 285 self.actions.add(attrs['metric']) | |
| 286 | |
| 287 def GrepForWebUIActions(path, actions): | |
| 288 """Grep a WebUI source file for elements with associated metrics. | |
| 289 | |
| 290 Arguments: | |
| 291 path: path to the file | |
| 292 actions: set of actions to add to | |
| 293 """ | |
| 294 parser = WebUIActionsParser(actions) | |
| 295 parser.feed(open(path).read()) | |
| 296 parser.close() | |
| 297 | |
| 298 def WalkDirectory(root_path, actions, extensions, callback): | |
| 239 for path, dirs, files in os.walk(root_path): | 299 for path, dirs, files in os.walk(root_path): |
| 240 if '.svn' in dirs: | 300 if '.svn' in dirs: |
| 241 dirs.remove('.svn') | 301 dirs.remove('.svn') |
| 242 for file in files: | 302 for file in files: |
| 243 ext = os.path.splitext(file)[1] | 303 ext = os.path.splitext(file)[1] |
| 244 if ext in ('.cc', '.mm', '.c', '.m'): | 304 if ext in extensions: |
| 245 GrepForActions(os.path.join(path, file), actions) | 305 callback(os.path.join(path, file), actions) |
| 306 | |
| 307 def AddLiteralActions(actions): | |
| 308 """Add literal actions specified via calls to UserMetrics functions. | |
| 309 | |
| 310 Arguments: | |
| 311 actions: set of actions to add to. | |
| 312 """ | |
| 313 EXTENSIONS = ('.cc', '.mm', '.c', '.m') | |
| 314 | |
| 315 # Walk the source tree to process all .cc files. | |
| 316 chrome_root = os.path.join(path_utils.ScriptDir(), '..') | |
| 317 WalkDirectory(chrome_root, actions, EXTENSIONS, GrepForActions) | |
| 318 content_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'content') | |
| 319 WalkDirectory(content_root, actions, EXTENSIONS, GrepForActions) | |
| 320 webkit_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'webkit') | |
| 321 WalkDirectory(os.path.join(webkit_root, 'glue'), actions, EXTENSIONS, | |
| 322 GrepForActions) | |
| 323 WalkDirectory(os.path.join(webkit_root, 'port'), actions, EXTENSIONS, | |
| 324 GrepForActions) | |
| 325 | |
| 326 def AddWebUIActions(actions): | |
| 327 """Add user actions defined in WebUI files. | |
| 328 | |
| 329 Arguments: | |
| 330 actions: set of actions to add to. | |
| 331 """ | |
| 332 resources_root = os.path.join(path_utils.ScriptDir(), '..', 'browser', | |
| 333 'resources') | |
| 334 WalkDirectory(resources_root, actions, ('.html'), GrepForWebUIActions) | |
| 246 | 335 |
| 247 def main(argv): | 336 def main(argv): |
| 248 if '--hash' in argv: | 337 if '--hash' in argv: |
| 249 hash_output = True | 338 hash_output = True |
| 250 else: | 339 else: |
| 251 hash_output = False | 340 hash_output = False |
| 252 print >>sys.stderr, "WARNING: If you added new UMA tags, you must" + \ | 341 print >>sys.stderr, "WARNING: If you added new UMA tags, you must" + \ |
| 253 " use the --hash option to update chromeactions.txt." | 342 " use the --hash option to update chromeactions.txt." |
| 254 # if we do a hash output, we want to only append NEW actions, and we know | 343 # if we do a hash output, we want to only append NEW actions, and we know |
| 255 # the file we want to work on | 344 # the file we want to work on |
| 256 actions = set() | 345 actions = set() |
| 257 | 346 |
| 258 chromeactions_path = os.path.join(path_utils.ScriptDir(), "chromeactions.txt") | 347 chromeactions_path = os.path.join(path_utils.ScriptDir(), "chromeactions.txt") |
| 259 | 348 |
| 260 if hash_output: | 349 if hash_output: |
| 261 f = open(chromeactions_path) | 350 f = open(chromeactions_path) |
| 262 for line in f: | 351 for line in f: |
| 263 part = line.rpartition("\t") | 352 part = line.rpartition("\t") |
| 264 part = part[2].strip() | 353 part = part[2].strip() |
| 265 actions.add(part) | 354 actions.add(part) |
| 266 f.close() | 355 f.close() |
| 267 | 356 |
| 268 | 357 |
| 269 AddComputedActions(actions) | 358 AddComputedActions(actions) |
| 270 # TODO(fmantek): bring back webkit editor actions. | 359 # TODO(fmantek): bring back webkit editor actions. |
| 271 # AddWebKitEditorActions(actions) | 360 # AddWebKitEditorActions(actions) |
| 272 AddAboutFlagsActions(actions) | 361 AddAboutFlagsActions(actions) |
| 362 AddWebUIActions(actions) | |
| 273 | 363 |
| 274 # Walk the source tree to process all .cc files. | 364 AddLiteralActions(actions) |
| 275 chrome_root = os.path.join(path_utils.ScriptDir(), '..') | |
| 276 WalkDirectory(chrome_root, actions) | |
| 277 content_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'content') | |
| 278 WalkDirectory(content_root, actions) | |
| 279 webkit_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'webkit') | |
| 280 WalkDirectory(os.path.join(webkit_root, 'glue'), actions) | |
| 281 WalkDirectory(os.path.join(webkit_root, 'port'), actions) | |
| 282 | 365 |
| 283 # print "Scanned {0} number of files".format(number_of_files_total) | 366 # print "Scanned {0} number of files".format(number_of_files_total) |
| 284 # print "Found {0} entries".format(len(actions)) | 367 # print "Found {0} entries".format(len(actions)) |
| 285 | 368 |
| 286 AddClosedSourceActions(actions) | 369 AddClosedSourceActions(actions) |
| 287 AddChromeOSActions(actions) | 370 AddChromeOSActions(actions) |
| 288 | 371 |
| 289 if hash_output: | 372 if hash_output: |
| 290 f = open(chromeactions_path, "w") | 373 f = open(chromeactions_path, "w") |
| 291 | 374 |
| 292 | 375 |
| 293 # Print out the actions as a sorted list. | 376 # Print out the actions as a sorted list. |
| 294 for action in sorted(actions): | 377 for action in sorted(actions): |
| 295 if hash_output: | 378 if hash_output: |
| 296 hash = hashlib.md5() | 379 hash = hashlib.md5() |
| 297 hash.update(action) | 380 hash.update(action) |
| 298 print >>f, '0x%s\t%s' % (hash.hexdigest()[:16], action) | 381 print >>f, '0x%s\t%s' % (hash.hexdigest()[:16], action) |
| 299 else: | 382 else: |
| 300 print action | 383 print action |
| 301 | 384 |
| 302 if hash_output: | 385 if hash_output: |
| 303 print "Done. Do not forget to add chromeactions.txt to your changelist" | 386 print "Done. Do not forget to add chromeactions.txt to your changelist" |
| 304 | 387 |
| 305 if '__main__' == __name__: | 388 if '__main__' == __name__: |
| 306 main(sys.argv) | 389 main(sys.argv) |
| OLD | NEW |