Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / core / backends / chrome / inspector_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
5 import logging
6 import os
7 import sys
8
9 from telemetry import decorators
10 from telemetry.core import bitmap
11 from telemetry.core import exceptions
12 from telemetry.core import util
13 from telemetry.core.backends.chrome import inspector_console
14 from telemetry.core.backends.chrome import inspector_memory
15 from telemetry.core.backends.chrome import inspector_network
16 from telemetry.core.backends.chrome import inspector_page
17 from telemetry.core.backends.chrome import inspector_runtime
18 from telemetry.core.backends.chrome import inspector_timeline
19 from telemetry.core.backends.chrome import inspector_websocket
20 from telemetry.core.backends.chrome import websocket
21 from telemetry.core.heap import model
22 from telemetry.timeline import model as timeline_model
23 from telemetry.timeline import recording_options
24
25
26 class InspectorException(Exception):
27   pass
28
29
30 class InspectorBackend(inspector_websocket.InspectorWebsocket):
31   def __init__(self, browser_backend, context, timeout=60):
32     super(InspectorBackend, self).__init__(self._HandleNotification,
33                                            self._HandleError)
34
35     self._browser_backend = browser_backend
36     self._context = context
37     self._domain_handlers = {}
38
39     logging.debug('InspectorBackend._Connect() to %s', self.debugger_url)
40     try:
41       self.Connect(self.debugger_url)
42     except (websocket.WebSocketException, util.TimeoutException):
43       err_msg = sys.exc_info()[1]
44       if not self._browser_backend.IsBrowserRunning():
45         raise exceptions.BrowserGoneException(self.browser, err_msg)
46       elif not self._browser_backend.HasBrowserFinishedLaunching():
47         raise exceptions.BrowserConnectionGoneException(self.browser, err_msg)
48       else:
49         raise exceptions.TabCrashException(self.browser, err_msg)
50
51     self._console = inspector_console.InspectorConsole(self)
52     self._memory = inspector_memory.InspectorMemory(self)
53     self._page = inspector_page.InspectorPage(self, timeout=timeout)
54     self._runtime = inspector_runtime.InspectorRuntime(self)
55     self._timeline = inspector_timeline.InspectorTimeline(self)
56     self._network = inspector_network.InspectorNetwork(self)
57     self._timeline_model = None
58
59   def __del__(self):
60     self.Disconnect()
61
62   def Disconnect(self):
63     for _, handlers in self._domain_handlers.items():
64       _, will_close_handler = handlers
65       will_close_handler()
66     self._domain_handlers = {}
67
68     super(InspectorBackend, self).Disconnect()
69
70   @property
71   def browser(self):
72     return self._browser_backend.browser
73
74   @property
75   def url(self):
76     for c in self._browser_backend.ListInspectableContexts():
77       if c['id'] == self._context['id']:
78         return c['url']
79     return None
80
81   @property
82   def id(self):
83     return self.debugger_url
84
85   @property
86   def debugger_url(self):
87     return self._context['webSocketDebuggerUrl']
88
89   # Public methods implemented in JavaScript.
90
91   @property
92   @decorators.Cache
93   def screenshot_supported(self):
94     if (self.browser.platform.GetOSName() == 'linux' and (
95         os.getenv('DISPLAY') not in [':0', ':0.0'])):
96       # Displays other than 0 mean we are likely running in something like
97       # xvfb where screenshotting doesn't work.
98       return False
99     return not self.EvaluateJavaScript("""
100         window.chrome.gpuBenchmarking === undefined ||
101         window.chrome.gpuBenchmarking.beginWindowSnapshotPNG === undefined
102       """)
103
104   def Screenshot(self, timeout):
105     assert self.screenshot_supported, 'Browser does not support screenshotting'
106
107     self.EvaluateJavaScript("""
108         if(!window.__telemetry) {
109           window.__telemetry = {}
110         }
111         window.__telemetry.snapshotComplete = false;
112         window.__telemetry.snapshotData = null;
113         window.chrome.gpuBenchmarking.beginWindowSnapshotPNG(
114           function(snapshot) {
115             window.__telemetry.snapshotData = snapshot;
116             window.__telemetry.snapshotComplete = true;
117           }
118         );
119     """)
120
121     def IsSnapshotComplete():
122       return self.EvaluateJavaScript(
123           'window.__telemetry.snapshotComplete')
124
125     util.WaitFor(IsSnapshotComplete, timeout)
126
127     snap = self.EvaluateJavaScript("""
128       (function() {
129         var data = window.__telemetry.snapshotData;
130         delete window.__telemetry.snapshotComplete;
131         delete window.__telemetry.snapshotData;
132         return data;
133       })()
134     """)
135     if snap:
136       return bitmap.Bitmap.FromBase64Png(snap['data'])
137     return None
138
139   # Console public methods.
140
141   @property
142   def message_output_stream(self):  # pylint: disable=E0202
143     return self._console.message_output_stream
144
145   @message_output_stream.setter
146   def message_output_stream(self, stream):  # pylint: disable=E0202
147     self._console.message_output_stream = stream
148
149   # Memory public methods.
150
151   def GetDOMStats(self, timeout):
152     dom_counters = self._memory.GetDOMCounters(timeout)
153     return {
154       'document_count': dom_counters['documents'],
155       'node_count': dom_counters['nodes'],
156       'event_listener_count': dom_counters['jsEventListeners']
157     }
158
159   # Page public methods.
160
161   def WaitForNavigate(self, timeout):
162     self._page.WaitForNavigate(timeout)
163
164   def Navigate(self, url, script_to_evaluate_on_commit, timeout):
165     self._page.Navigate(url, script_to_evaluate_on_commit, timeout)
166
167   def GetCookieByName(self, name, timeout):
168     return self._page.GetCookieByName(name, timeout)
169
170   # Runtime public methods.
171
172   def ExecuteJavaScript(self, expr, context_id=None, timeout=60):
173     self._runtime.Execute(expr, context_id, timeout)
174
175   def EvaluateJavaScript(self, expr, context_id=None, timeout=60):
176     return self._runtime.Evaluate(expr, context_id, timeout)
177
178   def EnableAllContexts(self):
179     return self._runtime.EnableAllContexts()
180
181   # Timeline public methods.
182
183   @property
184   def timeline_model(self):
185     return self._timeline_model
186
187   def StartTimelineRecording(self, options=None):
188     if not options:
189       options = recording_options.TimelineRecordingOptions()
190     if options.record_timeline:
191       self._timeline.Start()
192     if options.record_network:
193       self._network.timeline_recorder.Start()
194
195   def StopTimelineRecording(self):
196     data = []
197     timeline_data = self._timeline.Stop()
198     if timeline_data:
199       data.append(timeline_data)
200     network_data = self._network.timeline_recorder.Stop()
201     if network_data:
202       data.append(network_data)
203     if data:
204       self._timeline_model = timeline_model.TimelineModel(
205           timeline_data=data, shift_world_to_zero=False)
206     else:
207       self._timeline_model = None
208
209   @property
210   def is_timeline_recording_running(self):
211     return self._timeline.is_timeline_recording_running
212
213   # Network public methods.
214
215   def ClearCache(self):
216     self._network.ClearCache()
217
218   # Methods used internally by other backends.
219
220   def _IsInspectable(self):
221     contexts = self._browser_backend.ListInspectableContexts()
222     return self._context['id'] in [c['id'] for c in contexts]
223
224   def _HandleNotification(self, res):
225     if (res['method'] == 'Inspector.detached' and
226         res.get('params', {}).get('reason', '') == 'replaced_with_devtools'):
227       self._WaitForInspectorToGoAwayAndReconnect()
228       return
229     if res['method'] == 'Inspector.targetCrashed':
230       raise exceptions.TabCrashException(self.browser)
231
232     mname = res['method']
233     dot_pos = mname.find('.')
234     domain_name = mname[:dot_pos]
235     if domain_name in self._domain_handlers:
236       try:
237         self._domain_handlers[domain_name][0](res)
238       except Exception:
239         import traceback
240         traceback.print_exc()
241     else:
242       logging.warn('Unhandled inspector message: %s', res)
243
244   def _HandleError(self, elapsed_time):
245     if self._IsInspectable():
246       raise util.TimeoutException(
247           'Received a socket error in the browser connection and the tab '
248           'still exists, assuming it timed out. '
249           'Elapsed=%ds Error=%s' % (elapsed_time, sys.exc_info()[1]))
250     raise exceptions.TabCrashException(self.browser,
251         'Received a socket error in the browser connection and the tab no '
252         'longer exists, assuming it crashed. Error=%s' % sys.exc_info()[1])
253
254   def _WaitForInspectorToGoAwayAndReconnect(self):
255     sys.stderr.write('The connection to Chrome was lost to the Inspector UI.\n')
256     sys.stderr.write('Telemetry is waiting for the inspector to be closed...\n')
257     super(InspectorBackend, self).Disconnect()
258     self._socket.close()
259     self._socket = None
260     def IsBack():
261       if not self._IsInspectable():
262         return False
263       try:
264         self.Connect(self.debugger_url)
265       except exceptions.TabCrashException, ex:
266         if ex.message.message.find('Handshake Status 500') == 0:
267           return False
268         raise
269       return True
270     util.WaitFor(IsBack, 512)
271     sys.stderr.write('\n')
272     sys.stderr.write('Inspector\'s UI closed. Telemetry will now resume.\n')
273
274   def RegisterDomain(self,
275       domain_name, notification_handler, will_close_handler):
276     """Registers a given domain for handling notification methods.
277
278     For example, given inspector_backend:
279        def OnConsoleNotification(msg):
280           if msg['method'] == 'Console.messageAdded':
281              print msg['params']['message']
282           return
283        def OnConsoleClose(self):
284           pass
285        inspector_backend.RegisterDomain('Console',
286                                         OnConsoleNotification, OnConsoleClose)
287        """
288     assert domain_name not in self._domain_handlers
289     self._domain_handlers[domain_name] = (notification_handler,
290                                           will_close_handler)
291
292   def UnregisterDomain(self, domain_name):
293     """Unregisters a previously registered domain."""
294     assert domain_name in self._domain_handlers
295     self._domain_handlers.pop(domain_name)
296
297   def CollectGarbage(self):
298     self._page.CollectGarbage()
299
300   def TakeJSHeapSnapshot(self, timeout=120):
301     snapshot = []
302
303     def OnNotification(res):
304       if res['method'] == 'HeapProfiler.addHeapSnapshotChunk':
305         snapshot.append(res['params']['chunk'])
306
307     def OnClose():
308       pass
309
310     self.RegisterDomain('HeapProfiler', OnNotification, OnClose)
311
312     self.SyncRequest({'method': 'Page.getResourceTree'}, timeout)
313     self.SyncRequest({'method': 'Debugger.enable'}, timeout)
314     self.SyncRequest({'method': 'HeapProfiler.takeHeapSnapshot'}, timeout)
315     snapshot = ''.join(snapshot)
316
317     self.UnregisterDomain('HeapProfiler')
318     return model.Model(snapshot)