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

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: Indent PrettyPrintNode back. 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 base/metrics/user_metrics.h 16 base/metrics/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 diff_util
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 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
108 'xkb:sk::slo', 'xkb:tr::tur', 'xkb:ua::ukr', 'xkb:us::eng', 119 'xkb:sk::slo', 'xkb:tr::tur', 'xkb:ua::ukr', 'xkb:us::eng',
109 'xkb:us:altgr-intl:eng', 'xkb:us:colemak:eng', 'xkb:us:dvorak:eng', 120 'xkb:us:altgr-intl:eng', 'xkb:us:colemak:eng', 'xkb:us:dvorak:eng',
110 'xkb:us:intl:eng', 121 'xkb:us:intl:eng',
111 ) 122 )
112 123
113 # The path to the root of the repository. 124 # The path to the root of the repository.
114 REPOSITORY_ROOT = os.path.join(path_utils.ScriptDir(), '..', '..', '..') 125 REPOSITORY_ROOT = os.path.join(path_utils.ScriptDir(), '..', '..', '..')
115 126
116 number_of_files_total = 0 127 number_of_files_total = 0
117 128
129 # Tags that need to be inserted to each 'action' tag and their default content.
130 TAGS = {'description': 'Please enter the description of this user action.',
131 'owner': ('Please specify the owners of this user action. ' +
132 'Add more owner tags as needed')}
133
134 # Doc for actions.xml
135 _DOC = 'User action XML'
136
137 # Desired order for tag and tag attributes.
138 # { tag_name: [attribute_name, ...] }
139 ATTRIBUTE_ORDER = {
140 'action': ['name'],
141 'owners': [],
142 'owner':[],
143 'description': [],
144 }
145
146 # Tag names for top-level nodes whose children we don't want to indent.
147 TAGS_THAT_DONT_INDENT = ['actions']
148
149 # Extra vertical spacing rules for special tag names.
150 # {tag_name: (newlines_after_open, newlines_before_close, newlines_after_close)}
151 TAGS_THAT_HAVE_EXTRA_NEWLINE = {
152 'actions': (2, 1, 1),
153 'action': (1, 1, 1),
154 }
155
156 # Tags that we allow to be squished into a single line for brevity.
157 TAGS_THAT_ALLOW_SINGLE_LINE = ['owner']
158
118 159
119 def AddComputedActions(actions): 160 def AddComputedActions(actions):
120 """Add computed actions to the actions list. 161 """Add computed actions to the actions list.
121 162
122 Arguments: 163 Arguments:
123 actions: set of actions to add to. 164 actions: set of actions to add to.
124 """ 165 """
125 166
126 # Actions for back_forward_menu_model.cc. 167 # Actions for back_forward_menu_model.cc.
127 for dir in ('BackMenu_', 'ForwardMenu_'): 168 for dir in ('BackMenu_', 'ForwardMenu_'):
(...skipping 432 matching lines...) Expand 10 before | Expand all | Expand 10 after
560 """Add actions that are used for the automatic profile settings reset banner 601 """Add actions that are used for the automatic profile settings reset banner
561 in chrome://settings. 602 in chrome://settings.
562 603
563 Arguments 604 Arguments
564 actions: set of actions to add to. 605 actions: set of actions to add to.
565 """ 606 """
566 actions.add('AutomaticReset_WebUIBanner_BannerShown') 607 actions.add('AutomaticReset_WebUIBanner_BannerShown')
567 actions.add('AutomaticReset_WebUIBanner_ManuallyClosed') 608 actions.add('AutomaticReset_WebUIBanner_ManuallyClosed')
568 actions.add('AutomaticReset_WebUIBanner_ResetClicked') 609 actions.add('AutomaticReset_WebUIBanner_ResetClicked')
569 610
570 def main(argv): 611
571 if '--hash' in argv: 612 class Action(object):
572 hash_output = True 613 def __init__(self, name, description, owners):
614 self.name = name
615 self.description = description
616 self.owners = owners
617
618
619 class Error(Exception):
620 pass
621
622
623 def _ExtractText(parent_dom, tag_name, as_list=True):
624 """Extract the text enclosed by |tag_name| under |parent_dom|
625
626 Args:
627 parent_dom: The parent Element under which text node is searched for.
628 tag_name: The name of the tag which contains a text node.
629 as_list: If set to True, returns a list of string. Otherwise, returns a
630 single string.
631
632 Returns:
633 A (list of) string enclosed by |tag_name| under |parent_dom|.
634 """
635 texts = []
636 for child_dom in parent_dom.getElementsByTagName(tag_name):
637 text_dom = child_dom.childNodes
638 if text_dom.length != 1:
639 raise Error('More than 1 child node exists under %s' % tag_name)
640 if text_dom[0].nodeType != minidom.Node.TEXT_NODE:
641 raise Error('%s\'s child node is not a text node.' % tag_name)
642 texts.append(text_dom[0].data)
643 if not as_list:
644 if texts:
645 return texts[0]
646 else:
647 return None
648 return texts
649
650
651 def _CreateActionTag(top_dom, action_name, action_object):
652 """Create a new action tag.
653
654 Format of an action tag:
655 <action name="name">
656 <owners>
657 <owner>Owner </owner>
658 </owners>
659 <description>
660 Description.
661 </description>
662 </action>
663
664 If action_name is in actions_dict, the values to be inserted is based on the
665 corresponding Action object. If action_name is not in actions_dict, the
666 default value from TAGS is used.
667
668 Args:
669 top_dom: The parent node under which the new action tag is created.
670 action_name: The name of an action.
671 actions_dict: A map from action name to Action object.
672
673 Returns:
674 An action tag Element with proper children elements.
675 """
676 action_dom = top_dom.createElement('action')
677 action_dom.setAttribute('name', action_name)
678 owners_dom = top_dom.createElement('owners')
679 action_dom.appendChild(owners_dom)
680 description_dom = top_dom.createElement('description')
681 action_dom.appendChild(description_dom)
682
683 # Create description tag.
684 if action_object and action_object.description:
685 # If description for this action is not None, use the store value.
686 # Otherwise, use the default value.
687 description_dom.appendChild(top_dom.createTextNode(
688 action_object.description))
573 else: 689 else:
574 hash_output = False 690 description_dom.appendChild(top_dom.createTextNode(
575 print >>sys.stderr, "WARNING: If you added new UMA tags, you must" + \ 691 TAGS.get('description', '')))
576 " use the --hash option to update chromeactions.txt." 692
577 # if we do a hash output, we want to only append NEW actions, and we know 693 # Create owner tag.
578 # the file we want to work on 694 if action_object and action_object.owners:
695 # If owners for this action is not None, use the stored value. Otherwise,
696 # use the default value.
697 for owner in action_object.owners:
698 owner_dom = top_dom.createElement('owner')
699 owner_dom.appendChild(top_dom.createTextNode(owner))
700 owners_dom.appendChild(owner_dom)
701 else:
702 # Use default value.
703 owner_dom = top_dom.createElement('owner')
704 owner_dom.appendChild(top_dom.createTextNode(TAGS.get('owner', '')))
705 owners_dom.appendChild(owner_dom)
706 return action_dom
707
708
709 def main():
710 # A set to store all actions.
579 actions = set() 711 actions = set()
712 # A map from action name to an Action object.
713 actions_dict = {}
580 714
581 chromeactions_path = os.path.join(path_utils.ScriptDir(), "chromeactions.txt") 715 actions_xml_path = os.path.join(path_utils.ScriptDir(), 'actions.xml')
582 716
583 if hash_output: 717 # Save the original file content.
584 f = open(chromeactions_path) 718 with open(actions_xml_path, 'rb') as f:
585 for line in f: 719 original_xml = f.read()
586 part = line.rpartition("\t")
587 part = part[2].strip()
588 actions.add(part)
589 f.close()
590 720
721 # Parse and store the XML data currently stored in file.
722 dom = minidom.parse(actions_xml_path)
723 for action_dom in dom.getElementsByTagName('action'):
724 action_name = action_dom.getAttribute('name')
725 actions.add(action_name)
726 description = _ExtractText(action_dom, 'description', False)
727 owners = _ExtractText(action_dom, 'owner', True)
728 actions_dict[action_name] = Action(action_name, description, owners)
591 729
592 AddComputedActions(actions) 730 AddComputedActions(actions)
593 # TODO(fmantek): bring back webkit editor actions. 731 # TODO(fmantek): bring back webkit editor actions.
594 # AddWebKitEditorActions(actions) 732 # AddWebKitEditorActions(actions)
595 AddAboutFlagsActions(actions) 733 AddAboutFlagsActions(actions)
596 AddWebUIActions(actions) 734 AddWebUIActions(actions)
597 735
598 AddLiteralActions(actions) 736 AddLiteralActions(actions)
599 737
600 # print "Scanned {0} number of files".format(number_of_files_total) 738 # print "Scanned {0} number of files".format(number_of_files_total)
601 # print "Found {0} entries".format(len(actions)) 739 # print "Found {0} entries".format(len(actions))
602 740
603 AddAndroidActions(actions) 741 AddAndroidActions(actions)
604 AddAutomaticResetBannerActions(actions) 742 AddAutomaticResetBannerActions(actions)
605 AddBookmarkManagerActions(actions) 743 AddBookmarkManagerActions(actions)
606 AddChromeOSActions(actions) 744 AddChromeOSActions(actions)
607 AddClosedSourceActions(actions) 745 AddClosedSourceActions(actions)
608 AddExtensionActions(actions) 746 AddExtensionActions(actions)
609 AddHistoryPageActions(actions) 747 AddHistoryPageActions(actions)
610 AddKeySystemSupportActions(actions) 748 AddKeySystemSupportActions(actions)
611 749
612 if hash_output: 750 # Form new minidom nodes based on updated |actions|.
613 f = open(chromeactions_path, "wb") 751 new_dom = minidom.getDOMImplementation().createDocument(None, 'actions', None)
614 752 top_element = new_dom.documentElement
753 top_element.appendChild(new_dom.createComment(_DOC))
615 754
616 # Print out the actions as a sorted list. 755 # Print out the actions as a sorted list.
617 for action in sorted(actions): 756 for action in sorted(actions):
618 if hash_output: 757 top_element.appendChild(_CreateActionTag(new_dom, action,
619 hash = hashlib.md5() 758 actions_dict.get(action, None)))
620 hash.update(action)
621 print >>f, '0x%s\t%s' % (hash.hexdigest()[:16], action)
622 else:
623 print action
624 759
625 if hash_output: 760 # Pretty print the generated minidom node |new_dom|.
626 print "Done. Do not forget to add chromeactions.txt to your changelist" 761 xml_style = pretty_print_xml.XmlStyle(ATTRIBUTE_ORDER,
762 TAGS_THAT_HAVE_EXTRA_NEWLINE,
763 TAGS_THAT_DONT_INDENT,
764 TAGS_THAT_ALLOW_SINGLE_LINE)
765 pretty = xml_style.PrettyPrintNode(new_dom)
Alexei Svitkine (slow) 2014/02/11 19:05:31 Can you extract the code from line 750 up to this
yao 2014/02/13 15:58:08 Done.
766
767 if original_xml == pretty:
768 print 'actions.xml is correctly pretty-printed.'
769 sys.exit(0)
770
771 # Prompt user to consent on the change.
772 if not diff_util.PromptUserToAcceptDiff(
773 original_xml, pretty, 'Is the new version acceptable?'):
774 logging.error('Aborting')
775 sys.exit(0)
776
777 print 'Creating backup file: actions.before.xml.'
778 shutil.move(actions_xml_path, 'actions.before.xml')
779
780 with open(actions_xml_path, 'wb') as f:
781 f.write(pretty)
782 print ('Updated %s. Do not forget to add it to your changelist' %
783 actions_xml_path)
627 return 0 784 return 0
628 785
629 786
630 if '__main__' == __name__: 787 if '__main__' == __name__:
631 sys.exit(main(sys.argv)) 788 sys.exit(main())
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698