| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 import glob | |
| 5 import heapq | |
| 6 import logging | |
| 7 import os | |
| 8 import subprocess as subprocess | |
| 9 import shutil | |
| 10 import sys | |
| 11 import tempfile | |
| 12 import time | |
| 13 | |
| 14 from telemetry.core import util | |
| 15 from telemetry.core.backends import browser_backend | |
| 16 from telemetry.core.backends.chrome import chrome_browser_backend | |
| 17 | |
| 18 class DesktopBrowserBackend(chrome_browser_backend.ChromeBrowserBackend): | |
| 19 """The backend for controlling a locally-executed browser instance, on Linux, | |
| 20 Mac or Windows. | |
| 21 """ | |
| 22 def __init__(self, options, executable, flash_path, is_content_shell, | |
| 23 browser_directory, delete_profile_dir_after_run=True): | |
| 24 super(DesktopBrowserBackend, self).__init__( | |
| 25 is_content_shell=is_content_shell, | |
| 26 supports_extensions=not is_content_shell, | |
| 27 options=options) | |
| 28 | |
| 29 # Initialize fields so that an explosion during init doesn't break in Close. | |
| 30 self._proc = None | |
| 31 self._tmp_profile_dir = None | |
| 32 self._tmp_output_file = None | |
| 33 | |
| 34 self._executable = executable | |
| 35 if not self._executable: | |
| 36 raise Exception('Cannot create browser, no executable found!') | |
| 37 | |
| 38 self._flash_path = flash_path | |
| 39 if self._flash_path and not os.path.exists(self._flash_path): | |
| 40 logging.warning(('Could not find flash at %s. Running without flash.\n\n' | |
| 41 'To fix this see http://go/read-src-internal') % | |
| 42 self._flash_path) | |
| 43 self._flash_path = None | |
| 44 | |
| 45 if len(options.extensions_to_load) > 0 and is_content_shell: | |
| 46 raise browser_backend.ExtensionsNotSupportedException( | |
| 47 'Content shell does not support extensions.') | |
| 48 | |
| 49 self._browser_directory = browser_directory | |
| 50 self._port = util.GetAvailableLocalPort() | |
| 51 self._profile_dir = None | |
| 52 self._supports_net_benchmarking = True | |
| 53 self._delete_profile_dir_after_run = delete_profile_dir_after_run | |
| 54 self._tmp_minidump_dir = tempfile.mkdtemp() | |
| 55 | |
| 56 self._SetupProfile() | |
| 57 | |
| 58 def _SetupProfile(self): | |
| 59 if not self.options.dont_override_profile: | |
| 60 self._tmp_profile_dir = tempfile.mkdtemp() | |
| 61 profile_dir = self._profile_dir or self.options.profile_dir | |
| 62 if profile_dir: | |
| 63 if self.is_content_shell: | |
| 64 logging.critical('Profiles cannot be used with content shell') | |
| 65 sys.exit(1) | |
| 66 shutil.rmtree(self._tmp_profile_dir) | |
| 67 shutil.copytree(profile_dir, self._tmp_profile_dir) | |
| 68 | |
| 69 def _LaunchBrowser(self): | |
| 70 args = [self._executable] | |
| 71 args.extend(self.GetBrowserStartupArgs()) | |
| 72 env = os.environ.copy() | |
| 73 env['CHROME_HEADLESS'] = '1' # Don't upload minidumps. | |
| 74 env['BREAKPAD_DUMP_LOCATION'] = self._tmp_minidump_dir | |
| 75 if not self.options.show_stdout: | |
| 76 self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0) | |
| 77 self._proc = subprocess.Popen( | |
| 78 args, stdout=self._tmp_output_file, stderr=subprocess.STDOUT, env=env) | |
| 79 else: | |
| 80 self._proc = subprocess.Popen(args, env=env) | |
| 81 | |
| 82 try: | |
| 83 self._WaitForBrowserToComeUp() | |
| 84 self._PostBrowserStartupInitialization() | |
| 85 except: | |
| 86 self.Close() | |
| 87 raise | |
| 88 | |
| 89 def GetBrowserStartupArgs(self): | |
| 90 args = super(DesktopBrowserBackend, self).GetBrowserStartupArgs() | |
| 91 args.append('--remote-debugging-port=%i' % self._port) | |
| 92 args.append('--enable-crash-reporter-for-testing') | |
| 93 if not self.is_content_shell: | |
| 94 args.append('--window-size=1280,1024') | |
| 95 if self._flash_path: | |
| 96 args.append('--ppapi-flash-path=%s' % self._flash_path) | |
| 97 if self._supports_net_benchmarking: | |
| 98 args.append('--enable-net-benchmarking') | |
| 99 else: | |
| 100 args.append('--enable-benchmarking') | |
| 101 if not self.options.dont_override_profile: | |
| 102 args.append('--user-data-dir=%s' % self._tmp_profile_dir) | |
| 103 return args | |
| 104 | |
| 105 def SetProfileDirectory(self, profile_dir): | |
| 106 # Make sure _profile_dir hasn't already been set. | |
| 107 assert self._profile_dir is None | |
| 108 | |
| 109 if self.is_content_shell: | |
| 110 logging.critical('Profile creation cannot be used with content shell') | |
| 111 sys.exit(1) | |
| 112 | |
| 113 self._profile_dir = profile_dir | |
| 114 | |
| 115 def Start(self): | |
| 116 self._LaunchBrowser() | |
| 117 | |
| 118 # For old chrome versions, might have to relaunch to have the | |
| 119 # correct net_benchmarking switch. | |
| 120 if self._chrome_branch_number < 1418: | |
| 121 self.Close() | |
| 122 self._supports_net_benchmarking = False | |
| 123 self._LaunchBrowser() | |
| 124 | |
| 125 @property | |
| 126 def pid(self): | |
| 127 if self._proc: | |
| 128 return self._proc.pid | |
| 129 return None | |
| 130 | |
| 131 @property | |
| 132 def browser_directory(self): | |
| 133 return self._browser_directory | |
| 134 | |
| 135 @property | |
| 136 def profile_directory(self): | |
| 137 return self._tmp_profile_dir | |
| 138 | |
| 139 def IsBrowserRunning(self): | |
| 140 return self._proc.poll() == None | |
| 141 | |
| 142 def GetStandardOutput(self): | |
| 143 assert self._tmp_output_file, "Can't get standard output with show_stdout" | |
| 144 self._tmp_output_file.flush() | |
| 145 try: | |
| 146 with open(self._tmp_output_file.name) as f: | |
| 147 return f.read() | |
| 148 except IOError: | |
| 149 return '' | |
| 150 | |
| 151 def GetStackTrace(self): | |
| 152 executable_dir = os.path.dirname(self._executable) | |
| 153 stackwalk = os.path.join(executable_dir, 'minidump_stackwalk') | |
| 154 if not os.path.exists(stackwalk): | |
| 155 logging.warning('minidump_stackwalk binary not found. Must build it to ' | |
| 156 'symbolize crash dumps. Returning browser stdout.') | |
| 157 return self.GetStandardOutput() | |
| 158 | |
| 159 dumps = glob.glob(os.path.join(self._tmp_minidump_dir, '*.dmp')) | |
| 160 if not dumps: | |
| 161 logging.warning('No crash dump found. Returning browser stdout.') | |
| 162 return self.GetStandardOutput() | |
| 163 most_recent_dump = heapq.nlargest(1, dumps, os.path.getmtime)[0] | |
| 164 if os.path.getmtime(most_recent_dump) < (time.time() - (5 * 60)): | |
| 165 logging.warn('Crash dump is older than 5 minutes. May not be correct.') | |
| 166 | |
| 167 minidump = most_recent_dump + '.stripped' | |
| 168 with open(most_recent_dump, 'rb') as infile: | |
| 169 with open(minidump, 'wb') as outfile: | |
| 170 outfile.write(''.join(infile.read().partition('MDMP')[1:])) | |
| 171 | |
| 172 symbols = glob.glob(os.path.join(executable_dir, 'chrome.breakpad.*'))[0] | |
| 173 if not symbols: | |
| 174 logging.warning('No breakpad symbols found. Returning browser stdout.') | |
| 175 return self.GetStandardOutput() | |
| 176 | |
| 177 symbols_path = os.path.join(self._tmp_minidump_dir, 'symbols') | |
| 178 with open(symbols, 'r') as f: | |
| 179 _, _, _, sha, binary = f.readline().split() | |
| 180 symbol_path = os.path.join(symbols_path, binary, sha) | |
| 181 os.makedirs(symbol_path) | |
| 182 shutil.copyfile(symbols, os.path.join(symbol_path, binary + '.sym')) | |
| 183 | |
| 184 error = tempfile.NamedTemporaryFile('w', 0) | |
| 185 return subprocess.Popen( | |
| 186 [stackwalk, minidump, symbols_path], | |
| 187 stdout=subprocess.PIPE, stderr=error).communicate()[0] | |
| 188 | |
| 189 def __del__(self): | |
| 190 self.Close() | |
| 191 | |
| 192 def Close(self): | |
| 193 super(DesktopBrowserBackend, self).Close() | |
| 194 | |
| 195 if self._proc: | |
| 196 | |
| 197 def IsClosed(): | |
| 198 if not self._proc: | |
| 199 return True | |
| 200 return self._proc.poll() != None | |
| 201 | |
| 202 # Try to politely shutdown, first. | |
| 203 self._proc.terminate() | |
| 204 try: | |
| 205 util.WaitFor(IsClosed, timeout=1) | |
| 206 self._proc = None | |
| 207 except util.TimeoutException: | |
| 208 pass | |
| 209 | |
| 210 # Kill it. | |
| 211 if not IsClosed(): | |
| 212 self._proc.kill() | |
| 213 try: | |
| 214 util.WaitFor(IsClosed, timeout=5) | |
| 215 self._proc = None | |
| 216 except util.TimeoutException: | |
| 217 self._proc = None | |
| 218 raise Exception('Could not shutdown the browser.') | |
| 219 | |
| 220 if self._delete_profile_dir_after_run and \ | |
| 221 self._tmp_profile_dir and os.path.exists(self._tmp_profile_dir): | |
| 222 shutil.rmtree(self._tmp_profile_dir, ignore_errors=True) | |
| 223 self._tmp_profile_dir = None | |
| 224 | |
| 225 if self._tmp_output_file: | |
| 226 self._tmp_output_file.close() | |
| 227 self._tmp_output_file = None | |
| 228 | |
| 229 def CreateForwarder(self, *port_pairs): | |
| 230 return browser_backend.DoNothingForwarder(*port_pairs) | |
| OLD | NEW |