- add sources.
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / core / backends / chrome / desktop_browser_backend.py
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
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, browser_options, executable, flash_path, is_content_shell,
23                browser_directory, output_profile_path, extensions_to_load):
24     super(DesktopBrowserBackend, self).__init__(
25         is_content_shell=is_content_shell,
26         supports_extensions=not is_content_shell,
27         browser_options=browser_options,
28         output_profile_path=output_profile_path,
29         extensions_to_load=extensions_to_load)
30
31     # Initialize fields so that an explosion during init doesn't break in Close.
32     self._proc = None
33     self._tmp_profile_dir = None
34     self._tmp_output_file = None
35
36     self._executable = executable
37     if not self._executable:
38       raise Exception('Cannot create browser, no executable found!')
39
40     self._flash_path = flash_path
41     if self._flash_path and not os.path.exists(self._flash_path):
42       logging.warning(('Could not find flash at %s. Running without flash.\n\n'
43                        'To fix this see http://go/read-src-internal') %
44                       self._flash_path)
45       self._flash_path = None
46
47     if len(extensions_to_load) > 0 and is_content_shell:
48       raise browser_backend.ExtensionsNotSupportedException(
49           'Content shell does not support extensions.')
50
51     self._browser_directory = browser_directory
52     self._port = util.GetAvailableLocalPort()
53     self._profile_dir = None
54     self._supports_net_benchmarking = True
55     self._tmp_minidump_dir = tempfile.mkdtemp()
56
57     self._SetupProfile()
58
59   def _SetupProfile(self):
60     if not self.browser_options.dont_override_profile:
61       if self._output_profile_path:
62         # If both |_output_profile_path| and |profile_dir| are specified then
63         # the calling code will throw an exception, so we don't need to worry
64         # about that case here.
65         self._tmp_profile_dir = self._output_profile_path
66       else:
67         self._tmp_profile_dir = tempfile.mkdtemp()
68       profile_dir = self._profile_dir or self.browser_options.profile_dir
69       if profile_dir:
70         if self.is_content_shell:
71           logging.critical('Profiles cannot be used with content shell')
72           sys.exit(1)
73         logging.info("Using profile directory:'%s'." % profile_dir)
74         shutil.rmtree(self._tmp_profile_dir)
75         shutil.copytree(profile_dir, self._tmp_profile_dir)
76
77   def _LaunchBrowser(self):
78     args = [self._executable]
79     args.extend(self.GetBrowserStartupArgs())
80     if self.browser_options.startup_url:
81       args.append(self.browser_options.startup_url)
82     env = os.environ.copy()
83     env['CHROME_HEADLESS'] = '1'  # Don't upload minidumps.
84     env['BREAKPAD_DUMP_LOCATION'] = self._tmp_minidump_dir
85     if not self.browser_options.show_stdout:
86       self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0)
87       self._proc = subprocess.Popen(
88           args, stdout=self._tmp_output_file, stderr=subprocess.STDOUT, env=env)
89     else:
90       self._proc = subprocess.Popen(args, env=env)
91
92     try:
93       self._WaitForBrowserToComeUp()
94       self._PostBrowserStartupInitialization()
95     except:
96       self.Close()
97       raise
98
99   def GetBrowserStartupArgs(self):
100     args = super(DesktopBrowserBackend, self).GetBrowserStartupArgs()
101     args.append('--remote-debugging-port=%i' % self._port)
102     args.append('--enable-crash-reporter-for-testing')
103     if not self.is_content_shell:
104       args.append('--window-size=1280,1024')
105       if self._flash_path:
106         args.append('--ppapi-flash-path=%s' % self._flash_path)
107       if self._supports_net_benchmarking:
108         args.append('--enable-net-benchmarking')
109       else:
110         args.append('--enable-benchmarking')
111       if not self.browser_options.dont_override_profile:
112         args.append('--user-data-dir=%s' % self._tmp_profile_dir)
113     return args
114
115   def SetProfileDirectory(self, profile_dir):
116     # Make sure _profile_dir hasn't already been set.
117     assert self._profile_dir is None
118
119     if self.is_content_shell:
120       logging.critical('Profile creation cannot be used with content shell')
121       sys.exit(1)
122
123     self._profile_dir = profile_dir
124
125   def Start(self):
126     self._LaunchBrowser()
127
128     # For old chrome versions, might have to relaunch to have the
129     # correct net_benchmarking switch.
130     if self.chrome_branch_number < 1418:
131       self.Close()
132       self._supports_net_benchmarking = False
133       self._LaunchBrowser()
134
135   @property
136   def pid(self):
137     if self._proc:
138       return self._proc.pid
139     return None
140
141   @property
142   def browser_directory(self):
143     return self._browser_directory
144
145   @property
146   def profile_directory(self):
147     return self._tmp_profile_dir
148
149   def IsBrowserRunning(self):
150     return self._proc.poll() == None
151
152   def GetStandardOutput(self):
153     assert self._tmp_output_file, "Can't get standard output with show_stdout"
154     self._tmp_output_file.flush()
155     try:
156       with open(self._tmp_output_file.name) as f:
157         return f.read()
158     except IOError:
159       return ''
160
161   def GetStackTrace(self):
162     stackwalk = util.FindSupportBinary('minidump_stackwalk')
163     if not stackwalk:
164       logging.warning('minidump_stackwalk binary not found. Must build it to '
165                       'symbolize crash dumps. Returning browser stdout.')
166       return self.GetStandardOutput()
167
168     dumps = glob.glob(os.path.join(self._tmp_minidump_dir, '*.dmp'))
169     if not dumps:
170       logging.warning('No crash dump found. Returning browser stdout.')
171       return self.GetStandardOutput()
172     most_recent_dump = heapq.nlargest(1, dumps, os.path.getmtime)[0]
173     if os.path.getmtime(most_recent_dump) < (time.time() - (5 * 60)):
174       logging.warn('Crash dump is older than 5 minutes. May not be correct.')
175
176     minidump = most_recent_dump + '.stripped'
177     with open(most_recent_dump, 'rb') as infile:
178       with open(minidump, 'wb') as outfile:
179         outfile.write(''.join(infile.read().partition('MDMP')[1:]))
180
181     symbols = glob.glob(os.path.join(os.path.dirname(stackwalk), '*.breakpad*'))
182     if not symbols:
183       logging.warning('No breakpad symbols found. Returning browser stdout.')
184       return self.GetStandardOutput()
185
186     symbols_path = os.path.join(self._tmp_minidump_dir, 'symbols')
187     for symbol in sorted(symbols, key=os.path.getmtime, reverse=True):
188       if not os.path.isfile(symbol):
189         continue
190       with open(symbol, 'r') as f:
191         fields = f.readline().split()
192         if not fields:
193           continue
194         sha = fields[3]
195         binary = ' '.join(fields[4:])
196       symbol_path = os.path.join(symbols_path, binary, sha)
197       if os.path.exists(symbol_path):
198         continue
199       os.makedirs(symbol_path)
200       shutil.copyfile(symbol, os.path.join(symbol_path, binary + '.sym'))
201
202     error = tempfile.NamedTemporaryFile('w', 0)
203     return subprocess.Popen(
204         [stackwalk, minidump, symbols_path],
205         stdout=subprocess.PIPE, stderr=error).communicate()[0]
206
207   def __del__(self):
208     self.Close()
209
210   def Close(self):
211     super(DesktopBrowserBackend, self).Close()
212
213     if self._proc:
214
215       def IsClosed():
216         if not self._proc:
217           return True
218         return self._proc.poll() != None
219
220       # Try to politely shutdown, first.
221       self._proc.terminate()
222       try:
223         util.WaitFor(IsClosed, timeout=1)
224         self._proc = None
225       except util.TimeoutException:
226         pass
227
228       # Kill it.
229       if not IsClosed():
230         self._proc.kill()
231         try:
232           util.WaitFor(IsClosed, timeout=5)
233           self._proc = None
234         except util.TimeoutException:
235           self._proc = None
236           raise Exception('Could not shutdown the browser.')
237
238     if self._output_profile_path:
239       # If we need the output then double check that it exists.
240       if not (self._tmp_profile_dir and os.path.exists(self._tmp_profile_dir)):
241         raise Exception("No profile directory generated by Chrome: '%s'." %
242             self._tmp_profile_dir)
243     else:
244       # If we don't need the profile after the run then cleanup.
245       if self._tmp_profile_dir and os.path.exists(self._tmp_profile_dir):
246         shutil.rmtree(self._tmp_profile_dir, ignore_errors=True)
247         self._tmp_profile_dir = None
248
249     if self._tmp_output_file:
250       self._tmp_output_file.close()
251       self._tmp_output_file = None
252
253   def CreateForwarder(self, *port_pairs):
254     return browser_backend.DoNothingForwarder(*port_pairs)