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 """Grep a source file for calls to UserMetrics functions. | 219 """Grep a source file for calls to UserMetrics functions. |
216 | 220 |
217 Arguments: | 221 Arguments: |
218 path: path to the file | 222 path: path to the file |
219 actions: set of actions to add to | 223 actions: set of actions to add to |
220 """ | 224 """ |
221 global number_of_files_total | 225 global number_of_files_total |
222 number_of_files_total = number_of_files_total + 1 | 226 number_of_files_total = number_of_files_total + 1 |
223 # we look for the UserMetricsAction structure constructor | 227 # we look for the UserMetricsAction structure constructor |
224 # this should be on one line | 228 # this should be on one line |
225 action_re = re.compile(r'UserMetricsAction\("([^"]*)') | 229 action_re = re.compile(r'[^a-zA-Z]UserMetricsAction\("([^"]*)') |
| 230 malformed_action_re = re.compile(r'[^a-zA-Z]UserMetricsAction\([^"]') |
226 computed_action_re = re.compile(r'UserMetrics::RecordComputedAction') | 231 computed_action_re = re.compile(r'UserMetrics::RecordComputedAction') |
227 line_number = 0 | 232 line_number = 0 |
228 for line in open(path): | 233 for line in open(path): |
229 line_number = line_number + 1 | 234 line_number = line_number + 1 |
230 match = action_re.search(line) | 235 match = action_re.search(line) |
231 if match: # Plain call to RecordAction | 236 if match: # Plain call to RecordAction |
232 actions.add(match.group(1)) | 237 actions.add(match.group(1)) |
| 238 elif malformed_action_re.search(line): |
| 239 # Warn if this line is using RecordAction incorrectly. |
| 240 print >>sys.stderr, ('WARNING: %s has malformed call to RecordAction' |
| 241 ' at %d' % (path, line_number)) |
233 elif computed_action_re.search(line): | 242 elif computed_action_re.search(line): |
234 # Warn if this file shouldn't be calling RecordComputedAction. | 243 # Warn if this file shouldn't be calling RecordComputedAction. |
235 if os.path.basename(path) not in KNOWN_COMPUTED_USERS: | 244 if os.path.basename(path) not in KNOWN_COMPUTED_USERS: |
236 print >>sys.stderr, 'WARNING: {0} has RecordComputedAction at {1}'.\ | 245 print >>sys.stderr, ('WARNING: %s has RecordComputedAction at %d' % |
237 format(path, line_number) | 246 (path, line_number)) |
238 | 247 |
239 def WalkDirectory(root_path, actions): | 248 class WebUIActionsParser(HTMLParser): |
| 249 """Parses an HTML file, looking for all tags with a 'metric' attribute. |
| 250 Adds user actions corresponding to any metrics found. |
| 251 |
| 252 Arguments: |
| 253 actions: set of actions to add to |
| 254 """ |
| 255 def __init__(self, actions): |
| 256 HTMLParser.__init__(self) |
| 257 self.actions = actions |
| 258 |
| 259 def handle_starttag(self, tag, attrs): |
| 260 # We only care to examine tags that have a 'metric' attribute. |
| 261 attrs = dict(attrs) |
| 262 if not 'metric' in attrs: |
| 263 return |
| 264 |
| 265 # Boolean metrics have two corresponding actions. All other metrics have |
| 266 # just one corresponding action. By default, we check the 'dataType' |
| 267 # attribute. |
| 268 is_boolean = ('dataType' in attrs and attrs['dataType'] == 'boolean') |
| 269 if 'type' in attrs and attrs['type'] in ('checkbox', 'radio'): |
| 270 if attrs['type'] == 'checkbox': |
| 271 # Checkboxes are boolean by default. However, their 'value-type' can |
| 272 # instead be set to 'integer'. |
| 273 if 'value-type' not in attrs or attrs['value-type'] in ['', 'boolean']: |
| 274 is_boolean = True |
| 275 else: |
| 276 # Radio buttons are boolean if and only if their values are 'true' or |
| 277 # 'false'. |
| 278 assert(attrs['type'] == 'radio') |
| 279 if 'value' in attrs and attrs['value'] in ['true', 'false']: |
| 280 is_boolean = True |
| 281 |
| 282 if is_boolean: |
| 283 self.actions.add(attrs['metric'] + '_Enable') |
| 284 self.actions.add(attrs['metric'] + '_Disable') |
| 285 else: |
| 286 self.actions.add(attrs['metric']) |
| 287 |
| 288 def GrepForWebUIActions(path, actions): |
| 289 """Grep a WebUI source file for elements with associated metrics. |
| 290 |
| 291 Arguments: |
| 292 path: path to the file |
| 293 actions: set of actions to add to |
| 294 """ |
| 295 parser = WebUIActionsParser(actions) |
| 296 parser.feed(open(path).read()) |
| 297 parser.close() |
| 298 |
| 299 def WalkDirectory(root_path, actions, extensions, callback): |
240 for path, dirs, files in os.walk(root_path): | 300 for path, dirs, files in os.walk(root_path): |
241 if '.svn' in dirs: | 301 if '.svn' in dirs: |
242 dirs.remove('.svn') | 302 dirs.remove('.svn') |
243 for file in files: | 303 for file in files: |
244 ext = os.path.splitext(file)[1] | 304 ext = os.path.splitext(file)[1] |
245 if ext in ('.cc', '.mm', '.c', '.m'): | 305 if ext in extensions: |
246 GrepForActions(os.path.join(path, file), actions) | 306 callback(os.path.join(path, file), actions) |
| 307 |
| 308 def AddLiteralActions(actions): |
| 309 """Add literal actions specified via calls to UserMetrics functions. |
| 310 |
| 311 Arguments: |
| 312 actions: set of actions to add to. |
| 313 """ |
| 314 EXTENSIONS = ('.cc', '.mm', '.c', '.m') |
| 315 |
| 316 # Walk the source tree to process all .cc files. |
| 317 chrome_root = os.path.join(path_utils.ScriptDir(), '..') |
| 318 WalkDirectory(chrome_root, actions, EXTENSIONS, GrepForActions) |
| 319 content_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'content') |
| 320 WalkDirectory(content_root, actions, EXTENSIONS, GrepForActions) |
| 321 webkit_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'webkit') |
| 322 WalkDirectory(os.path.join(webkit_root, 'glue'), actions, EXTENSIONS, |
| 323 GrepForActions) |
| 324 WalkDirectory(os.path.join(webkit_root, 'port'), actions, EXTENSIONS, |
| 325 GrepForActions) |
| 326 |
| 327 def AddWebUIActions(actions): |
| 328 """Add user actions defined in WebUI files. |
| 329 |
| 330 Arguments: |
| 331 actions: set of actions to add to. |
| 332 """ |
| 333 resources_root = os.path.join(path_utils.ScriptDir(), '..', 'browser', |
| 334 'resources') |
| 335 WalkDirectory(resources_root, actions, ('.html'), GrepForWebUIActions) |
247 | 336 |
248 def main(argv): | 337 def main(argv): |
249 if '--hash' in argv: | 338 if '--hash' in argv: |
250 hash_output = True | 339 hash_output = True |
251 else: | 340 else: |
252 hash_output = False | 341 hash_output = False |
253 print >>sys.stderr, "WARNING: If you added new UMA tags, you must" + \ | 342 print >>sys.stderr, "WARNING: If you added new UMA tags, you must" + \ |
254 " use the --hash option to update chromeactions.txt." | 343 " use the --hash option to update chromeactions.txt." |
255 # if we do a hash output, we want to only append NEW actions, and we know | 344 # if we do a hash output, we want to only append NEW actions, and we know |
256 # the file we want to work on | 345 # the file we want to work on |
257 actions = set() | 346 actions = set() |
258 | 347 |
259 chromeactions_path = os.path.join(path_utils.ScriptDir(), "chromeactions.txt") | 348 chromeactions_path = os.path.join(path_utils.ScriptDir(), "chromeactions.txt") |
260 | 349 |
261 if hash_output: | 350 if hash_output: |
262 f = open(chromeactions_path) | 351 f = open(chromeactions_path) |
263 for line in f: | 352 for line in f: |
264 part = line.rpartition("\t") | 353 part = line.rpartition("\t") |
265 part = part[2].strip() | 354 part = part[2].strip() |
266 actions.add(part) | 355 actions.add(part) |
267 f.close() | 356 f.close() |
268 | 357 |
269 | 358 |
270 AddComputedActions(actions) | 359 AddComputedActions(actions) |
271 # TODO(fmantek): bring back webkit editor actions. | 360 # TODO(fmantek): bring back webkit editor actions. |
272 # AddWebKitEditorActions(actions) | 361 # AddWebKitEditorActions(actions) |
273 AddAboutFlagsActions(actions) | 362 AddAboutFlagsActions(actions) |
| 363 AddWebUIActions(actions) |
274 | 364 |
275 # Walk the source tree to process all .cc files. | 365 AddLiteralActions(actions) |
276 chrome_root = os.path.join(path_utils.ScriptDir(), '..') | |
277 WalkDirectory(chrome_root, actions) | |
278 content_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'content') | |
279 WalkDirectory(content_root, actions) | |
280 webkit_root = os.path.join(path_utils.ScriptDir(), '..', '..', 'webkit') | |
281 WalkDirectory(os.path.join(webkit_root, 'glue'), actions) | |
282 WalkDirectory(os.path.join(webkit_root, 'port'), actions) | |
283 | 366 |
284 # print "Scanned {0} number of files".format(number_of_files_total) | 367 # print "Scanned {0} number of files".format(number_of_files_total) |
285 # print "Found {0} entries".format(len(actions)) | 368 # print "Found {0} entries".format(len(actions)) |
286 | 369 |
287 AddClosedSourceActions(actions) | 370 AddClosedSourceActions(actions) |
288 AddChromeOSActions(actions) | 371 AddChromeOSActions(actions) |
289 | 372 |
290 if hash_output: | 373 if hash_output: |
291 f = open(chromeactions_path, "w") | 374 f = open(chromeactions_path, "w") |
292 | 375 |
293 | 376 |
294 # Print out the actions as a sorted list. | 377 # Print out the actions as a sorted list. |
295 for action in sorted(actions): | 378 for action in sorted(actions): |
296 if hash_output: | 379 if hash_output: |
297 hash = hashlib.md5() | 380 hash = hashlib.md5() |
298 hash.update(action) | 381 hash.update(action) |
299 print >>f, '0x%s\t%s' % (hash.hexdigest()[:16], action) | 382 print >>f, '0x%s\t%s' % (hash.hexdigest()[:16], action) |
300 else: | 383 else: |
301 print action | 384 print action |
302 | 385 |
303 if hash_output: | 386 if hash_output: |
304 print "Done. Do not forget to add chromeactions.txt to your changelist" | 387 print "Done. Do not forget to add chromeactions.txt to your changelist" |
305 | 388 |
306 if '__main__' == __name__: | 389 if '__main__' == __name__: |
307 main(sys.argv) | 390 main(sys.argv) |
OLD | NEW |