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

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: Rebase. 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,
Ilya Sherman 2014/02/22 07:07:37 nit: "needs be" -> "needs to be"
yao 2014/02/24 19:16:23 Done.
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')}
Ilya Sherman 2014/02/22 07:07:37 nit: Please end the sentence with a period.
yao 2014/02/24 19:16:23 Done.
133
134 # Doc for actions.xml
135 _DOC = 'User action XML'
Ilya Sherman 2014/02/22 07:07:37 nit: Why the leading underscore?
yao 2014/02/24 19:16:23 Done.
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 443 matching lines...) Expand 10 before | Expand all | Expand 10 after
571 actions.add('AutomaticReset_WebUIBanner_BannerShown') 612 actions.add('AutomaticReset_WebUIBanner_BannerShown')
572 actions.add('AutomaticReset_WebUIBanner_ManuallyClosed') 613 actions.add('AutomaticReset_WebUIBanner_ManuallyClosed')
573 actions.add('AutomaticReset_WebUIBanner_ResetClicked') 614 actions.add('AutomaticReset_WebUIBanner_ResetClicked')
574 615
575 # These actions relate to the the automatic settings reset banner shown as 616 # These actions relate to the the automatic settings reset banner shown as
576 # a result of settings hardening. 617 # a result of settings hardening.
577 actions.add('AutomaticSettingsReset_WebUIBanner_BannerShown') 618 actions.add('AutomaticSettingsReset_WebUIBanner_BannerShown')
578 actions.add('AutomaticSettingsReset_WebUIBanner_ManuallyClosed') 619 actions.add('AutomaticSettingsReset_WebUIBanner_ManuallyClosed')
579 actions.add('AutomaticSettingsReset_WebUIBanner_LearnMoreClicked') 620 actions.add('AutomaticSettingsReset_WebUIBanner_LearnMoreClicked')
580 621
622
623 class Action(object):
Alexei Svitkine (slow) 2014/02/20 22:50:55 Can you move this closer to where it's used?
yao 2014/02/24 19:16:23 Done.
624 def __init__(self, name, description, owners):
625 self.name = name
626 self.description = description
627 self.owners = owners
628
629
630 class Error(Exception):
631 pass
632
633
634 def _ExtractText(parent_dom, tag_name, as_list=True):
Ilya Sherman 2014/02/22 07:07:37 nit: Why the underscores before the method names?
Ilya Sherman 2014/02/22 07:07:37 Having as_list here seems pretty hacky. Why not j
yao 2014/02/24 19:16:23 In google3, internal/private function names should
yao 2014/02/24 19:16:23 Done.
635 """Extract the text enclosed by |tag_name| under |parent_dom|
636
637 Args:
638 parent_dom: The parent Element under which text node is searched for.
639 tag_name: The name of the tag which contains a text node.
640 as_list: If set to True, returns a list of string. Otherwise, returns a
Alexei Svitkine (slow) 2014/02/20 22:50:55 nit: "a list of string" -> " a list of strings"
yao 2014/02/24 19:16:23 Done.
641 single string.
642
643 Returns:
644 A (list of) string enclosed by |tag_name| under |parent_dom|.
645 """
646 texts = []
647 for child_dom in parent_dom.getElementsByTagName(tag_name):
648 text_dom = child_dom.childNodes
649 if text_dom.length != 1:
650 raise Error('More than 1 child node exists under %s' % tag_name)
651 if text_dom[0].nodeType != minidom.Node.TEXT_NODE:
652 raise Error('%s\'s child node is not a text node.' % tag_name)
653 texts.append(text_dom[0].data)
654 if not as_list:
655 if texts:
656 return texts[0]
Ilya Sherman 2014/02/22 07:07:37 Should it be an error for tests to have a size not
yao 2014/02/24 19:16:23 Done.
657 else:
658 return None
659 return texts
660
661
662 def _CreateActionTag(top_dom, action_name, action_object):
663 """Create a new action tag.
664
665 Format of an action tag:
666 <action name="name">
667 <owners>
668 <owner>Owner </owner>
Ilya Sherman 2014/02/22 07:07:37 nit: Spurious space after "Owner"?
yao 2014/02/24 19:16:23 Done.
669 </owners>
670 <description>
671 Description.
672 </description>
673 </action>
674
675 If action_name is in actions_dict, the values to be inserted is based on the
Ilya Sherman 2014/02/22 07:07:37 nit: "values ... is" -> "values ... are"
yao 2014/02/24 19:16:23 Done.
676 corresponding Action object. If action_name is not in actions_dict, the
677 default value from TAGS is used.
678
679 Args:
680 top_dom: The parent node under which the new action tag is created.
681 action_name: The name of an action.
682 actions_dict: A map from action name to Action object.
683
684 Returns:
685 An action tag Element with proper children elements.
686 """
687 action_dom = top_dom.createElement('action')
688 action_dom.setAttribute('name', action_name)
689 owners_dom = top_dom.createElement('owners')
690 action_dom.appendChild(owners_dom)
691 description_dom = top_dom.createElement('description')
692 action_dom.appendChild(description_dom)
693
694 # Create description tag.
695 if action_object and action_object.description:
696 # If description for this action is not None, use the store value.
697 # Otherwise, use the default value.
698 description_dom.appendChild(top_dom.createTextNode(
699 action_object.description))
700 else:
701 description_dom.appendChild(top_dom.createTextNode(
702 TAGS.get('description', '')))
703
704 # Create owner tag.
705 if action_object and action_object.owners:
706 # If owners for this action is not None, use the stored value. Otherwise,
707 # use the default value.
708 for owner in action_object.owners:
709 owner_dom = top_dom.createElement('owner')
710 owner_dom.appendChild(top_dom.createTextNode(owner))
711 owners_dom.appendChild(owner_dom)
712 else:
713 # Use default value.
714 owner_dom = top_dom.createElement('owner')
715 owner_dom.appendChild(top_dom.createTextNode(TAGS.get('owner', '')))
716 owners_dom.appendChild(owner_dom)
717 return action_dom
718
719
720 def _PrettyPrint(actions, actions_dict):
721 """Given a list of action data, create a well-printed minidom document.
722
723 Args:
724 actions: A list of action names.
725 actions_dict: A mappting from action name to Action object.
726
727 Returns:
728 A well-printed minidom document that represents the input action data.
729 """
730 # Form new minidom nodes based on updated |actions|.
731 new_dom = minidom.getDOMImplementation().createDocument(None, 'actions', None)
732 top_element = new_dom.documentElement
733 top_element.appendChild(new_dom.createComment(_DOC))
734
735 # Print out the actions as a sorted list.
736 for action in sorted(actions):
737 top_element.appendChild(_CreateActionTag(new_dom, action,
738 actions_dict.get(action, None)))
739
740 # Pretty print the generated minidom node |new_dom|.
741 xml_style = pretty_print_xml.XmlStyle(ATTRIBUTE_ORDER,
Alexei Svitkine (slow) 2014/02/20 22:50:55 I noticed you moved this to its own print_style fi
yao 2014/02/24 19:16:23 Done.
742 TAGS_THAT_HAVE_EXTRA_NEWLINE,
743 TAGS_THAT_DONT_INDENT,
744 TAGS_THAT_ALLOW_SINGLE_LINE)
745 return xml_style.PrettyPrintNode(new_dom)
746
747
581 def main(argv): 748 def main(argv):
582 if '--hash' in argv: 749 presubmit = ('--presubmit' in argv)
583 hash_output = True 750
584 else: 751 # A set to store all actions.
585 hash_output = False
586 print >>sys.stderr, "WARNING: If you added new UMA tags, you must" + \
587 " use the --hash option to update chromeactions.txt."
588 # if we do a hash output, we want to only append NEW actions, and we know
589 # the file we want to work on
590 actions = set() 752 actions = set()
753 # A map from action name to an Action object.
754 actions_dict = {}
755 actions_xml_path = os.path.join(path_utils.ScriptDir(), 'actions.xml')
591 756
592 chromeactions_path = os.path.join(path_utils.ScriptDir(), "chromeactions.txt") 757 # Save the original file content.
758 with open(actions_xml_path, 'rb') as f:
759 original_xml = f.read()
593 760
594 if hash_output: 761 # Parse and store the XML data currently stored in file.
Alexei Svitkine (slow) 2014/02/20 22:50:55 Nit: "in file" -> "in the file"
yao 2014/02/24 19:16:23 Done.
595 f = open(chromeactions_path) 762 dom = minidom.parse(actions_xml_path)
596 for line in f: 763 for action_dom in dom.getElementsByTagName('action'):
597 part = line.rpartition("\t") 764 action_name = action_dom.getAttribute('name')
598 part = part[2].strip() 765 actions.add(action_name)
599 actions.add(part) 766 description = _ExtractText(action_dom, 'description', False)
600 f.close() 767 owners = _ExtractText(action_dom, 'owner', True)
601 768 actions_dict[action_name] = Action(action_name, description, owners)
Alexei Svitkine (slow) 2014/02/20 22:50:55 Nit: Actually, I'd also make a function for this b
yao 2014/02/24 19:16:23 Done.
602 769
603 AddComputedActions(actions) 770 AddComputedActions(actions)
604 # TODO(fmantek): bring back webkit editor actions. 771 # TODO(fmantek): bring back webkit editor actions.
605 # AddWebKitEditorActions(actions) 772 # AddWebKitEditorActions(actions)
606 AddAboutFlagsActions(actions) 773 AddAboutFlagsActions(actions)
607 AddWebUIActions(actions) 774 AddWebUIActions(actions)
608 775
609 AddLiteralActions(actions) 776 AddLiteralActions(actions)
610 777
611 # print "Scanned {0} number of files".format(number_of_files_total) 778 # print "Scanned {0} number of files".format(number_of_files_total)
612 # print "Found {0} entries".format(len(actions)) 779 # print "Found {0} entries".format(len(actions))
613 780
614 AddAndroidActions(actions) 781 AddAndroidActions(actions)
615 AddAutomaticResetBannerActions(actions) 782 AddAutomaticResetBannerActions(actions)
616 AddBookmarkManagerActions(actions) 783 AddBookmarkManagerActions(actions)
617 AddChromeOSActions(actions) 784 AddChromeOSActions(actions)
618 AddClosedSourceActions(actions) 785 AddClosedSourceActions(actions)
619 AddExtensionActions(actions) 786 AddExtensionActions(actions)
620 AddHistoryPageActions(actions) 787 AddHistoryPageActions(actions)
621 AddKeySystemSupportActions(actions) 788 AddKeySystemSupportActions(actions)
622 789
623 if hash_output: 790 pretty = _PrettyPrint(actions, actions_dict)
624 f = open(chromeactions_path, "wb") 791 if original_xml == pretty:
792 print 'actions.xml is correctly pretty-printed.'
793 sys.exit(0)
794 if presubmit:
795 logging.info('actions.xml is not formatted correctly; run '
796 'extract_actions.py to fix.')
797 sys.exit(1)
625 798
799 # Prompt user to consent on the change.
800 if not diff_util.PromptUserToAcceptDiff(
801 original_xml, pretty, 'Is the new version acceptable?'):
802 logging.error('Aborting')
803 sys.exit(0)
Ilya Sherman 2014/02/22 07:07:37 This should probably be sys.exit(1), since the scr
yao 2014/02/24 19:16:23 Done.
626 804
627 # Print out the actions as a sorted list. 805 print 'Creating backup file: actions.before.xml.'
628 for action in sorted(actions): 806 shutil.move(actions_xml_path, 'actions.before.xml')
Alexei Svitkine (slow) 2014/02/20 22:50:55 I'd name this actions.old.xml.
yao 2014/02/24 19:16:23 Done.
629 if hash_output:
630 hash = hashlib.md5()
631 hash.update(action)
632 print >>f, '0x%s\t%s' % (hash.hexdigest()[:16], action)
633 else:
634 print action
635 807
636 if hash_output: 808 with open(actions_xml_path, 'wb') as f:
637 print "Done. Do not forget to add chromeactions.txt to your changelist" 809 f.write(pretty)
810 print ('Updated %s. Do not forget to add it to your changelist' %
Ilya Sherman 2014/02/22 07:07:37 Optional nit: "Do not" -> "Don't" (reads more natu
yao 2014/02/24 19:16:23 Done.
811 actions_xml_path)
638 return 0 812 return 0
639 813
640 814
641 if '__main__' == __name__: 815 if '__main__' == __name__:
642 sys.exit(main(sys.argv)) 816 sys.exit(main(sys.argv))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698