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

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

Issue 149503005: Change actions.txt to actions.xml (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Reupload(Resolve old chunk mismatch) Created 6 years, 10 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
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 content/browser/user_metrics.h 16 content/browser/user_metrics.h
17 http://wiki.corp.google.com/twiki/bin/view/Main/ChromeUserExperienceMetrics 17 http://wiki.corp.google.com/twiki/bin/view/Main/ChromeUserExperienceMetrics
18 18
19 If run with a "--hash" argument, chromeactions.txt will be updated. 19 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 be changed,
21 a window will be prompted asking for user's consent. The old version will also
22 be saved in a backup file.
20 """ 23 """
21 24
22 __author__ = 'evanm (Evan Martin)' 25 __author__ = 'evanm (Evan Martin)'
23 26
24 import hashlib
25 from HTMLParser import HTMLParser 27 from HTMLParser import HTMLParser
28 import logging
26 import os 29 import os
27 import re 30 import re
31 import shutil
28 import sys 32 import sys
33 from xml.dom import minidom
34
29 35
30 sys.path.insert(1, os.path.join(sys.path[0], '..', '..', 'python')) 36 sys.path.insert(1, os.path.join(sys.path[0], '..', '..', 'python'))
31 from google import path_utils 37 from google import path_utils
32 38
39 # Import the metrics/common module for pretty print xml.
40 sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common'))
41 import diffutil
Alexei Svitkine (slow) 2014/02/03 19:37:34 Hmm, can diffutil and pretty_print_xml use the sam
yao 2014/02/04 19:08:12 Done.
42 import pretty_print_xml
43
33 # Files that are known to use content::RecordComputedAction(), which means 44 # Files that are known to use content::RecordComputedAction(), which means
34 # they require special handling code in this script. 45 # they require special handling code in this script.
35 # To add a new file, add it to this list and add the appropriate logic to 46 # To add a new file, add it to this list and add the appropriate logic to
36 # generate the known actions to AddComputedActions() below. 47 # generate the known actions to AddComputedActions() below.
37 KNOWN_COMPUTED_USERS = ( 48 KNOWN_COMPUTED_USERS = (
38 'back_forward_menu_model.cc', 49 'back_forward_menu_model.cc',
39 'options_page_view.cc', 50 'options_page_view.cc',
40 'render_view_host.cc', # called using webkit identifiers 51 'render_view_host.cc', # called using webkit identifiers
41 'user_metrics.cc', # method definition 52 'user_metrics.cc', # method definition
42 'new_tab_ui.cc', # most visited clicks 1-9 53 'new_tab_ui.cc', # most visited clicks 1-9
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
114 'xkb:se::swe', 'xkb:si::slv', 'xkb:sk::slo', 'xkb:tr::tur', 'xkb:ua::ukr', 125 'xkb:se::swe', 'xkb:si::slv', 'xkb:sk::slo', 'xkb:tr::tur', 'xkb:ua::ukr',
115 'xkb:us::eng', 'xkb:us:altgr-intl:eng', 'xkb:us:colemak:eng', 126 'xkb:us::eng', 'xkb:us:altgr-intl:eng', 'xkb:us:colemak:eng',
116 'xkb:us:dvorak:eng', 'xkb:us:intl:eng', 127 'xkb:us:dvorak:eng', 'xkb:us:intl:eng',
117 ) 128 )
118 129
119 # The path to the root of the repository. 130 # The path to the root of the repository.
120 REPOSITORY_ROOT = os.path.join(path_utils.ScriptDir(), '..', '..', '..') 131 REPOSITORY_ROOT = os.path.join(path_utils.ScriptDir(), '..', '..', '..')
121 132
122 number_of_files_total = 0 133 number_of_files_total = 0
123 134
135 # Tags that need to be inserted to each 'action' tag and their default content.
136 TAGS = {'description': 'Please enter the description of this user action.',
137 'owner': 'Please specify the owner of this user action.'}
138
139 # Doc for actions.xml
140 _DOC = 'User action XML'
141
142 # Desired order for tag and tag attributes.
143 # { tag_name: [attribute_name, ...] }
144 ATTRIBUTE_ORDER = {
145 'action': ['name'],
146 'owner': [],
147 'description': [],
148 }
149
150 # Tag names for top-level nodes whose children we don't want to indent.
151 TAGS_THAT_DONT_INDENT = ['actions']
152
153 # Extra vertical spacing rules for special tag names.
154 # {tag_name: (newlines_after_open, newlines_before_close, newlines_after_close)}
155 TAGS_THAT_HAVE_EXTRA_NEWLINE = {
156 'actions': (2, 1, 1),
157 'action': (1, 1, 1),
158 }
159
160 # Tags that we allow to be squished into a single line for brevity.
161 TAGS_THAT_ALLOW_SINGLE_LINE = ['owner']
162
124 163
125 def AddComputedActions(actions): 164 def AddComputedActions(actions):
126 """Add computed actions to the actions list. 165 """Add computed actions to the actions list.
127 166
128 Arguments: 167 Arguments:
129 actions: set of actions to add to. 168 actions: set of actions to add to.
130 """ 169 """
131 170
132 # Actions for back_forward_menu_model.cc. 171 # Actions for back_forward_menu_model.cc.
133 for dir in ('BackMenu_', 'ForwardMenu_'): 172 for dir in ('BackMenu_', 'ForwardMenu_'):
(...skipping 432 matching lines...) Expand 10 before | Expand all | Expand 10 after
566 """Add actions that are used for the automatic profile settings reset banner 605 """Add actions that are used for the automatic profile settings reset banner
567 in chrome://settings. 606 in chrome://settings.
568 607
569 Arguments 608 Arguments
570 actions: set of actions to add to. 609 actions: set of actions to add to.
571 """ 610 """
572 actions.add('AutomaticReset_WebUIBanner_BannerShown') 611 actions.add('AutomaticReset_WebUIBanner_BannerShown')
573 actions.add('AutomaticReset_WebUIBanner_ManuallyClosed') 612 actions.add('AutomaticReset_WebUIBanner_ManuallyClosed')
574 actions.add('AutomaticReset_WebUIBanner_ResetClicked') 613 actions.add('AutomaticReset_WebUIBanner_ResetClicked')
575 614
615
616 class Action(object):
617 def __init__(self, name, description, owner):
618 self.name = name
619 self.values = {'description': description, 'owner': owner}
620
621
622 class Error(Exception):
623 pass
624
625
626 def _ExtractText(parent_dom, tag_name):
627 """Extract the text value enclosed by |tag_name| under |parent_dom|"""
628 child_dom = parent_dom.getElementsByTagName(tag_name)
629 if child_dom.length == 1:
630 text_dom = child_dom[0].childNodes
631 if text_dom.length != 1:
632 logging.error('More than 1 child node exist under %s' % tag_name)
Alexei Svitkine (slow) 2014/02/03 19:37:34 Nit: exist -> exists
yao 2014/02/04 19:08:12 Done.
633 raise Error()
Alexei Svitkine (slow) 2014/02/03 19:37:34 Can't the message above be included in the Error?
yao 2014/02/04 19:08:12 Done.
634 if text_dom[0].nodeType != minidom.Node.TEXT_NODE:
635 logging.error('%s\'s child node is not a text node.' % tag_name)
636 raise Error()
637 return text_dom[0].data
638 elif child_dom.length > 1:
639 logging.error('There are more than 1 %s tag.' % tag_name)
640 raise Error()
641
642
643 def _CreateTag(top_dom, tag_name, action_name, actions_dict):
644 """Create a new tag
645
646 If action_name is in actions_dict, the values to be inserted is based on the
647 corresponding Action object. If action_name is not in actions_dict, the
648 default value from TAGS is used.
649
650 Args:
651 top_dom: The parent node under which the new tag is created.
652 tag_name: The name of the tag to be created.
653 action_name: The name of an action.
654 actions_dict: A map from action name to Action object.
655 """
656 tag_dom = top_dom.createElement(tag_name)
657 if action_name in actions_dict:
658 tag_dom.appendChild(top_dom.createTextNode(
659 actions_dict[action_name].values[tag_name]))
660 else:
661 tag_dom.appendChild(top_dom.createTextNode(TAGS.get(tag_name, '')))
662 return tag_dom
663
664
576 def main(argv): 665 def main(argv):
577 if '--hash' in argv: 666 # A set to store all actions.
578 hash_output = True
579 else:
580 hash_output = False
581 print >>sys.stderr, "WARNING: If you added new UMA tags, you must" + \
582 " use the --hash option to update chromeactions.txt."
583 # if we do a hash output, we want to only append NEW actions, and we know
584 # the file we want to work on
585 actions = set() 667 actions = set()
668 # A map from action name to an Action object.
669 actions_dict = {}
586 670
587 chromeactions_path = os.path.join(path_utils.ScriptDir(), "chromeactions.txt") 671 actions_xml_path = os.path.join(path_utils.ScriptDir(), 'actions.xml')
588 672
589 if hash_output: 673 # Save the original file content.
590 f = open(chromeactions_path) 674 with open(actions_xml_path, 'rb') as f:
591 for line in f: 675 original_xml = f.read()
592 part = line.rpartition("\t")
593 part = part[2].strip()
594 actions.add(part)
595 f.close()
596 676
677 # Parse and store the XML data currently stored in file.
678 dom = minidom.parse(actions_xml_path)
679 for action_dom in dom.getElementsByTagName('action'):
680 action_name = action_dom.getAttribute('name')
681 actions.add(action_name)
682 description = _ExtractText(action_dom, 'description')
683 owner = _ExtractText(action_dom, 'owner')
684 actions_dict[action_name] = Action(action_name, description, owner)
597 685
598 AddComputedActions(actions) 686 AddComputedActions(actions)
599 # TODO(fmantek): bring back webkit editor actions. 687 # TODO(fmantek): bring back webkit editor actions.
600 # AddWebKitEditorActions(actions) 688 # AddWebKitEditorActions(actions)
601 AddAboutFlagsActions(actions) 689 AddAboutFlagsActions(actions)
602 AddWebUIActions(actions) 690 AddWebUIActions(actions)
603 691
604 AddLiteralActions(actions) 692 AddLiteralActions(actions)
605 693
606 # print "Scanned {0} number of files".format(number_of_files_total) 694 # print "Scanned {0} number of files".format(number_of_files_total)
607 # print "Found {0} entries".format(len(actions)) 695 # print "Found {0} entries".format(len(actions))
608 696
609 AddAndroidActions(actions) 697 AddAndroidActions(actions)
610 AddAutomaticResetBannerActions(actions) 698 AddAutomaticResetBannerActions(actions)
611 AddBookmarkManagerActions(actions) 699 AddBookmarkManagerActions(actions)
612 AddChromeOSActions(actions) 700 AddChromeOSActions(actions)
613 AddClosedSourceActions(actions) 701 AddClosedSourceActions(actions)
614 AddExtensionActions(actions) 702 AddExtensionActions(actions)
615 AddHistoryPageActions(actions) 703 AddHistoryPageActions(actions)
616 AddKeySystemSupportActions(actions) 704 AddKeySystemSupportActions(actions)
617 705
618 if hash_output: 706 # Form new minidom nodes based on updated |actions|.
619 f = open(chromeactions_path, "wb") 707 new_dom = minidom.getDOMImplementation().createDocument(None, 'actions', None)
620 708 top_element = new_dom.documentElement
709 top_element.appendChild(new_dom.createComment(_DOC))
621 710
622 # Print out the actions as a sorted list. 711 # Print out the actions as a sorted list.
623 for action in sorted(actions): 712 for action in sorted(actions):
624 if hash_output: 713 action_dom = new_dom.createElement('action')
625 hash = hashlib.md5() 714 action_dom.setAttribute('name', action)
626 hash.update(action) 715 action_dom.appendChild(_CreateTag(new_dom, 'owner', action, actions_dict))
627 print >>f, '0x%s\t%s' % (hash.hexdigest()[:16], action) 716 action_dom.appendChild(_CreateTag(
628 else: 717 new_dom, 'description', action, actions_dict))
629 print action 718 top_element.appendChild(action_dom)
630 719
631 if hash_output: 720 # Pretty print the generate minidom node |new_dom|.
632 print "Done. Do not forget to add chromeactions.txt to your changelist" 721 pretty = pretty_print_xml.PrettyPrintNode(
722 new_dom, pretty_print_xml.XmlStyle(ATTRIBUTE_ORDER,
723 TAGS_THAT_HAVE_EXTRA_NEWLINE,
724 TAGS_THAT_DONT_INDENT,
725 TAGS_THAT_ALLOW_SINGLE_LINE))
726 if original_xml == pretty:
727 print 'actions.xml is correctly pretty-printed.'
728 sys.exit(0)
729
730 # Prompt user to consent on the change.
731 if not diffutil.PromptUserToAcceptDiff(
732 original_xml, pretty, 'Is the new version acceptable?'):
733 logging.error('Aborting')
734 sys.exit(0)
735
736 print 'Creating backup file: actions.before.xml.'
737 shutil.move(actions_xml_path, 'actions.before.xml')
738
739 with open(actions_xml_path, 'wb') as f:
740 f.write(pretty)
741 print ('Updated %s. Do not forget to add it to your changelist' %
742 actions_xml_path)
633 return 0 743 return 0
634 744
635 745
636 if '__main__' == __name__: 746 if '__main__' == __name__:
637 sys.exit(main(sys.argv)) 747 sys.exit(main(sys.argv))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698