OLD | NEW |
---|---|
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Extract UserMetrics "actions" strings from the Chrome source. | 6 """Extract UserMetrics "actions" strings from the Chrome source. |
7 | 7 |
8 This program generates the list of known actions we expect to see in the | 8 This program generates the list of known actions we expect to see in the |
9 user behavior logs. It walks the Chrome source, looking for calls to | 9 user behavior logs. It walks the Chrome source, looking for calls to |
10 UserMetrics functions, extracting actions and warning on improper calls, | 10 UserMetrics functions, extracting actions and warning on improper calls, |
11 as well as generating the lists of possible actions in situations where | 11 as well as generating the lists of possible actions in situations where |
12 there are many possible actions. | 12 there are many possible actions. |
13 | 13 |
14 See also: | 14 See also: |
15 chrome/browser/user_metrics.h | 15 chrome/browser/user_metrics.h |
16 http://wiki.corp.google.com/twiki/bin/view/Main/ChromeUserExperienceMetrics | 16 http://wiki.corp.google.com/twiki/bin/view/Main/ChromeUserExperienceMetrics |
17 | 17 |
18 If run with a "--hash" argument, chromeactions.txt will be updated. | 18 If run with a "--hash" argument, chromeactions.txt will be updated. |
19 """ | 19 """ |
20 | 20 |
21 __author__ = 'evanm (Evan Martin)' | 21 __author__ = 'evanm (Evan Martin)' |
22 | 22 |
23 import hashlib | |
24 from HTMLParser import HTMLParser | |
23 import os | 25 import os |
24 import re | 26 import re |
25 import sys | 27 import sys |
26 import hashlib | |
27 | 28 |
28 sys.path.insert(1, os.path.join(sys.path[0], '..', '..', 'tools', 'python')) | 29 sys.path.insert(1, os.path.join(sys.path[0], '..', '..', 'tools', 'python')) |
29 from google import path_utils | 30 from google import path_utils |
30 | 31 |
31 # Files that are known to use UserMetrics::RecordComputedAction(), which means | 32 # Files that are known to use UserMetrics::RecordComputedAction(), which means |
32 # they require special handling code in this script. | 33 # they require special handling code in this script. |
33 # To add a new file, add it to this list and add the appropriate logic to | 34 # To add a new file, add it to this list and add the appropriate logic to |
34 # generate the known actions to AddComputedActions() below. | 35 # generate the known actions to AddComputedActions() below. |
35 KNOWN_COMPUTED_USERS = ( | 36 KNOWN_COMPUTED_USERS = ( |
36 'back_forward_menu_model.cc', | 37 'back_forward_menu_model.cc', |
37 'options_page_view.cc', | 38 'options_page_view.cc', |
38 'render_view_host.cc', # called using webkit identifiers | 39 'render_view_host.cc', # called using webkit identifiers |
39 'user_metrics.cc', # method definition | 40 'user_metrics.cc', # method definition |
40 'new_tab_ui.cc', # most visited clicks 1-9 | 41 'new_tab_ui.cc', # most visited clicks 1-9 |
41 'extension_metrics_module.cc', # extensions hook for user metrics | 42 'extension_metrics_module.cc', # extensions hook for user metrics |
42 'safe_browsing_blocking_page.cc', # various interstitial types and actions | 43 'safe_browsing_blocking_page.cc', # various interstitial types and actions |
43 'language_options_handler.cc', # languages and input methods in chrome os | 44 'language_options_handler_common.cc', # languages and input methods in CrOS |
45 'cros_language_options_handler.cc', # languages and input methods in CrOS | |
44 'about_flags.cc', # do not generate a warning; see AddAboutFlagsActions() | 46 'about_flags.cc', # do not generate a warning; see AddAboutFlagsActions() |
47 'external_metrics.cc', # see AddChromeOSActions() | |
48 'core_options_handler.cc' # see AddWebUIActions() | |
45 ) | 49 ) |
46 | 50 |
47 # Language codes used in Chrome. The list should be updated when a new | 51 # Language codes used in Chrome. The list should be updated when a new |
48 # language is added to app/l10n_util.cc, as follows: | 52 # language is added to app/l10n_util.cc, as follows: |
49 # | 53 # |
50 # % (cat app/l10n_util.cc | \ | 54 # % (cat app/l10n_util.cc | \ |
51 # perl -n0e 'print $1 if /kAcceptLanguageList.*?\{(.*?)\}/s' | \ | 55 # perl -n0e 'print $1 if /kAcceptLanguageList.*?\{(.*?)\}/s' | \ |
52 # perl -nle 'print $1, if /"(.*)"/'; echo 'es-419') | \ | 56 # perl -nle 'print $1, if /"(.*)"/'; echo 'es-419') | \ |
53 # sort | perl -pe "s/(.*)\n/'\$1', /" | \ | 57 # sort | perl -pe "s/(.*)\n/'\$1', /" | \ |
54 # fold -w75 -s | perl -pe 's/^/ /;s/ $//'; echo | 58 # fold -w75 -s | perl -pe 's/^/ /;s/ $//'; echo |
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
215 | 219 |
216 Arguments: | 220 Arguments: |
217 path: path to the file | 221 path: path to the file |
218 actions: set of actions to add to | 222 actions: set of actions to add to |
219 """ | 223 """ |
220 global number_of_files_total | 224 global number_of_files_total |
221 number_of_files_total = number_of_files_total + 1 | 225 number_of_files_total = number_of_files_total + 1 |
222 # we look for the UserMetricsAction structure constructor | 226 # we look for the UserMetricsAction structure constructor |
223 # this should be on one line | 227 # this should be on one line |
224 action_re = re.compile(r'UserMetricsAction\("([^"]*)') | 228 action_re = re.compile(r'UserMetricsAction\("([^"]*)') |
229 malformed_action_re = re.compile(r'[^a-zA-Z]UserMetricsAction\([^"]') | |
225 computed_action_re = re.compile(r'UserMetrics::RecordComputedAction') | 230 computed_action_re = re.compile(r'UserMetrics::RecordComputedAction') |
226 line_number = 0 | 231 line_number = 0 |
227 for line in open(path): | 232 for line in open(path): |
228 line_number = line_number + 1 | 233 line_number = line_number + 1 |
229 match = action_re.search(line) | 234 match = action_re.search(line) |
230 if match: # Plain call to RecordAction | 235 if match: # Plain call to RecordAction |
231 actions.add(match.group(1)) | 236 actions.add(match.group(1)) |
237 elif malformed_action_re.search(line): | |
238 # Warn if this line is using RecordAction incorrectly. | |
239 print >>sys.stderr, ('WARNING: %s has malformed call to RecordAction' | |
240 ' at %d' % (path, line_number)) | |
232 elif computed_action_re.search(line): | 241 elif computed_action_re.search(line): |
233 # Warn if this file shouldn't be calling RecordComputedAction. | 242 # Warn if this file shouldn't be calling RecordComputedAction. |
234 if os.path.basename(path) not in KNOWN_COMPUTED_USERS: | 243 if os.path.basename(path) not in KNOWN_COMPUTED_USERS: |
235 print >>sys.stderr, 'WARNING: {0} has RecordComputedAction at {1}'.\ | 244 print >>sys.stderr, ('WARNING: %s has RecordComputedAction at %d' % |
236 format(path, line_number) | 245 (path, line_number)) |
237 | 246 |
238 def WalkDirectory(root_path, actions): | 247 class WebUIActionsParser(HTMLParser): |
248 """Parses an HTML file, looking for all tags with a 'metric' attribute. | |
249 Adds user actions corresponding to any metrics found. | |
250 | |
251 Arguments: | |
252 actions: set of actions to add to | |
253 """ | |
254 def __init__(self, actions): | |
255 HTMLParser.__init__(self) | |
256 self.actions = actions | |
257 | |
258 def handle_starttag(self, tag, attrs): | |
259 # We only care to examine tags that have a 'metric' attribute. | |
260 attrs = dict(attrs) | |
261 if not 'metric' in attrs: | |
262 return | |
263 | |
264 # Boolean metrics have two corresponding actions. All other metrics have | |
265 # just one corresponding action. By default, we check the 'dataType' | |
266 # attribute. | |
267 is_boolean = ('dataType' in attrs and attrs['dataType'] == 'boolean') | |
268 if 'type' in attrs and attrs['type'] in ('checkbox', 'radio'): | |
269 if attrs['type'] == 'checkbox': | |
270 # Checkboxes are boolean by default. However, their 'value-type' can | |
271 # instead be set to 'integer'. | |
272 if 'value-type' not in attrs or attrs['value-type'] in ['', 'boolean']: | |
273 is_boolean = True | |
274 else: | |
275 # Radio buttons are boolean if and only if their values are 'true' or | |
276 # 'false'. | |
277 assert(attrs['type'] == 'radio') | |
278 if 'value' in attrs and attrs['value'] in ['true', 'false']: | |
279 is_boolean = True | |
Ilya Sherman
2011/07/07 21:01:01
Actually, James, could you look at this bit of log
James Hawkins
2011/07/07 21:10:08
This part looks OK to me.
| |
280 | |
281 if is_boolean: | |
282 self.actions.add(attrs['metric'] + '_Enable') | |
283 self.actions.add(attrs['metric'] + '_Disable') | |
284 else: | |
285 self.actions.add(attrs['metric']) | |
286 | |
287 def GrepForWebUIActions(path, actions): | |
288 """Grep a WebUI source file for elements with associated metrics. | |
289 | |
290 Arguments: | |
291 path: path to the file | |
292 actions: set of actions to add to | |
293 """ | |
294 parser = WebUIActionsParser(actions) | |
295 parser.feed(open(path).read()) | |
296 parser.close() | |
297 | |
298 def WalkDirectory(root_path, actions, extensions, callback): | |
239 for path, dirs, files in os.walk(root_path): | 299 for path, dirs, files in os.walk(root_path): |
240 if '.svn' in dirs: | 300 if '.svn' in dirs: |
241 dirs.remove('.svn') | 301 dirs.remove('.svn') |
242 for file in files: | 302 for file in files: |
243 ext = os.path.splitext(file)[1] | 303 ext = os.path.splitext(file)[1] |
244 if ext in ('.cc', '.mm', '.c', '.m'): | 304 if ext in extensions: |
245 GrepForActions(os.path.join(path, file), actions) | 305 callback(os.path.join(path, file), actions) |
306 | |
307 def AddLiteralActions(actions): | |
308 """Add literal actions specified via calls to UserMetrics functions. | |
309 | |
310 Arguments: | |
311 actions: set of actions to add to. | |
312 """ | |
313 EXTENSIONS = ('.cc', '.mm', '.c', '.m') | |
314 | |
315 # Walk the source tree to process all .cc files. | |
316 chrome_root = os.path.join(path_utils.ScriptDir(), '..') | |
317 WalkDirectory(chrome_root, actions, EXTENSIONS, GrepForActions) | |
318 content_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'content') | |
319 WalkDirectory(content_root, actions, EXTENSIONS, GrepForActions) | |
320 webkit_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'webkit') | |
321 WalkDirectory(os.path.join(webkit_root, 'glue'), actions, EXTENSIONS, | |
322 GrepForActions) | |
323 WalkDirectory(os.path.join(webkit_root, 'port'), actions, EXTENSIONS, | |
324 GrepForActions) | |
325 | |
326 def AddWebUIActions(actions): | |
327 """Add user actions defined in WebUI files. | |
328 | |
329 Arguments: | |
330 actions: set of actions to add to. | |
331 """ | |
332 resources_root = os.path.join(path_utils.ScriptDir(), '..', 'browser', | |
333 'resources') | |
334 WalkDirectory(resources_root, actions, ('.html'), GrepForWebUIActions) | |
246 | 335 |
247 def main(argv): | 336 def main(argv): |
248 if '--hash' in argv: | 337 if '--hash' in argv: |
249 hash_output = True | 338 hash_output = True |
250 else: | 339 else: |
251 hash_output = False | 340 hash_output = False |
252 print >>sys.stderr, "WARNING: If you added new UMA tags, you must" + \ | 341 print >>sys.stderr, "WARNING: If you added new UMA tags, you must" + \ |
253 " use the --hash option to update chromeactions.txt." | 342 " use the --hash option to update chromeactions.txt." |
254 # if we do a hash output, we want to only append NEW actions, and we know | 343 # if we do a hash output, we want to only append NEW actions, and we know |
255 # the file we want to work on | 344 # the file we want to work on |
256 actions = set() | 345 actions = set() |
257 | 346 |
258 chromeactions_path = os.path.join(path_utils.ScriptDir(), "chromeactions.txt") | 347 chromeactions_path = os.path.join(path_utils.ScriptDir(), "chromeactions.txt") |
259 | 348 |
260 if hash_output: | 349 if hash_output: |
261 f = open(chromeactions_path) | 350 f = open(chromeactions_path) |
262 for line in f: | 351 for line in f: |
263 part = line.rpartition("\t") | 352 part = line.rpartition("\t") |
264 part = part[2].strip() | 353 part = part[2].strip() |
265 actions.add(part) | 354 actions.add(part) |
266 f.close() | 355 f.close() |
267 | 356 |
268 | 357 |
269 AddComputedActions(actions) | 358 AddComputedActions(actions) |
270 # TODO(fmantek): bring back webkit editor actions. | 359 # TODO(fmantek): bring back webkit editor actions. |
271 # AddWebKitEditorActions(actions) | 360 # AddWebKitEditorActions(actions) |
272 AddAboutFlagsActions(actions) | 361 AddAboutFlagsActions(actions) |
362 AddWebUIActions(actions) | |
273 | 363 |
274 # Walk the source tree to process all .cc files. | 364 AddLiteralActions(actions) |
275 chrome_root = os.path.join(path_utils.ScriptDir(), '..') | |
276 WalkDirectory(chrome_root, actions) | |
277 content_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'content') | |
278 WalkDirectory(content_root, actions) | |
279 webkit_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'webkit') | |
280 WalkDirectory(os.path.join(webkit_root, 'glue'), actions) | |
281 WalkDirectory(os.path.join(webkit_root, 'port'), actions) | |
282 | 365 |
283 # print "Scanned {0} number of files".format(number_of_files_total) | 366 # print "Scanned {0} number of files".format(number_of_files_total) |
284 # print "Found {0} entries".format(len(actions)) | 367 # print "Found {0} entries".format(len(actions)) |
285 | 368 |
286 AddClosedSourceActions(actions) | 369 AddClosedSourceActions(actions) |
287 AddChromeOSActions(actions) | 370 AddChromeOSActions(actions) |
288 | 371 |
289 if hash_output: | 372 if hash_output: |
290 f = open(chromeactions_path, "w") | 373 f = open(chromeactions_path, "w") |
291 | 374 |
292 | 375 |
293 # Print out the actions as a sorted list. | 376 # Print out the actions as a sorted list. |
294 for action in sorted(actions): | 377 for action in sorted(actions): |
295 if hash_output: | 378 if hash_output: |
296 hash = hashlib.md5() | 379 hash = hashlib.md5() |
297 hash.update(action) | 380 hash.update(action) |
298 print >>f, '0x%s\t%s' % (hash.hexdigest()[:16], action) | 381 print >>f, '0x%s\t%s' % (hash.hexdigest()[:16], action) |
299 else: | 382 else: |
300 print action | 383 print action |
301 | 384 |
302 if hash_output: | 385 if hash_output: |
303 print "Done. Do not forget to add chromeactions.txt to your changelist" | 386 print "Done. Do not forget to add chromeactions.txt to your changelist" |
304 | 387 |
305 if '__main__' == __name__: | 388 if '__main__' == __name__: |
306 main(sys.argv) | 389 main(sys.argv) |
OLD | NEW |