Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(30)

Side by Side Diff: tools/metrics/actions/extract_actions.py

Issue 848453002: Improve script to find user actions. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Address comments. Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | tools/metrics/actions/extract_actions_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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.
50 \) # Closing parenthesis.
51 """,
52 re.VERBOSE | re.DOTALL # Verbose syntax and makes . also match new lines.
Ilya Sherman 2015/01/14 19:15:20 Why does . need to match newlines? Is this behavi
Alexei Svitkine (slow) 2015/01/14 20:28:21 No, it's whether (.+?) matches newlines. Without
Ilya Sherman 2015/01/14 23:16:45 Ah, I see -- it's important for the better error m
53 )
54 COMPUTED_ACTION_RE = re.compile(r'RecordComputedAction')
55 QUOTED_STRING_RE = re.compile(r'\"(.+?)\"')
56
45 # Files that are known to use content::RecordComputedAction(), which means 57 # Files that are known to use content::RecordComputedAction(), which means
46 # they require special handling code in this script. 58 # 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 59 # To add a new file, add it to this list and add the appropriate logic to
48 # generate the known actions to AddComputedActions() below. 60 # generate the known actions to AddComputedActions() below.
49 KNOWN_COMPUTED_USERS = ( 61 KNOWN_COMPUTED_USERS = (
50 'back_forward_menu_model.cc', 62 'back_forward_menu_model.cc',
51 'options_page_view.cc', 63 'options_page_view.cc',
52 'render_view_host.cc', # called using webkit identifiers 64 'render_view_host.cc', # called using webkit identifiers
53 'user_metrics.cc', # method definition 65 'user_metrics.cc', # method definition
54 'new_tab_ui.cc', # most visited clicks 1-9 66 'new_tab_ui.cc', # most visited clicks 1-9
(...skipping 348 matching lines...) Expand 10 before | Expand all | Expand 10 after
403 actions.add('ConnectivityDiagnostics.LaunchSource.WebStore') 415 actions.add('ConnectivityDiagnostics.LaunchSource.WebStore')
404 actions.add('ConnectivityDiagnostics.UA.LogsShown') 416 actions.add('ConnectivityDiagnostics.UA.LogsShown')
405 actions.add('ConnectivityDiagnostics.UA.PassingTestsShown') 417 actions.add('ConnectivityDiagnostics.UA.PassingTestsShown')
406 actions.add('ConnectivityDiagnostics.UA.SettingsShown') 418 actions.add('ConnectivityDiagnostics.UA.SettingsShown')
407 actions.add('ConnectivityDiagnostics.UA.TestResultExpanded') 419 actions.add('ConnectivityDiagnostics.UA.TestResultExpanded')
408 actions.add('ConnectivityDiagnostics.UA.TestSuiteRun') 420 actions.add('ConnectivityDiagnostics.UA.TestSuiteRun')
409 421
410 # Actions sent by 'Ok Google' Hotwording. 422 # Actions sent by 'Ok Google' Hotwording.
411 actions.add('Hotword.HotwordTrigger') 423 actions.add('Hotword.HotwordTrigger')
412 424
425 def _LogWarning(message):
426 print >>sys.stderr, 'WARNING: ' + message
Ilya Sherman 2015/01/14 19:15:20 Please use logging.warning() or logging.error().
Alexei Svitkine (slow) 2015/01/14 20:28:21 Done.
427
428 class InvalidStatementException(Exception):
429 """Indicates an invalid statement was found."""
430
431 class ActionNameFinder:
432 """Helper class to find action names in source code file."""
433
434 def __init__(self, path, contents):
435 self.path = path
436 self.pos = 0
437 self.contents = contents
Ilya Sherman 2015/01/14 19:15:20 nit: The private vars' names should all start with
Alexei Svitkine (slow) 2015/01/14 20:28:21 Done.
438
439 def FindNextAction(self):
440 """Finds the next action name in the file.
441
442 Returns:
443 The name of the action found or None if there are no more actions.
444 Raises:
445 InvalidStatementException if the next action statement is invalid
446 and could not be parsed. There may still be more actions in the file,
447 so FindNextAction() can continue to be called to find following ones.
448 """
449 match = USER_METRICS_ACTION_RE.search(self.contents, pos=self.pos)
450 if not match:
451 return None
452 match_start = match.start()
453 self.pos = match.end()
454 match = QUOTED_STRING_RE.match(match.group(1))
455 if not match:
456 self._RaiseException(match_start, self.pos)
457 return match.group(1)
458
459 def _RaiseException(self, match_start, match_end):
460 """Raises an InvalidStatementException for the specified code range."""
461 line_number = self.contents.count('\n', 0, match_start) + 1
462 # Add 1 to |match_start| since the RE checks the preceding character.
463 statement = self.contents[match_start + 1:match_end]
464 raise InvalidStatementException(
465 '%s uses UserMetricsAction incorrectly on line %d:\n%s' %
466 (self.path, line_number, statement))
467
468
413 def GrepForActions(path, actions): 469 def GrepForActions(path, actions):
414 """Grep a source file for calls to UserMetrics functions. 470 """Grep a source file for calls to UserMetrics functions.
415 471
416 Arguments: 472 Arguments:
417 path: path to the file 473 path: path to the file
418 actions: set of actions to add to 474 actions: set of actions to add to
419 """ 475 """
420 global number_of_files_total 476 global number_of_files_total
421 number_of_files_total = number_of_files_total + 1 477 number_of_files_total = number_of_files_total + 1
422 # we look for the UserMetricsAction structure constructor 478
423 # this should be on one line 479 finder = ActionNameFinder(path, open(path).read())
424 action_re = re.compile(r'[^a-zA-Z]UserMetricsAction\("([^"]*)') 480 while True:
425 malformed_action_re = re.compile(r'[^a-zA-Z]UserMetricsAction\([^"]') 481 try:
Ilya Sherman 2015/01/14 18:48:50 Hmm, if we're now in agreement to only loosen the
Alexei Svitkine (slow) 2015/01/14 18:52:19 The old code iterates line by line, so that wouldn
Ilya Sherman 2015/01/14 19:15:20 Fair enough :)
426 computed_action_re = re.compile(r'RecordComputedAction') 482 action_name = finder.FindNextAction()
Ilya Sherman 2015/01/14 19:15:20 It looks like you have dropped the line to update
Alexei Svitkine (slow) 2015/01/14 20:28:22 Ack - no, this was accidentally lost when I remove
483 if not action_name:
484 break
485 except InvalidStatementException, e:
486 _LogWarning(str(e))
487
427 line_number = 0 488 line_number = 0
428 for line in open(path): 489 for line in open(path):
429 line_number = line_number + 1 490 line_number = line_number + 1
430 match = action_re.search(line) 491 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. 492 # Warn if this file shouldn't be calling RecordComputedAction.
439 if os.path.basename(path) not in KNOWN_COMPUTED_USERS: 493 if os.path.basename(path) not in KNOWN_COMPUTED_USERS:
440 print >>sys.stderr, ('WARNING: %s has RecordComputedAction at %d' % 494 _LogWarning('%s has RecordComputedAction statement on line %d' %
441 (path, line_number)) 495 (path, line_number))
442 496
443 class WebUIActionsParser(HTMLParser): 497 class WebUIActionsParser(HTMLParser):
444 """Parses an HTML file, looking for all tags with a 'metric' attribute. 498 """Parses an HTML file, looking for all tags with a 'metric' attribute.
445 Adds user actions corresponding to any metrics found. 499 Adds user actions corresponding to any metrics found.
446 500
447 Arguments: 501 Arguments:
448 actions: set of actions to add to 502 actions: set of actions to add to
449 """ 503 """
450 def __init__(self, actions): 504 def __init__(self, actions):
451 HTMLParser.__init__(self) 505 HTMLParser.__init__(self)
(...skipping 362 matching lines...) Expand 10 before | Expand all | Expand 10 after
814 868
815 with open(actions_xml_path, 'wb') as f: 869 with open(actions_xml_path, 'wb') as f:
816 f.write(pretty) 870 f.write(pretty)
817 print ('Updated %s. Don\'t forget to add it to your changelist' % 871 print ('Updated %s. Don\'t forget to add it to your changelist' %
818 actions_xml_path) 872 actions_xml_path)
819 return 0 873 return 0
820 874
821 875
822 if '__main__' == __name__: 876 if '__main__' == __name__:
823 sys.exit(main(sys.argv)) 877 sys.exit(main(sys.argv))
OLDNEW
« no previous file with comments | « no previous file | tools/metrics/actions/extract_actions_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698