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