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

Side by Side Diff: telemetry/telemetry/internal/backends/chrome/desktop_browser_backend.py

Issue 2162963002: [polymer] Merge of master into polymer10-migration (Closed) Base URL: git@github.com:catapult-project/catapult.git@polymer10-migration
Patch Set: Merge polymer10-migration int polymer10-merge Created 4 years, 5 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 # Copyright 2013 The Chromium Authors. All rights reserved. 1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import datetime 5 import datetime
6 import glob 6 import glob
7 import heapq 7 import heapq
8 import logging 8 import logging
9 import os 9 import os
10 import os.path 10 import os.path
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
107 super(DesktopBrowserBackend, self).__init__( 107 super(DesktopBrowserBackend, self).__init__(
108 desktop_platform_backend, 108 desktop_platform_backend,
109 supports_tab_control=not is_content_shell, 109 supports_tab_control=not is_content_shell,
110 supports_extensions=not is_content_shell, 110 supports_extensions=not is_content_shell,
111 browser_options=browser_options) 111 browser_options=browser_options)
112 112
113 # Initialize fields so that an explosion during init doesn't break in Close. 113 # Initialize fields so that an explosion during init doesn't break in Close.
114 self._proc = None 114 self._proc = None
115 self._tmp_profile_dir = None 115 self._tmp_profile_dir = None
116 self._tmp_output_file = None 116 self._tmp_output_file = None
117 self._most_recent_symbolized_minidump_paths = set([])
117 118
118 self._executable = executable 119 self._executable = executable
119 if not self._executable: 120 if not self._executable:
120 raise Exception('Cannot create browser, no executable found!') 121 raise Exception('Cannot create browser, no executable found!')
121 122
122 assert not flash_path or os.path.exists(flash_path) 123 assert not flash_path or os.path.exists(flash_path)
123 self._flash_path = flash_path 124 self._flash_path = flash_path
124 125
125 self._is_content_shell = is_content_shell 126 self._is_content_shell = is_content_shell
126 127
127 extensions_to_load = browser_options.extensions_to_load 128 extensions_to_load = browser_options.extensions_to_load
128 129
129 if len(extensions_to_load) > 0 and is_content_shell: 130 if len(extensions_to_load) > 0 and is_content_shell:
130 raise browser_backend.ExtensionsNotSupportedException( 131 raise browser_backend.ExtensionsNotSupportedException(
131 'Content shell does not support extensions.') 132 'Content shell does not support extensions.')
132 133
133 self._browser_directory = browser_directory 134 self._browser_directory = browser_directory
134 self._port = None 135 self._port = None
135 self._tmp_minidump_dir = tempfile.mkdtemp() 136 self._tmp_minidump_dir = tempfile.mkdtemp()
136 if self.browser_options.enable_logging: 137 if self.is_logging_enabled:
137 self._log_file_path = os.path.join(tempfile.mkdtemp(), 'chrome.log') 138 self._log_file_path = os.path.join(tempfile.mkdtemp(), 'chrome.log')
138 else: 139 else:
139 self._log_file_path = None 140 self._log_file_path = None
140 141
141 self._SetupProfile() 142 self._SetupProfile()
142 143
143 @property 144 @property
145 def is_logging_enabled(self):
146 return self.browser_options.logging_verbosity in [
147 self.browser_options.NON_VERBOSE_LOGGING,
148 self.browser_options.VERBOSE_LOGGING]
149
150 @property
144 def log_file_path(self): 151 def log_file_path(self):
145 return self._log_file_path 152 return self._log_file_path
146 153
147 @property 154 @property
148 def supports_uploading_logs(self): 155 def supports_uploading_logs(self):
149 return (self.browser_options.logs_cloud_bucket and self.log_file_path and 156 return (self.browser_options.logs_cloud_bucket and self.log_file_path and
150 os.path.isfile(self.log_file_path)) 157 os.path.isfile(self.log_file_path))
151 158
152 def _SetupProfile(self): 159 def _SetupProfile(self):
153 if not self.browser_options.dont_override_profile: 160 if not self.browser_options.dont_override_profile:
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after
261 def Start(self): 268 def Start(self):
262 assert not self._proc, 'Must call Close() before Start()' 269 assert not self._proc, 'Must call Close() before Start()'
263 270
264 args = [self._executable] 271 args = [self._executable]
265 args.extend(self.GetBrowserStartupArgs()) 272 args.extend(self.GetBrowserStartupArgs())
266 if self.browser_options.startup_url: 273 if self.browser_options.startup_url:
267 args.append(self.browser_options.startup_url) 274 args.append(self.browser_options.startup_url)
268 env = os.environ.copy() 275 env = os.environ.copy()
269 env['CHROME_HEADLESS'] = '1' # Don't upload minidumps. 276 env['CHROME_HEADLESS'] = '1' # Don't upload minidumps.
270 env['BREAKPAD_DUMP_LOCATION'] = self._tmp_minidump_dir 277 env['BREAKPAD_DUMP_LOCATION'] = self._tmp_minidump_dir
271 if self.browser_options.enable_logging: 278 if self.is_logging_enabled:
272 sys.stderr.write( 279 sys.stderr.write(
273 'Chrome log file will be saved in %s\n' % self.log_file_path) 280 'Chrome log file will be saved in %s\n' % self.log_file_path)
274 env['CHROME_LOG_FILE'] = self.log_file_path 281 env['CHROME_LOG_FILE'] = self.log_file_path
275 logging.info('Starting Chrome %s', args) 282 logging.info('Starting Chrome %s', args)
276 if not self.browser_options.show_stdout: 283 if not self.browser_options.show_stdout:
277 self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0) 284 self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0)
278 self._proc = subprocess.Popen( 285 self._proc = subprocess.Popen(
279 args, stdout=self._tmp_output_file, stderr=subprocess.STDOUT, env=env) 286 args, stdout=self._tmp_output_file, stderr=subprocess.STDOUT, env=env)
280 else: 287 else:
281 self._proc = subprocess.Popen(args, env=env) 288 self._proc = subprocess.Popen(args, env=env)
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
319 # recursive call to this function. 326 # recursive call to this function.
320 print >> sys.stderr, "Can't get standard output with --show-stdout" 327 print >> sys.stderr, "Can't get standard output with --show-stdout"
321 return '' 328 return ''
322 self._tmp_output_file.flush() 329 self._tmp_output_file.flush()
323 try: 330 try:
324 with open(self._tmp_output_file.name) as f: 331 with open(self._tmp_output_file.name) as f:
325 return f.read() 332 return f.read()
326 except IOError: 333 except IOError:
327 return '' 334 return ''
328 335
329 def _GetMostRecentCrashpadMinidump(self): 336 def _GetAllCrashpadMinidumps(self):
330 os_name = self.browser.platform.GetOSName() 337 os_name = self.browser.platform.GetOSName()
331 arch_name = self.browser.platform.GetArchName() 338 arch_name = self.browser.platform.GetArchName()
332 try: 339 try:
333 crashpad_database_util = binary_manager.FetchPath( 340 crashpad_database_util = binary_manager.FetchPath(
334 'crashpad_database_util', arch_name, os_name) 341 'crashpad_database_util', arch_name, os_name)
335 if not crashpad_database_util: 342 if not crashpad_database_util:
336 logging.warning('No crashpad_database_util found') 343 logging.warning('No crashpad_database_util found')
337 return None 344 return None
338 except dependency_manager.NoPathFoundError: 345 except dependency_manager.NoPathFoundError:
339 logging.warning('No path to crashpad_database_util found') 346 logging.warning('No path to crashpad_database_util found')
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
378 # Include the last report. 385 # Include the last report.
379 if report_dict: 386 if report_dict:
380 try: 387 try:
381 report_time = ParseCrashpadDateTime(report_dict['Creation time']) 388 report_time = ParseCrashpadDateTime(report_dict['Creation time'])
382 report_path = report_dict['Path'].strip() 389 report_path = report_dict['Path'].strip()
383 reports_list.append((report_time, report_path)) 390 reports_list.append((report_time, report_path))
384 except (ValueError, KeyError) as e: 391 except (ValueError, KeyError) as e:
385 logging.warning('Crashpad report expected valid keys' 392 logging.warning('Crashpad report expected valid keys'
386 ' "Path" and "Creation time": %s', e) 393 ' "Path" and "Creation time": %s', e)
387 394
395 return reports_list
396
397 def _GetMostRecentCrashpadMinidump(self):
398 reports_list = self._GetAllCrashpadMinidumps()
388 if reports_list: 399 if reports_list:
389 _, most_recent_report_path = max(reports_list) 400 _, most_recent_report_path = max(reports_list)
390 return most_recent_report_path 401 return most_recent_report_path
391 402
392 return None 403 return None
393 404
405 def _GetBreakPadMinidumpPaths(self):
406 return glob.glob(os.path.join(self._tmp_minidump_dir, '*.dmp'))
407
394 def _GetMostRecentMinidump(self): 408 def _GetMostRecentMinidump(self):
395 # Crashpad dump layout will be the standard eventually, check it first. 409 # Crashpad dump layout will be the standard eventually, check it first.
396 most_recent_dump = self._GetMostRecentCrashpadMinidump() 410 most_recent_dump = self._GetMostRecentCrashpadMinidump()
397 411
398 # Typical breakpad format is simply dump files in a folder. 412 # Typical breakpad format is simply dump files in a folder.
399 if not most_recent_dump: 413 if not most_recent_dump:
400 logging.info('No minidump found via crashpad_database_util') 414 logging.info('No minidump found via crashpad_database_util')
401 dumps = glob.glob(os.path.join(self._tmp_minidump_dir, '*.dmp')) 415 dumps = self._GetBreakPadMinidumpPaths()
402 if dumps: 416 if dumps:
403 most_recent_dump = heapq.nlargest(1, dumps, os.path.getmtime)[0] 417 most_recent_dump = heapq.nlargest(1, dumps, os.path.getmtime)[0]
404 if most_recent_dump: 418 if most_recent_dump:
405 logging.info('Found minidump via globbing in minidump dir') 419 logging.info('Found minidump via globbing in minidump dir')
406 420
407 # As a sanity check, make sure the crash dump is recent. 421 # As a sanity check, make sure the crash dump is recent.
408 if (most_recent_dump and 422 if (most_recent_dump and
409 os.path.getmtime(most_recent_dump) < (time.time() - (5 * 60))): 423 os.path.getmtime(most_recent_dump) < (time.time() - (5 * 60))):
410 logging.warning('Crash dump is older than 5 minutes. May not be correct.') 424 logging.warning('Crash dump is older than 5 minutes. May not be correct.')
411 425
(...skipping 16 matching lines...) Expand all
428 else: 442 else:
429 return False 443 return False
430 444
431 def _GetStackFromMinidump(self, minidump): 445 def _GetStackFromMinidump(self, minidump):
432 os_name = self.browser.platform.GetOSName() 446 os_name = self.browser.platform.GetOSName()
433 if os_name == 'win': 447 if os_name == 'win':
434 cdb = self._GetCdbPath() 448 cdb = self._GetCdbPath()
435 if not cdb: 449 if not cdb:
436 logging.warning('cdb.exe not found.') 450 logging.warning('cdb.exe not found.')
437 return None 451 return None
452 # Include all the threads' stacks ("~*kb30") in addition to the
453 # ostensibly crashed stack associated with the exception context
454 # record (".ecxr;kb30"). Note that stack dumps, including that
455 # for the crashed thread, may not be as precise as the one
456 # starting from the exception context record.
457 # Specify kb instead of k in order to get four arguments listed, for
458 # easier diagnosis from stacks.
438 output = subprocess.check_output([cdb, '-y', self._browser_directory, 459 output = subprocess.check_output([cdb, '-y', self._browser_directory,
439 '-c', '.ecxr;k30;q', '-z', minidump]) 460 '-c', '.ecxr;kb30;~*kb30;q',
461 '-z', minidump])
440 # cdb output can start the stack with "ChildEBP", "Child-SP", and possibly 462 # cdb output can start the stack with "ChildEBP", "Child-SP", and possibly
441 # other things we haven't seen yet. If we can't find the start of the 463 # other things we haven't seen yet. If we can't find the start of the
442 # stack, include output from the beginning. 464 # stack, include output from the beginning.
443 stack_start = 0 465 stack_start = 0
444 stack_start_match = re.search("^Child(?:EBP|-SP)", output, re.MULTILINE) 466 stack_start_match = re.search("^Child(?:EBP|-SP)", output, re.MULTILINE)
445 if stack_start_match: 467 if stack_start_match:
446 stack_start = stack_start_match.start() 468 stack_start = stack_start_match.start()
447 stack_end = output.find('quit:') 469 stack_end = output.find('quit:')
448 return output[stack_start:stack_end] 470 return output[stack_start:stack_end]
449 471
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
483 def GetStackTrace(self): 505 def GetStackTrace(self):
484 """Returns a stack trace if a valid minidump is found, will return a tuple 506 """Returns a stack trace if a valid minidump is found, will return a tuple
485 (valid, output) where valid will be True if a valid minidump was found 507 (valid, output) where valid will be True if a valid minidump was found
486 and output will contain either an error message or the attempt to 508 and output will contain either an error message or the attempt to
487 symbolize the minidump if one was found. 509 symbolize the minidump if one was found.
488 """ 510 """
489 most_recent_dump = self._GetMostRecentMinidump() 511 most_recent_dump = self._GetMostRecentMinidump()
490 if not most_recent_dump: 512 if not most_recent_dump:
491 return (False, 'No crash dump found.') 513 return (False, 'No crash dump found.')
492 logging.info('Minidump found: %s' % most_recent_dump) 514 logging.info('Minidump found: %s' % most_recent_dump)
493 stack = self._GetStackFromMinidump(most_recent_dump) 515 return self._InternalSymbolizeMinidump(most_recent_dump)
516
517 def GetMostRecentMinidumpPath(self):
518 return self._GetMostRecentMinidump()
519
520 def GetAllMinidumpPaths(self):
521 reports_list = self._GetAllCrashpadMinidumps()
522 if reports_list:
523 return [report[1] for report in reports_list]
524 else:
525 logging.info('No minidump found via crashpad_database_util')
526 dumps = self._GetBreakPadMinidumpPaths()
527 if dumps:
528 logging.info('Found minidump via globbing in minidump dir')
529 return dumps
530 return None
531
532 def GetAllUnsymbolizedMinidumpPaths(self):
533 minidump_paths = set(self.GetAllMinidumpPaths())
534 # If we have already symbolized paths remove them from the list
535 unsymbolized_paths = (minidump_paths
536 - self._most_recent_symbolized_minidump_paths)
537 return list(unsymbolized_paths)
538
539 def SymbolizeMinidump(self, minidump_path):
540 return self._InternalSymbolizeMinidump(minidump_path)
541
542 def _InternalSymbolizeMinidump(self, minidump_path):
543 stack = self._GetStackFromMinidump(minidump_path)
494 if not stack: 544 if not stack:
495 cloud_storage_link = self._UploadMinidumpToCloudStorage(most_recent_dump) 545 cloud_storage_link = self._UploadMinidumpToCloudStorage(minidump_path)
496 error_message = ('Failed to symbolize minidump. Raw stack is uploaded to' 546 error_message = ('Failed to symbolize minidump. Raw stack is uploaded to'
497 ' cloud storage: %s.' % cloud_storage_link) 547 ' cloud storage: %s.' % cloud_storage_link)
498 return (False, error_message) 548 return (False, error_message)
499 549
550 self._most_recent_symbolized_minidump_paths.add(minidump_path)
500 return (True, stack) 551 return (True, stack)
501 552
502 def __del__(self): 553 def __del__(self):
503 self.Close() 554 self.Close()
504 555
505 def _TryCooperativeShutdown(self): 556 def _TryCooperativeShutdown(self):
506 if self.browser.platform.IsCooperativeShutdownSupported(): 557 if self.browser.platform.IsCooperativeShutdownSupported():
507 # Ideally there would be a portable, cooperative shutdown 558 # Ideally there would be a portable, cooperative shutdown
508 # mechanism for the browser. This seems difficult to do 559 # mechanism for the browser. This seems difficult to do
509 # correctly for all embedders of the content API. The only known 560 # correctly for all embedders of the content API. The only known
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
551 shutil.rmtree(self._tmp_profile_dir, ignore_errors=True) 602 shutil.rmtree(self._tmp_profile_dir, ignore_errors=True)
552 self._tmp_profile_dir = None 603 self._tmp_profile_dir = None
553 604
554 if self._tmp_output_file: 605 if self._tmp_output_file:
555 self._tmp_output_file.close() 606 self._tmp_output_file.close()
556 self._tmp_output_file = None 607 self._tmp_output_file = None
557 608
558 if self._tmp_minidump_dir: 609 if self._tmp_minidump_dir:
559 shutil.rmtree(self._tmp_minidump_dir, ignore_errors=True) 610 shutil.rmtree(self._tmp_minidump_dir, ignore_errors=True)
560 self._tmp_minidump_dir = None 611 self._tmp_minidump_dir = None
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698