| 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 """Grep a source file for calls to UserMetrics functions. | 219 """Grep a source file for calls to UserMetrics functions. |
| 216 | 220 |
| 217 Arguments: | 221 Arguments: |
| 218 path: path to the file | 222 path: path to the file |
| 219 actions: set of actions to add to | 223 actions: set of actions to add to |
| 220 """ | 224 """ |
| 221 global number_of_files_total | 225 global number_of_files_total |
| 222 number_of_files_total = number_of_files_total + 1 | 226 number_of_files_total = number_of_files_total + 1 |
| 223 # we look for the UserMetricsAction structure constructor | 227 # we look for the UserMetricsAction structure constructor |
| 224 # this should be on one line | 228 # this should be on one line |
| 225 action_re = re.compile(r'UserMetricsAction\("([^"]*)') | 229 action_re = re.compile(r'[^a-zA-Z]UserMetricsAction\("([^"]*)') |
| 230 malformed_action_re = re.compile(r'[^a-zA-Z]UserMetricsAction\([^"]') |
| 226 computed_action_re = re.compile(r'UserMetrics::RecordComputedAction') | 231 computed_action_re = re.compile(r'UserMetrics::RecordComputedAction') |
| 227 line_number = 0 | 232 line_number = 0 |
| 228 for line in open(path): | 233 for line in open(path): |
| 229 line_number = line_number + 1 | 234 line_number = line_number + 1 |
| 230 match = action_re.search(line) | 235 match = action_re.search(line) |
| 231 if match: # Plain call to RecordAction | 236 if match: # Plain call to RecordAction |
| 232 actions.add(match.group(1)) | 237 actions.add(match.group(1)) |
| 238 elif malformed_action_re.search(line): |
| 239 # Warn if this line is using RecordAction incorrectly. |
| 240 print >>sys.stderr, ('WARNING: %s has malformed call to RecordAction' |
| 241 ' at %d' % (path, line_number)) |
| 233 elif computed_action_re.search(line): | 242 elif computed_action_re.search(line): |
| 234 # Warn if this file shouldn't be calling RecordComputedAction. | 243 # Warn if this file shouldn't be calling RecordComputedAction. |
| 235 if os.path.basename(path) not in KNOWN_COMPUTED_USERS: | 244 if os.path.basename(path) not in KNOWN_COMPUTED_USERS: |
| 236 print >>sys.stderr, 'WARNING: {0} has RecordComputedAction at {1}'.\ | 245 print >>sys.stderr, ('WARNING: %s has RecordComputedAction at %d' % |
| 237 format(path, line_number) | 246 (path, line_number)) |
| 238 | 247 |
| 239 def WalkDirectory(root_path, actions): | 248 class WebUIActionsParser(HTMLParser): |
| 249 """Parses an HTML file, looking for all tags with a 'metric' attribute. |
| 250 Adds user actions corresponding to any metrics found. |
| 251 |
| 252 Arguments: |
| 253 actions: set of actions to add to |
| 254 """ |
| 255 def __init__(self, actions): |
| 256 HTMLParser.__init__(self) |
| 257 self.actions = actions |
| 258 |
| 259 def handle_starttag(self, tag, attrs): |
| 260 # We only care to examine tags that have a 'metric' attribute. |
| 261 attrs = dict(attrs) |
| 262 if not 'metric' in attrs: |
| 263 return |
| 264 |
| 265 # Boolean metrics have two corresponding actions. All other metrics have |
| 266 # just one corresponding action. By default, we check the 'dataType' |
| 267 # attribute. |
| 268 is_boolean = ('dataType' in attrs and attrs['dataType'] == 'boolean') |
| 269 if 'type' in attrs and attrs['type'] in ('checkbox', 'radio'): |
| 270 if attrs['type'] == 'checkbox': |
| 271 # Checkboxes are boolean by default. However, their 'value-type' can |
| 272 # instead be set to 'integer'. |
| 273 if 'value-type' not in attrs or attrs['value-type'] in ['', 'boolean']: |
| 274 is_boolean = True |
| 275 else: |
| 276 # Radio buttons are boolean if and only if their values are 'true' or |
| 277 # 'false'. |
| 278 assert(attrs['type'] == 'radio') |
| 279 if 'value' in attrs and attrs['value'] in ['true', 'false']: |
| 280 is_boolean = True |
| 281 |
| 282 if is_boolean: |
| 283 self.actions.add(attrs['metric'] + '_Enable') |
| 284 self.actions.add(attrs['metric'] + '_Disable') |
| 285 else: |
| 286 self.actions.add(attrs['metric']) |
| 287 |
| 288 def GrepForWebUIActions(path, actions): |
| 289 """Grep a WebUI source file for elements with associated metrics. |
| 290 |
| 291 Arguments: |
| 292 path: path to the file |
| 293 actions: set of actions to add to |
| 294 """ |
| 295 parser = WebUIActionsParser(actions) |
| 296 parser.feed(open(path).read()) |
| 297 parser.close() |
| 298 |
| 299 def WalkDirectory(root_path, actions, extensions, callback): |
| 240 for path, dirs, files in os.walk(root_path): | 300 for path, dirs, files in os.walk(root_path): |
| 241 if '.svn' in dirs: | 301 if '.svn' in dirs: |
| 242 dirs.remove('.svn') | 302 dirs.remove('.svn') |
| 243 for file in files: | 303 for file in files: |
| 244 ext = os.path.splitext(file)[1] | 304 ext = os.path.splitext(file)[1] |
| 245 if ext in ('.cc', '.mm', '.c', '.m'): | 305 if ext in extensions: |
| 246 GrepForActions(os.path.join(path, file), actions) | 306 callback(os.path.join(path, file), actions) |
| 307 |
| 308 def AddLiteralActions(actions): |
| 309 """Add literal actions specified via calls to UserMetrics functions. |
| 310 |
| 311 Arguments: |
| 312 actions: set of actions to add to. |
| 313 """ |
| 314 EXTENSIONS = ('.cc', '.mm', '.c', '.m') |
| 315 |
| 316 # Walk the source tree to process all .cc files. |
| 317 chrome_root = os.path.join(path_utils.ScriptDir(), '..') |
| 318 WalkDirectory(chrome_root, actions, EXTENSIONS, GrepForActions) |
| 319 content_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'content') |
| 320 WalkDirectory(content_root, actions, EXTENSIONS, GrepForActions) |
| 321 webkit_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'webkit') |
| 322 WalkDirectory(os.path.join(webkit_root, 'glue'), actions, EXTENSIONS, |
| 323 GrepForActions) |
| 324 WalkDirectory(os.path.join(webkit_root, 'port'), actions, EXTENSIONS, |
| 325 GrepForActions) |
| 326 |
| 327 def AddWebUIActions(actions): |
| 328 """Add user actions defined in WebUI files. |
| 329 |
| 330 Arguments: |
| 331 actions: set of actions to add to. |
| 332 """ |
| 333 resources_root = os.path.join(path_utils.ScriptDir(), '..', 'browser', |
| 334 'resources') |
| 335 WalkDirectory(resources_root, actions, ('.html'), GrepForWebUIActions) |
| 247 | 336 |
| 248 def main(argv): | 337 def main(argv): |
| 249 if '--hash' in argv: | 338 if '--hash' in argv: |
| 250 hash_output = True | 339 hash_output = True |
| 251 else: | 340 else: |
| 252 hash_output = False | 341 hash_output = False |
| 253 print >>sys.stderr, "WARNING: If you added new UMA tags, you must" + \ | 342 print >>sys.stderr, "WARNING: If you added new UMA tags, you must" + \ |
| 254 " use the --hash option to update chromeactions.txt." | 343 " use the --hash option to update chromeactions.txt." |
| 255 # if we do a hash output, we want to only append NEW actions, and we know | 344 # if we do a hash output, we want to only append NEW actions, and we know |
| 256 # the file we want to work on | 345 # the file we want to work on |
| 257 actions = set() | 346 actions = set() |
| 258 | 347 |
| 259 chromeactions_path = os.path.join(path_utils.ScriptDir(), "chromeactions.txt") | 348 chromeactions_path = os.path.join(path_utils.ScriptDir(), "chromeactions.txt") |
| 260 | 349 |
| 261 if hash_output: | 350 if hash_output: |
| 262 f = open(chromeactions_path) | 351 f = open(chromeactions_path) |
| 263 for line in f: | 352 for line in f: |
| 264 part = line.rpartition("\t") | 353 part = line.rpartition("\t") |
| 265 part = part[2].strip() | 354 part = part[2].strip() |
| 266 actions.add(part) | 355 actions.add(part) |
| 267 f.close() | 356 f.close() |
| 268 | 357 |
| 269 | 358 |
| 270 AddComputedActions(actions) | 359 AddComputedActions(actions) |
| 271 # TODO(fmantek): bring back webkit editor actions. | 360 # TODO(fmantek): bring back webkit editor actions. |
| 272 # AddWebKitEditorActions(actions) | 361 # AddWebKitEditorActions(actions) |
| 273 AddAboutFlagsActions(actions) | 362 AddAboutFlagsActions(actions) |
| 363 AddWebUIActions(actions) |
| 274 | 364 |
| 275 # Walk the source tree to process all .cc files. | 365 AddLiteralActions(actions) |
| 276 chrome_root = os.path.join(path_utils.ScriptDir(), '..') | |
| 277 WalkDirectory(chrome_root, actions) | |
| 278 content_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'content') | |
| 279 WalkDirectory(content_root, actions) | |
| 280 webkit_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'webkit') | |
| 281 WalkDirectory(os.path.join(webkit_root, 'glue'), actions) | |
| 282 WalkDirectory(os.path.join(webkit_root, 'port'), actions) | |
| 283 | 366 |
| 284 # print "Scanned {0} number of files".format(number_of_files_total) | 367 # print "Scanned {0} number of files".format(number_of_files_total) |
| 285 # print "Found {0} entries".format(len(actions)) | 368 # print "Found {0} entries".format(len(actions)) |
| 286 | 369 |
| 287 AddClosedSourceActions(actions) | 370 AddClosedSourceActions(actions) |
| 288 AddChromeOSActions(actions) | 371 AddChromeOSActions(actions) |
| 289 | 372 |
| 290 if hash_output: | 373 if hash_output: |
| 291 f = open(chromeactions_path, "w") | 374 f = open(chromeactions_path, "w") |
| 292 | 375 |
| 293 | 376 |
| 294 # Print out the actions as a sorted list. | 377 # Print out the actions as a sorted list. |
| 295 for action in sorted(actions): | 378 for action in sorted(actions): |
| 296 if hash_output: | 379 if hash_output: |
| 297 hash = hashlib.md5() | 380 hash = hashlib.md5() |
| 298 hash.update(action) | 381 hash.update(action) |
| 299 print >>f, '0x%s\t%s' % (hash.hexdigest()[:16], action) | 382 print >>f, '0x%s\t%s' % (hash.hexdigest()[:16], action) |
| 300 else: | 383 else: |
| 301 print action | 384 print action |
| 302 | 385 |
| 303 if hash_output: | 386 if hash_output: |
| 304 print "Done. Do not forget to add chromeactions.txt to your changelist" | 387 print "Done. Do not forget to add chromeactions.txt to your changelist" |
| 305 | 388 |
| 306 if '__main__' == __name__: | 389 if '__main__' == __name__: |
| 307 main(sys.argv) | 390 main(sys.argv) |
| OLD | NEW |