Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # | 2 # |
| 3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
| 5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
| 6 | 6 |
| 7 """Extract UserMetrics "actions" strings from the Chrome source. | 7 """Extract UserMetrics "actions" strings from the Chrome source. |
| 8 | 8 |
| 9 This program generates the list of known actions we expect to see in the | 9 This program generates the list of known actions we expect to see in the |
| 10 user behavior logs. It walks the Chrome source, looking for calls to | 10 user behavior logs. It walks the Chrome source, looking for calls to |
| 11 UserMetrics functions, extracting actions and warning on improper calls, | 11 UserMetrics functions, extracting actions and warning on improper calls, |
| 12 as well as generating the lists of possible actions in situations where | 12 as well as generating the lists of possible actions in situations where |
| 13 there are many possible actions. | 13 there are many possible actions. |
| 14 | 14 |
| 15 See also: | 15 See also: |
| 16 base/metrics/user_metrics.h | 16 base/metrics/user_metrics.h |
| 17 http://wiki.corp.google.com/twiki/bin/view/Main/ChromeUserExperienceMetrics | |
| 18 | 17 |
| 19 After extracting all actions, the content will go through a pretty print | 18 After extracting all actions, the content will go through a pretty print |
| 20 function to make sure it's well formatted. If the file content needs to be | 19 function to make sure it's well formatted. If the file content needs to be |
| 21 changed, a window will be prompted asking for user's consent. The old version | 20 changed, a window will be prompted asking for user's consent. The old version |
| 22 will also be saved in a backup file. | 21 will also be saved in a backup file. |
| 23 """ | 22 """ |
| 24 | 23 |
| 25 __author__ = 'evanm (Evan Martin)' | 24 __author__ = 'evanm (Evan Martin)' |
| 26 | 25 |
| 27 from HTMLParser import HTMLParser | 26 from HTMLParser import HTMLParser |
| 28 import logging | 27 import logging |
| 29 import os | 28 import os |
| 30 import re | 29 import re |
| 31 import shutil | 30 import shutil |
| 32 import sys | 31 import sys |
| 33 from xml.dom import minidom | 32 from xml.dom import minidom |
| 34 | 33 |
| 35 import print_style | 34 import print_style |
| 36 | 35 |
| 37 sys.path.insert(1, os.path.join(sys.path[0], '..', '..', 'python')) | 36 sys.path.insert(1, os.path.join(sys.path[0], '..', '..', 'python')) |
| 38 from google import path_utils | 37 from google import path_utils |
| 39 | 38 |
| 40 # Import the metrics/common module for pretty print xml. | 39 # Import the metrics/common module for pretty print xml. |
| 41 sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common')) | 40 sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common')) |
| 42 import diff_util | 41 import diff_util |
| 43 import pretty_print_xml | 42 import pretty_print_xml |
| 44 | 43 |
| 44 USER_METRICS_ACTION_RE = re.compile(r""" | |
| 45 [^a-zA-Z] # Preceded by a non-alphabetical character. | |
| 46 UserMetricsAction # Name of the function. | |
| 47 \( # Opening parenthesis. | |
| 48 \s* # Any amount of whitespace, including new lines. | |
| 49 (.+?) # A sequence of characters for the param. | |
|
Ilya Sherman
2015/01/12 22:17:19
Ah, I see now that "+?" means non-greedy. Okay, t
Alexei Svitkine (slow)
2015/01/13 18:06:17
Right. It will also catch base::UserMetricsAction(
Ilya Sherman
2015/01/13 23:05:37
I guess this is where we disagree -- I would much
Alexei Svitkine (slow)
2015/01/14 18:32:05
OK, I caved in and changed the code to not support
| |
| 50 \) # Closing parenthesis. | |
| 51 """, re.VERBOSE) | |
| 52 COMPUTED_ACTION_RE = re.compile(r'RecordComputedAction') | |
| 53 QUOTED_STRING_RE = re.compile(r'\"(.+?)\"') | |
| 54 | |
| 45 # Files that are known to use content::RecordComputedAction(), which means | 55 # Files that are known to use content::RecordComputedAction(), which means |
| 46 # they require special handling code in this script. | 56 # they require special handling code in this script. |
| 47 # To add a new file, add it to this list and add the appropriate logic to | 57 # To add a new file, add it to this list and add the appropriate logic to |
| 48 # generate the known actions to AddComputedActions() below. | 58 # generate the known actions to AddComputedActions() below. |
| 49 KNOWN_COMPUTED_USERS = ( | 59 KNOWN_COMPUTED_USERS = ( |
| 50 'back_forward_menu_model.cc', | 60 'back_forward_menu_model.cc', |
| 51 'options_page_view.cc', | 61 'options_page_view.cc', |
| 52 'render_view_host.cc', # called using webkit identifiers | 62 'render_view_host.cc', # called using webkit identifiers |
| 53 'user_metrics.cc', # method definition | 63 'user_metrics.cc', # method definition |
| 54 'new_tab_ui.cc', # most visited clicks 1-9 | 64 'new_tab_ui.cc', # most visited clicks 1-9 |
| (...skipping 348 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 403 actions.add('ConnectivityDiagnostics.LaunchSource.WebStore') | 413 actions.add('ConnectivityDiagnostics.LaunchSource.WebStore') |
| 404 actions.add('ConnectivityDiagnostics.UA.LogsShown') | 414 actions.add('ConnectivityDiagnostics.UA.LogsShown') |
| 405 actions.add('ConnectivityDiagnostics.UA.PassingTestsShown') | 415 actions.add('ConnectivityDiagnostics.UA.PassingTestsShown') |
| 406 actions.add('ConnectivityDiagnostics.UA.SettingsShown') | 416 actions.add('ConnectivityDiagnostics.UA.SettingsShown') |
| 407 actions.add('ConnectivityDiagnostics.UA.TestResultExpanded') | 417 actions.add('ConnectivityDiagnostics.UA.TestResultExpanded') |
| 408 actions.add('ConnectivityDiagnostics.UA.TestSuiteRun') | 418 actions.add('ConnectivityDiagnostics.UA.TestSuiteRun') |
| 409 | 419 |
| 410 # Actions sent by 'Ok Google' Hotwording. | 420 # Actions sent by 'Ok Google' Hotwording. |
| 411 actions.add('Hotword.HotwordTrigger') | 421 actions.add('Hotword.HotwordTrigger') |
| 412 | 422 |
| 423 def FindActionNames(contents, pos): | |
| 424 """Finds actions from the first UserMetricsAction() call in |contents|. | |
| 425 | |
| 426 Arguments: | |
| 427 contents: string to search through | |
| 428 pos: position in |contents| to start the search from | |
| 429 | |
| 430 Returns: | |
| 431 A tuple consisting of: | |
| 432 - The list action names (strings) that was found. | |
|
Ilya Sherman
2015/01/12 22:17:19
nit: "list action names" -> "list of action names"
Alexei Svitkine (slow)
2015/01/13 18:06:17
Done.
| |
| 433 - The index in |content| indicating the end of the matching string. | |
| 434 """ | |
| 435 match = USER_METRICS_ACTION_RE.search(contents, pos=pos) | |
| 436 if not match: | |
| 437 return None, None | |
| 438 return QUOTED_STRING_RE.search(match.group(1)), match.end() | |
| 439 | |
| 413 def GrepForActions(path, actions): | 440 def GrepForActions(path, actions): |
| 414 """Grep a source file for calls to UserMetrics functions. | 441 """Grep a source file for calls to UserMetrics functions. |
| 415 | 442 |
| 416 Arguments: | 443 Arguments: |
| 417 path: path to the file | 444 path: path to the file |
| 418 actions: set of actions to add to | 445 actions: set of actions to add to |
| 419 """ | 446 """ |
| 420 global number_of_files_total | 447 global number_of_files_total |
| 421 number_of_files_total = number_of_files_total + 1 | 448 number_of_files_total = number_of_files_total + 1 |
| 422 # we look for the UserMetricsAction structure constructor | 449 |
| 423 # this should be on one line | 450 contents = open(path).read() |
| 424 action_re = re.compile(r'[^a-zA-Z]UserMetricsAction\("([^"]*)') | 451 pos = 0 |
| 425 malformed_action_re = re.compile(r'[^a-zA-Z]UserMetricsAction\([^"]') | 452 while True: |
| 426 computed_action_re = re.compile(r'RecordComputedAction') | 453 action_names, pos = FindActionNames(contents, pos) |
| 454 if not action_names: | |
| 455 break | |
| 456 actions.update(action_names) | |
| 457 | |
| 427 line_number = 0 | 458 line_number = 0 |
| 428 for line in open(path): | 459 for line in open(path): |
| 429 line_number = line_number + 1 | 460 line_number = line_number + 1 |
| 430 match = action_re.search(line) | 461 if COMPUTED_ACTION_RE.search(line): |
| 431 if match: # Plain call to RecordAction | |
| 432 actions.add(match.group(1)) | |
| 433 elif malformed_action_re.search(line): | |
| 434 # Warn if this line is using RecordAction incorrectly. | |
| 435 print >>sys.stderr, ('WARNING: %s has malformed call to RecordAction' | |
| 436 ' at %d' % (path, line_number)) | |
| 437 elif computed_action_re.search(line): | |
| 438 # Warn if this file shouldn't be calling RecordComputedAction. | 462 # Warn if this file shouldn't be calling RecordComputedAction. |
| 439 if os.path.basename(path) not in KNOWN_COMPUTED_USERS: | 463 if os.path.basename(path) not in KNOWN_COMPUTED_USERS: |
| 440 print >>sys.stderr, ('WARNING: %s has RecordComputedAction at %d' % | 464 print >>sys.stderr, ('WARNING: %s has RecordComputedAction at %d' % |
| 441 (path, line_number)) | 465 (path, line_number)) |
| 442 | 466 |
| 443 class WebUIActionsParser(HTMLParser): | 467 class WebUIActionsParser(HTMLParser): |
| 444 """Parses an HTML file, looking for all tags with a 'metric' attribute. | 468 """Parses an HTML file, looking for all tags with a 'metric' attribute. |
| 445 Adds user actions corresponding to any metrics found. | 469 Adds user actions corresponding to any metrics found. |
| 446 | 470 |
| 447 Arguments: | 471 Arguments: |
| (...skipping 366 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 814 | 838 |
| 815 with open(actions_xml_path, 'wb') as f: | 839 with open(actions_xml_path, 'wb') as f: |
| 816 f.write(pretty) | 840 f.write(pretty) |
| 817 print ('Updated %s. Don\'t forget to add it to your changelist' % | 841 print ('Updated %s. Don\'t forget to add it to your changelist' % |
| 818 actions_xml_path) | 842 actions_xml_path) |
| 819 return 0 | 843 return 0 |
| 820 | 844 |
| 821 | 845 |
| 822 if '__main__' == __name__: | 846 if '__main__' == __name__: |
| 823 sys.exit(main(sys.argv)) | 847 sys.exit(main(sys.argv)) |
| OLD | NEW |