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

Unified Diff: tools/telemetry/telemetry/internal/backends/chrome/desktop_browser_backend.py

Issue 1647513002: Delete tools/telemetry. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 11 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 side-by-side diff with in-line comments
Download patch
Index: tools/telemetry/telemetry/internal/backends/chrome/desktop_browser_backend.py
diff --git a/tools/telemetry/telemetry/internal/backends/chrome/desktop_browser_backend.py b/tools/telemetry/telemetry/internal/backends/chrome/desktop_browser_backend.py
deleted file mode 100644
index a7a8f0bf22ecb8b42f61b511af0f1e1759e2d514..0000000000000000000000000000000000000000
--- a/tools/telemetry/telemetry/internal/backends/chrome/desktop_browser_backend.py
+++ /dev/null
@@ -1,571 +0,0 @@
-# Copyright 2013 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import datetime
-import glob
-import heapq
-import logging
-import os
-import os.path
-import random
-import re
-import shutil
-import subprocess as subprocess
-import sys
-import tempfile
-import time
-
-from catapult_base import cloud_storage # pylint: disable=import-error
-
-
-from telemetry.internal.util import binary_manager
-from telemetry.core import exceptions
-from telemetry.core import util
-from telemetry.internal.backends import browser_backend
-from telemetry.internal.backends.chrome import chrome_browser_backend
-from telemetry.internal.util import path
-
-
-def ParseCrashpadDateTime(date_time_str):
- # Python strptime does not support time zone parsing, strip it.
- date_time_parts = date_time_str.split()
- if len(date_time_parts) >= 3:
- date_time_str = ' '.join(date_time_parts[:2])
- return datetime.datetime.strptime(date_time_str, '%Y-%m-%d %H:%M:%S')
-
-
-def GetSymbolBinary(executable, os_name):
- # Returns binary file where symbols are located.
- if os_name == 'mac':
- version_dir = os.path.join(os.path.dirname(executable),
- '..',
- 'Versions')
- for version_num in os.listdir(version_dir):
- framework_file = os.path.join(version_dir,
- version_num,
- 'Chromium Framework.framework',
- 'Chromium Framework')
- if os.path.isfile(framework_file):
- return framework_file
-
- return executable
-
-
-class DesktopBrowserBackend(chrome_browser_backend.ChromeBrowserBackend):
- """The backend for controlling a locally-executed browser instance, on Linux,
- Mac or Windows.
- """
- def __init__(self, desktop_platform_backend, browser_options, executable,
- flash_path, is_content_shell, browser_directory,
- output_profile_path, extensions_to_load):
- super(DesktopBrowserBackend, self).__init__(
- desktop_platform_backend,
- supports_tab_control=not is_content_shell,
- supports_extensions=not is_content_shell,
- browser_options=browser_options,
- output_profile_path=output_profile_path,
- extensions_to_load=extensions_to_load)
-
- # Initialize fields so that an explosion during init doesn't break in Close.
- self._proc = None
- self._tmp_profile_dir = None
- self._tmp_output_file = None
-
- self._executable = executable
- if not self._executable:
- raise Exception('Cannot create browser, no executable found!')
-
- assert not flash_path or os.path.exists(flash_path)
- self._flash_path = flash_path
-
- self._is_content_shell = is_content_shell
-
- if len(extensions_to_load) > 0 and is_content_shell:
- raise browser_backend.ExtensionsNotSupportedException(
- 'Content shell does not support extensions.')
-
- self._browser_directory = browser_directory
- self._port = None
- self._tmp_minidump_dir = tempfile.mkdtemp()
- self._crash_service = None
- if self.browser_options.enable_logging:
- self._log_file_path = os.path.join(tempfile.mkdtemp(), 'chrome.log')
- else:
- self._log_file_path = None
-
- self._SetupProfile()
-
- @property
- def log_file_path(self):
- return self._log_file_path
-
- @property
- def supports_uploading_logs(self):
- return (self.browser_options.logs_cloud_bucket and self.log_file_path and
- os.path.isfile(self.log_file_path))
-
- def _SetupProfile(self):
- if not self.browser_options.dont_override_profile:
- if self._output_profile_path:
- self._tmp_profile_dir = self._output_profile_path
- else:
- self._tmp_profile_dir = tempfile.mkdtemp()
-
- profile_dir = self.browser_options.profile_dir
- if profile_dir:
- assert self._tmp_profile_dir != profile_dir
- if self._is_content_shell:
- logging.critical('Profiles cannot be used with content shell')
- sys.exit(1)
- logging.info("Using profile directory:'%s'." % profile_dir)
- shutil.rmtree(self._tmp_profile_dir)
- shutil.copytree(profile_dir, self._tmp_profile_dir)
- # No matter whether we're using an existing profile directory or
- # creating a new one, always delete the well-known file containing
- # the active DevTools port number.
- port_file = self._GetDevToolsActivePortPath()
- if os.path.isfile(port_file):
- try:
- os.remove(port_file)
- except Exception as e:
- logging.critical('Unable to remove DevToolsActivePort file: %s' % e)
- sys.exit(1)
-
- def _GetDevToolsActivePortPath(self):
- return os.path.join(self.profile_directory, 'DevToolsActivePort')
-
- def _GetCrashServicePipeName(self):
- # Ensure a unique pipe name by using the name of the temp dir.
- pipe = r'\\.\pipe\%s_service' % os.path.basename(self._tmp_minidump_dir)
- return pipe
-
- def _StartCrashService(self):
- os_name = self.browser.platform.GetOSName()
- if os_name != 'win':
- return None
- arch_name = self.browser.platform.GetArchName()
- command = binary_manager.FetchPath('crash_service', arch_name, os_name)
- if not command:
- logging.warning('crash_service.exe not found for %s %s',
- arch_name, os_name)
- return None
- if not os.path.exists(command):
- logging.warning('crash_service.exe not found for %s %s',
- arch_name, os_name)
- return None
-
- try:
- crash_service = subprocess.Popen([
- command,
- '--no-window',
- '--dumps-dir=%s' % self._tmp_minidump_dir,
- '--pipe-name=%s' % self._GetCrashServicePipeName()])
- except Exception:
- logging.error(
- 'Failed to run %s --no-window --dump-dir=%s --pip-name=%s' % (
- command, self._tmp_minidump_dir, self._GetCrashServicePipeName()))
- logging.error('Running on platform: %s and arch: %s.', os_name, arch_name)
- wmic_stdout, _ = subprocess.Popen(
- ['wmic', 'process', 'get', 'CommandLine,Name,ProcessId,ParentProcessId',
- '/format:csv'], stdout=subprocess.PIPE).communicate()
- logging.error('Current running processes:\n%s' % wmic_stdout)
- raise
- return crash_service
-
- def _GetCdbPath(self):
- possible_paths = (
- 'Debugging Tools For Windows',
- 'Debugging Tools For Windows (x86)',
- 'Debugging Tools For Windows (x64)',
- os.path.join('Windows Kits', '8.0', 'Debuggers', 'x86'),
- os.path.join('Windows Kits', '8.0', 'Debuggers', 'x64'),
- os.path.join('win_toolchain', 'vs2013_files', 'win8sdk', 'Debuggers',
- 'x86'),
- os.path.join('win_toolchain', 'vs2013_files', 'win8sdk', 'Debuggers',
- 'x64'),
- )
- for possible_path in possible_paths:
- app_path = os.path.join(possible_path, 'cdb.exe')
- app_path = path.FindInstalledWindowsApplication(app_path)
- if app_path:
- return app_path
- return None
-
- def HasBrowserFinishedLaunching(self):
- # In addition to the functional check performed by the base class, quickly
- # check if the browser process is still alive.
- if not self.IsBrowserRunning():
- raise exceptions.ProcessGoneException(
- "Return code: %d" % self._proc.returncode)
- # Start DevTools on an ephemeral port and wait for the well-known file
- # containing the port number to exist.
- port_file = self._GetDevToolsActivePortPath()
- if not os.path.isfile(port_file):
- # File isn't ready yet. Return false. Will retry.
- return False
- # Attempt to avoid reading the file until it's populated.
- got_port = False
- try:
- if os.stat(port_file).st_size > 0:
- with open(port_file) as f:
- port_string = f.read()
- self._port = int(port_string)
- logging.info('Discovered ephemeral port %s' % self._port)
- got_port = True
- except Exception:
- # Both stat and open can throw exceptions.
- pass
- if not got_port:
- # File isn't ready yet. Return false. Will retry.
- return False
- return super(DesktopBrowserBackend, self).HasBrowserFinishedLaunching()
-
- def GetBrowserStartupArgs(self):
- args = super(DesktopBrowserBackend, self).GetBrowserStartupArgs()
- self._port = 0
- logging.info('Requested remote debugging port: %d' % self._port)
- args.append('--remote-debugging-port=%i' % self._port)
- args.append('--enable-crash-reporter-for-testing')
- if not self._is_content_shell:
- args.append('--window-size=1280,1024')
- if self._flash_path:
- args.append('--ppapi-flash-path=%s' % self._flash_path)
- if not self.browser_options.dont_override_profile:
- args.append('--user-data-dir=%s' % self._tmp_profile_dir)
- else:
- args.append('--data-path=%s' % self._tmp_profile_dir)
-
- trace_config_file = (self.platform_backend.tracing_controller_backend
- .GetChromeTraceConfigFile())
- if trace_config_file:
- args.append('--trace-config-file=%s' % trace_config_file)
- return args
-
- def Start(self):
- assert not self._proc, 'Must call Close() before Start()'
-
- args = [self._executable]
- args.extend(self.GetBrowserStartupArgs())
- if self.browser_options.startup_url:
- args.append(self.browser_options.startup_url)
- env = os.environ.copy()
- env['CHROME_HEADLESS'] = '1' # Don't upload minidumps.
- env['BREAKPAD_DUMP_LOCATION'] = self._tmp_minidump_dir
- env['CHROME_BREAKPAD_PIPE_NAME'] = self._GetCrashServicePipeName()
- if self.browser_options.enable_logging:
- sys.stderr.write(
- 'Chrome log file will be saved in %s\n' % self.log_file_path)
- env['CHROME_LOG_FILE'] = self.log_file_path
- self._crash_service = self._StartCrashService()
- logging.info('Starting Chrome %s', args)
- if not self.browser_options.show_stdout:
- self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0)
- self._proc = subprocess.Popen(
- args, stdout=self._tmp_output_file, stderr=subprocess.STDOUT, env=env)
- else:
- self._proc = subprocess.Popen(args, env=env)
-
- try:
- self._WaitForBrowserToComeUp()
- # browser is foregrounded by default on Windows and Linux, but not Mac.
- if self.browser.platform.GetOSName() == 'mac':
- subprocess.Popen([
- 'osascript', '-e', ('tell application "%s" to activate' %
- self._executable)])
- self._InitDevtoolsClientBackend()
- if self._supports_extensions:
- self._WaitForExtensionsToLoad()
- except:
- self.Close()
- raise
-
- @property
- def pid(self):
- if self._proc:
- return self._proc.pid
- return None
-
- @property
- def browser_directory(self):
- return self._browser_directory
-
- @property
- def profile_directory(self):
- return self._tmp_profile_dir
-
- def IsBrowserRunning(self):
- return self._proc and self._proc.poll() == None
-
- def GetStandardOutput(self):
- if not self._tmp_output_file:
- if self.browser_options.show_stdout:
- # This can happen in the case that loading the Chrome binary fails.
- # We print rather than using logging here, because that makes a
- # recursive call to this function.
- print >> sys.stderr, "Can't get standard output with --show-stdout"
- return ''
- self._tmp_output_file.flush()
- try:
- with open(self._tmp_output_file.name) as f:
- return f.read()
- except IOError:
- return ''
-
- def _GetMostRecentCrashpadMinidump(self):
- os_name = self.browser.platform.GetOSName()
- arch_name = self.browser.platform.GetArchName()
- crashpad_database_util = binary_manager.FetchPath(
- 'crashpad_database_util', arch_name, os_name)
- if not crashpad_database_util:
- return None
-
- report_output = subprocess.check_output([
- crashpad_database_util, '--database=' + self._tmp_minidump_dir,
- '--show-pending-reports', '--show-completed-reports',
- '--show-all-report-info'])
-
- last_indentation = -1
- reports_list = []
- report_dict = {}
- for report_line in report_output.splitlines():
- # Report values are grouped together by the same indentation level.
- current_indentation = 0
- for report_char in report_line:
- if not report_char.isspace():
- break
- current_indentation += 1
-
- # Decrease in indentation level indicates a new report is being printed.
- if current_indentation >= last_indentation:
- report_key, report_value = report_line.split(':', 1)
- if report_value:
- report_dict[report_key.strip()] = report_value.strip()
- elif report_dict:
- try:
- report_time = ParseCrashpadDateTime(report_dict['Creation time'])
- report_path = report_dict['Path'].strip()
- reports_list.append((report_time, report_path))
- except (ValueError, KeyError) as e:
- logging.warning('Crashpad report expected valid keys'
- ' "Path" and "Creation time": %s', e)
- finally:
- report_dict = {}
-
- last_indentation = current_indentation
-
- # Include the last report.
- if report_dict:
- try:
- report_time = ParseCrashpadDateTime(report_dict['Creation time'])
- report_path = report_dict['Path'].strip()
- reports_list.append((report_time, report_path))
- except (ValueError, KeyError) as e:
- logging.warning('Crashpad report expected valid keys'
- ' "Path" and "Creation time": %s', e)
-
- if reports_list:
- _, most_recent_report_path = max(reports_list)
- return most_recent_report_path
-
- return None
-
- def _GetMostRecentMinidump(self):
- # Crashpad dump layout will be the standard eventually, check it first.
- most_recent_dump = self._GetMostRecentCrashpadMinidump()
-
- # Typical breakpad format is simply dump files in a folder.
- if not most_recent_dump:
- dumps = glob.glob(os.path.join(self._tmp_minidump_dir, '*.dmp'))
- if dumps:
- most_recent_dump = heapq.nlargest(1, dumps, os.path.getmtime)[0]
-
- # As a sanity check, make sure the crash dump is recent.
- if (most_recent_dump and
- os.path.getmtime(most_recent_dump) < (time.time() - (5 * 60))):
- logging.warning('Crash dump is older than 5 minutes. May not be correct.')
-
- return most_recent_dump
-
- def _IsExecutableStripped(self):
- if self.browser.platform.GetOSName() == 'mac':
- try:
- symbols = subprocess.check_output(['/usr/bin/nm', self._executable])
- except subprocess.CalledProcessError as err:
- logging.warning('Error when checking whether executable is stripped: %s'
- % err.output)
- # Just assume that binary is stripped to skip breakpad symbol generation
- # if this check failed.
- return True
- num_symbols = len(symbols.splitlines())
- # We assume that if there are more than 10 symbols the executable is not
- # stripped.
- return num_symbols < 10
- else:
- return False
-
- def _GetStackFromMinidump(self, minidump):
- os_name = self.browser.platform.GetOSName()
- if os_name == 'win':
- cdb = self._GetCdbPath()
- if not cdb:
- logging.warning('cdb.exe not found.')
- return None
- output = subprocess.check_output([cdb, '-y', self._browser_directory,
- '-c', '.ecxr;k30;q', '-z', minidump])
- # cdb output can start the stack with "ChildEBP", "Child-SP", and possibly
- # other things we haven't seen yet. If we can't find the start of the
- # stack, include output from the beginning.
- stack_start = 0
- stack_start_match = re.search("^Child(?:EBP|-SP)", output, re.MULTILINE)
- if stack_start_match:
- stack_start = stack_start_match.start()
- stack_end = output.find('quit:')
- return output[stack_start:stack_end]
-
- arch_name = self.browser.platform.GetArchName()
- stackwalk = binary_manager.FetchPath(
- 'minidump_stackwalk', arch_name, os_name)
- if not stackwalk:
- logging.warning('minidump_stackwalk binary not found.')
- return None
-
- with open(minidump, 'rb') as infile:
- minidump += '.stripped'
- with open(minidump, 'wb') as outfile:
- outfile.write(''.join(infile.read().partition('MDMP')[1:]))
-
- symbols_path = os.path.join(self._tmp_minidump_dir, 'symbols')
-
- symbols = glob.glob(os.path.join(self._browser_directory, '*.breakpad*'))
- if symbols:
- for symbol in sorted(symbols, key=os.path.getmtime, reverse=True):
- if not os.path.isfile(symbol):
- continue
- with open(symbol, 'r') as f:
- fields = f.readline().split()
- if not fields:
- continue
- sha = fields[3]
- binary = ' '.join(fields[4:])
- symbol_path = os.path.join(symbols_path, binary, sha)
- if os.path.exists(symbol_path):
- continue
- os.makedirs(symbol_path)
- shutil.copyfile(symbol, os.path.join(symbol_path, binary + '.sym'))
- else:
-
- # On some platforms generating the symbol table can be very time
- # consuming, skip it if there's nothing to dump.
- if self._IsExecutableStripped():
- logging.info('%s appears to be stripped, skipping symbol dump.' % (
- self._executable))
- return
-
- logging.info('Dumping breakpad symbols.')
- generate_breakpad_symbols_command = binary_manager.FetchPath(
- 'generate_breakpad_symbols', arch_name, os_name)
- if generate_breakpad_symbols_command is None:
- return
-
- cmd = [
- sys.executable,
- generate_breakpad_symbols_command,
- '--binary=%s' % GetSymbolBinary(self._executable,
- self.browser.platform.GetOSName()),
- '--symbols-dir=%s' % symbols_path,
- '--build-dir=%s' % self._browser_directory,
- ]
-
- try:
- subprocess.check_output(cmd, stderr=open(os.devnull, 'w'))
- except subprocess.CalledProcessError:
- logging.warning('Failed to execute "%s"' % ' '.join(cmd))
- return
-
- return subprocess.check_output([stackwalk, minidump, symbols_path],
- stderr=open(os.devnull, 'w'))
-
- def _UploadMinidumpToCloudStorage(self, minidump_path):
- """ Upload minidump_path to cloud storage and return the cloud storage url.
- """
- remote_path = ('minidump-%s-%i.dmp' %
- (datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'),
- random.randint(0, 1000000)))
- try:
- return cloud_storage.Insert(cloud_storage.TELEMETRY_OUTPUT, remote_path,
- minidump_path)
- except cloud_storage.CloudStorageError as err:
- logging.error('Cloud storage error while trying to upload dump: %s' %
- repr(err))
- return '<Missing link>'
-
- def GetStackTrace(self):
- most_recent_dump = self._GetMostRecentMinidump()
- if not most_recent_dump:
- return 'No crash dump found.'
- logging.info('Minidump found: %s' % most_recent_dump)
- stack = self._GetStackFromMinidump(most_recent_dump)
- if not stack:
- cloud_storage_link = self._UploadMinidumpToCloudStorage(most_recent_dump)
- return ('Failed to symbolize minidump. Raw stack is uploaded to cloud '
- 'storage: %s.' % cloud_storage_link)
- return stack
-
- def __del__(self):
- self.Close()
-
- def _TryCooperativeShutdown(self):
- if self.browser.platform.IsCooperativeShutdownSupported():
- # Ideally there would be a portable, cooperative shutdown
- # mechanism for the browser. This seems difficult to do
- # correctly for all embedders of the content API. The only known
- # problem with unclean shutdown of the browser process is on
- # Windows, where suspended child processes frequently leak. For
- # now, just solve this particular problem. See Issue 424024.
- if self.browser.platform.CooperativelyShutdown(self._proc, "chrome"):
- try:
- util.WaitFor(lambda: not self.IsBrowserRunning(), timeout=5)
- logging.info('Successfully shut down browser cooperatively')
- except exceptions.TimeoutException as e:
- logging.warning('Failed to cooperatively shutdown. ' +
- 'Proceeding to terminate: ' + str(e))
-
- def Close(self):
- super(DesktopBrowserBackend, self).Close()
-
- if self.IsBrowserRunning():
- self._TryCooperativeShutdown()
-
- # Shutdown politely if the profile may be used again.
- if self._output_profile_path and self.IsBrowserRunning():
- self._proc.terminate()
- try:
- util.WaitFor(lambda: not self.IsBrowserRunning(), timeout=5)
- self._proc = None
- except exceptions.TimeoutException:
- logging.warning('Failed to gracefully shutdown. Proceeding to kill.')
-
- # Shutdown aggressively if the above failed or if the profile is temporary.
- if self.IsBrowserRunning():
- self._proc.kill()
- self._proc = None
-
- if self._crash_service:
- self._crash_service.kill()
- self._crash_service = None
-
- if self._output_profile_path:
- # If we need the output then double check that it exists.
- if not (self._tmp_profile_dir and os.path.exists(self._tmp_profile_dir)):
- raise Exception("No profile directory generated by Chrome: '%s'." %
- self._tmp_profile_dir)
- else:
- # If we don't need the profile after the run then cleanup.
- if self._tmp_profile_dir and os.path.exists(self._tmp_profile_dir):
- shutil.rmtree(self._tmp_profile_dir, ignore_errors=True)
- self._tmp_profile_dir = None
-
- if self._tmp_output_file:
- self._tmp_output_file.close()
- self._tmp_output_file = None

Powered by Google App Engine
This is Rietveld 408576698