Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / core / browser.py
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
5 import os
6
7 from telemetry import decorators
8 from telemetry.core import browser_credentials
9 from telemetry.core import exceptions
10 from telemetry.core import extension_dict
11 from telemetry.core import local_server
12 from telemetry.core import memory_cache_http_server
13 from telemetry.core import platform
14 from telemetry.core import tab_list
15 from telemetry.core import wpr_modes
16 from telemetry.core import wpr_server
17 from telemetry.core.backends import browser_backend
18 from telemetry.core.platform.profiler import profiler_finder
19
20
21 class Browser(object):
22   """A running browser instance that can be controlled in a limited way.
23
24   To create a browser instance, use browser_finder.FindBrowser.
25
26   Be sure to clean up after yourself by calling Close() when you are done with
27   the browser. Or better yet:
28     browser_to_create = FindBrowser(options)
29     with browser_to_create.Create() as browser:
30       ... do all your operations on browser here
31   """
32   def __init__(self, backend, platform_backend):
33     self._browser_backend = backend
34     self._http_server = None
35     self._wpr_server = None
36     self._platform_backend = platform_backend
37     self._active_profilers = []
38     self._profilers_states = {}
39
40     self._extensions = None
41     if backend.supports_extensions:
42       self._extensions = extension_dict.ExtensionDict(
43           backend.extension_dict_backend)
44
45     self._local_server_controller = local_server.LocalServerController(backend)
46     self._tabs = tab_list.TabList(backend.tab_list_backend)
47     self.credentials = browser_credentials.BrowserCredentials()
48     self.platform.SetFullPerformanceModeEnabled(True)
49
50   def __enter__(self):
51     self.Start()
52     return self
53
54   def __exit__(self, *args):
55     self.Close()
56
57   @property
58   @decorators.Cache
59   def platform(self):
60     return platform.Platform(self._platform_backend)
61
62   @property
63   def browser_type(self):
64     return self._browser_backend.browser_type
65
66   @property
67   def is_content_shell(self):
68     """Returns whether this browser is a content shell, only."""
69     return self._browser_backend.is_content_shell
70
71   @property
72   def supports_extensions(self):
73     return self._browser_backend.supports_extensions
74
75   @property
76   def supports_tab_control(self):
77     return self._browser_backend.supports_tab_control
78
79   @property
80   def synthetic_gesture_source_type(self):
81     return self._browser_backend.browser_options.synthetic_gesture_source_type
82
83   @property
84   def tabs(self):
85     return self._tabs
86
87   @property
88   def foreground_tab(self):
89     for i in xrange(len(self._tabs)):
90       # The foreground tab is the first (only) one that isn't hidden.
91       # This only works through luck on Android, due to crbug.com/322544
92       # which means that tabs that have never been in the foreground return
93       # document.hidden as false; however in current code the Android foreground
94       # tab is always tab 0, which will be the first one that isn't hidden
95       if self._tabs[i].EvaluateJavaScript('!document.hidden'):
96         return self._tabs[i]
97     raise Exception("No foreground tab found")
98
99   @property
100   def extensions(self):
101     """Returns the extension dictionary if it exists."""
102     if not self.supports_extensions:
103       raise browser_backend.ExtensionsNotSupportedException(
104           'Extensions not supported')
105     return self._extensions
106
107   @property
108   def supports_tracing(self):
109     return self._browser_backend.supports_tracing
110
111   def is_profiler_active(self, profiler_name):
112     return profiler_name in [profiler.name() for
113                              profiler in self._active_profilers]
114
115   def _GetStatsCommon(self, pid_stats_function):
116     browser_pid = self._browser_backend.pid
117     result = {
118         'Browser': dict(pid_stats_function(browser_pid), **{'ProcessCount': 1}),
119         'Renderer': {'ProcessCount': 0},
120         'Gpu': {'ProcessCount': 0},
121         'Other': {'ProcessCount': 0}
122     }
123     process_count = 1
124     for child_pid in self._platform_backend.GetChildPids(browser_pid):
125       try:
126         child_cmd_line = self._platform_backend.GetCommandLine(child_pid)
127         child_stats = pid_stats_function(child_pid)
128       except exceptions.ProcessGoneException:
129         # It is perfectly fine for a process to have gone away between calling
130         # GetChildPids() and then further examining it.
131         continue
132       child_process_name = self._browser_backend.GetProcessName(child_cmd_line)
133       process_name_type_key_map = {'gpu-process': 'Gpu', 'renderer': 'Renderer'}
134       if child_process_name in process_name_type_key_map:
135         child_process_type_key = process_name_type_key_map[child_process_name]
136       else:
137         # TODO: identify other process types (zygote, plugin, etc), instead of
138         # lumping them in a single category.
139         child_process_type_key = 'Other'
140       result[child_process_type_key]['ProcessCount'] += 1
141       for k, v in child_stats.iteritems():
142         if k in result[child_process_type_key]:
143           result[child_process_type_key][k] += v
144         else:
145           result[child_process_type_key][k] = v
146       process_count += 1
147     for v in result.itervalues():
148       if v['ProcessCount'] > 1:
149         for k in v.keys():
150           if k.endswith('Peak'):
151             del v[k]
152       del v['ProcessCount']
153     result['ProcessCount'] = process_count
154     return result
155
156   @property
157   def memory_stats(self):
158     """Returns a dict of memory statistics for the browser:
159     { 'Browser': {
160         'VM': R,
161         'VMPeak': S,
162         'WorkingSetSize': T,
163         'WorkingSetSizePeak': U,
164         'ProportionalSetSize': V,
165         'PrivateDirty': W
166       },
167       'Gpu': {
168         'VM': R,
169         'VMPeak': S,
170         'WorkingSetSize': T,
171         'WorkingSetSizePeak': U,
172         'ProportionalSetSize': V,
173         'PrivateDirty': W
174       },
175       'Renderer': {
176         'VM': R,
177         'VMPeak': S,
178         'WorkingSetSize': T,
179         'WorkingSetSizePeak': U,
180         'ProportionalSetSize': V,
181         'PrivateDirty': W
182       },
183       'SystemCommitCharge': X,
184       'SystemTotalPhysicalMemory': Y,
185       'ProcessCount': Z,
186     }
187     Any of the above keys may be missing on a per-platform basis.
188     """
189     self._platform_backend.PurgeUnpinnedMemory()
190     result = self._GetStatsCommon(self._platform_backend.GetMemoryStats)
191     result['SystemCommitCharge'] = \
192         self._platform_backend.GetSystemCommitCharge()
193     result['SystemTotalPhysicalMemory'] = \
194         self._platform_backend.GetSystemTotalPhysicalMemory()
195     return result
196
197   @property
198   def cpu_stats(self):
199     """Returns a dict of cpu statistics for the system.
200     { 'Browser': {
201         'CpuProcessTime': S,
202         'TotalTime': T
203       },
204       'Gpu': {
205         'CpuProcessTime': S,
206         'TotalTime': T
207       },
208       'Renderer': {
209         'CpuProcessTime': S,
210         'TotalTime': T
211       }
212     }
213     Any of the above keys may be missing on a per-platform basis.
214     """
215     result = self._GetStatsCommon(self._platform_backend.GetCpuStats)
216     del result['ProcessCount']
217
218     # We want a single time value, not the sum for all processes.
219     for process_type in result:
220       # Skip any process_types that are empty
221       if not len(result[process_type]):
222         continue
223       result[process_type].update(self._platform_backend.GetCpuTimestamp())
224     return result
225
226   @property
227   def io_stats(self):
228     """Returns a dict of IO statistics for the browser:
229     { 'Browser': {
230         'ReadOperationCount': W,
231         'WriteOperationCount': X,
232         'ReadTransferCount': Y,
233         'WriteTransferCount': Z
234       },
235       'Gpu': {
236         'ReadOperationCount': W,
237         'WriteOperationCount': X,
238         'ReadTransferCount': Y,
239         'WriteTransferCount': Z
240       },
241       'Renderer': {
242         'ReadOperationCount': W,
243         'WriteOperationCount': X,
244         'ReadTransferCount': Y,
245         'WriteTransferCount': Z
246       }
247     }
248     """
249     result = self._GetStatsCommon(self._platform_backend.GetIOStats)
250     del result['ProcessCount']
251     return result
252
253   def StartProfiling(self, profiler_name, base_output_file):
254     """Starts profiling using |profiler_name|. Results are saved to
255     |base_output_file|.<process_name>."""
256     assert not self._active_profilers, 'Already profiling. Must stop first.'
257
258     profiler_class = profiler_finder.FindProfiler(profiler_name)
259
260     if not profiler_class.is_supported(self._browser_backend.browser_type):
261       raise Exception('The %s profiler is not '
262                       'supported on this platform.' % profiler_name)
263
264     if not profiler_class in self._profilers_states:
265       self._profilers_states[profiler_class] = {}
266
267     self._active_profilers.append(
268         profiler_class(self._browser_backend, self._platform_backend,
269             base_output_file, self._profilers_states[profiler_class]))
270
271   def StopProfiling(self):
272     """Stops all active profilers and saves their results.
273
274     Returns:
275       A list of filenames produced by the profiler.
276     """
277     output_files = []
278     for profiler in self._active_profilers:
279       output_files.extend(profiler.CollectProfile())
280     self._active_profilers = []
281     return output_files
282
283   def StartTracing(self, custom_categories=None, timeout=10):
284     return self._browser_backend.StartTracing(custom_categories, timeout)
285
286   def StopTracing(self):
287     return self._browser_backend.StopTracing()
288
289   def Start(self):
290     browser_options = self._browser_backend.browser_options
291     if browser_options.clear_sytem_cache_for_browser_and_profile_on_start:
292       if self.platform.CanFlushIndividualFilesFromSystemCache():
293         self.platform.FlushSystemCacheForDirectory(
294             self._browser_backend.profile_directory)
295         self.platform.FlushSystemCacheForDirectory(
296             self._browser_backend.browser_directory)
297       else:
298         self.platform.FlushEntireSystemCache()
299
300     self._browser_backend.Start()
301     self._browser_backend.SetBrowser(self)
302
303   def Close(self):
304     """Closes this browser."""
305     for profiler_class in self._profilers_states:
306       profiler_class.WillCloseBrowser(self._browser_backend,
307                                       self._platform_backend)
308
309     self.platform.SetFullPerformanceModeEnabled(False)
310
311     if self._wpr_server:
312       self._wpr_server.Close()
313       self._wpr_server = None
314
315     if self._http_server:
316       self._http_server.Close()
317       self._http_server = None
318
319     self._local_server_controller.Close()
320     self._browser_backend.Close()
321     self.credentials = None
322
323   @property
324   def http_server(self):
325     return self._local_server_controller.GetRunningServer(
326       memory_cache_http_server.MemoryCacheHTTPServer, None)
327
328   def SetHTTPServerDirectories(self, paths):
329     """Returns True if the HTTP server was started, False otherwise."""
330     if isinstance(paths, basestring):
331       paths = set([paths])
332     paths = set(os.path.realpath(p) for p in paths)
333
334     # If any path is in a subdirectory of another, remove the subdirectory.
335     duplicates = set()
336     for parent_path in paths:
337       for sub_path in paths:
338         if parent_path == sub_path:
339           continue
340         if os.path.commonprefix((parent_path, sub_path)) == parent_path:
341           duplicates.add(sub_path)
342     paths -= duplicates
343
344     if self.http_server:
345       if paths and self.http_server.paths == paths:
346         return False
347
348       self.http_server.Close()
349
350     if not paths:
351       return False
352
353     server = memory_cache_http_server.MemoryCacheHTTPServer(paths)
354     self.StartLocalServer(server)
355     return True
356
357   def StartLocalServer(self, server):
358     """Starts a LocalServer and associates it with this browser.
359
360     It will be closed when the browser closes.
361     """
362     self._local_server_controller.StartServer(server)
363
364   @property
365   def local_servers(self):
366     """Returns the currently running local servers."""
367     return self._local_server_controller.local_servers
368
369   def SetReplayArchivePath(self, archive_path, append_to_existing_wpr=False,
370                            make_javascript_deterministic=True):
371     if self._wpr_server:
372       self._wpr_server.Close()
373       self._wpr_server = None
374
375     if not archive_path:
376       return None
377
378     if self._browser_backend.wpr_mode == wpr_modes.WPR_OFF:
379       return
380
381     use_record_mode = self._browser_backend.wpr_mode == wpr_modes.WPR_RECORD
382     if not use_record_mode:
383       assert os.path.isfile(archive_path)
384
385     self._wpr_server = wpr_server.ReplayServer(
386         self._browser_backend,
387         archive_path,
388         use_record_mode,
389         append_to_existing_wpr,
390         make_javascript_deterministic)
391
392   def GetStandardOutput(self):
393     return self._browser_backend.GetStandardOutput()
394
395   def GetStackTrace(self):
396     return self._browser_backend.GetStackTrace()
397
398   @property
399   def supports_system_info(self):
400     return self._browser_backend.supports_system_info
401
402   def GetSystemInfo(self):
403     """Returns low-level information about the system, if available.
404
405        See the documentation of the SystemInfo class for more details."""
406     return self._browser_backend.GetSystemInfo()
407