Upstream version 7.36.149.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.core.timeline import model as timeline_model
23 from telemetry.core.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(err_msg)
46       elif not self._browser_backend.HasBrowserFinishedLaunching():
47         raise exceptions.BrowserConnectionGoneException(err_msg)
48       else:
49         raise exceptions.TabCrashException(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.id:
78         return c['url']
79     return None
80
81   @property
82   def id(self):
83     return self._context['id']
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 PerformActionAndWaitForNavigate(self, action_function, timeout):
162     self._page.PerformActionAndWaitForNavigate(action_function, 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   # Timeline public methods.
179
180   @property
181   def timeline_model(self):
182     return self._timeline_model
183
184   def StartTimelineRecording(self, options=None):
185     if not options:
186       options = recording_options.TimelineRecordingOptions()
187     if options.record_timeline:
188       self._timeline.Start()
189     if options.record_network:
190       self._network.timeline_recorder.Start()
191
192   def StopTimelineRecording(self):
193     data = []
194     timeline_data = self._timeline.Stop()
195     if timeline_data:
196       data.append(timeline_data)
197     network_data = self._network.timeline_recorder.Stop()
198     if network_data:
199       data.append(network_data)
200     if data:
201       self._timeline_model = timeline_model.TimelineModel(
202           timeline_data=data, shift_world_to_zero=False)
203     else:
204       self._timeline_model = None
205
206   @property
207   def is_timeline_recording_running(self):
208     return self._timeline.is_timeline_recording_running
209
210   # Network public methods.
211
212   def ClearCache(self):
213     self._network.ClearCache()
214
215   # Methods used internally by other backends.
216
217   def _IsInspectable(self):
218     contexts = self._browser_backend.ListInspectableContexts()
219     return self.id in [c['id'] for c in contexts]
220
221   def _HandleNotification(self, res):
222     if (res['method'] == 'Inspector.detached' and
223         res.get('params', {}).get('reason','') == 'replaced_with_devtools'):
224       self._WaitForInspectorToGoAwayAndReconnect()
225       return
226     if res['method'] == 'Inspector.targetCrashed':
227       raise exceptions.TabCrashException()
228
229     mname = res['method']
230     dot_pos = mname.find('.')
231     domain_name = mname[:dot_pos]
232     if domain_name in self._domain_handlers:
233       try:
234         self._domain_handlers[domain_name][0](res)
235       except Exception:
236         import traceback
237         traceback.print_exc()
238     else:
239       logging.debug('Unhandled inspector message: %s', res)
240
241   def _HandleError(self, elapsed_time):
242     if self._IsInspectable():
243       raise util.TimeoutException(
244           'Received a socket error in the browser connection and the tab '
245           'still exists, assuming it timed out. '
246           'Elapsed=%ds Error=%s' % (elapsed_time, sys.exc_info()[1]))
247     raise exceptions.TabCrashException(
248         'Received a socket error in the browser connection and the tab no '
249         'longer exists, assuming it crashed. Error=%s' % sys.exc_info()[1])
250
251   def _WaitForInspectorToGoAwayAndReconnect(self):
252     sys.stderr.write('The connection to Chrome was lost to the Inspector UI.\n')
253     sys.stderr.write('Telemetry is waiting for the inspector to be closed...\n')
254     super(InspectorBackend, self).Disconnect()
255     self._socket.close()
256     self._socket = None
257     def IsBack():
258       if not self._IsInspectable():
259         return False
260       try:
261         self.Connect(self.debugger_url)
262       except exceptions.TabCrashException, ex:
263         if ex.message.message.find('Handshake Status 500') == 0:
264           return False
265         raise
266       return True
267     util.WaitFor(IsBack, 512)
268     sys.stderr.write('\n')
269     sys.stderr.write('Inspector\'s UI closed. Telemetry will now resume.\n')
270
271   def RegisterDomain(self,
272       domain_name, notification_handler, will_close_handler):
273     """Registers a given domain for handling notification methods.
274
275     For example, given inspector_backend:
276        def OnConsoleNotification(msg):
277           if msg['method'] == 'Console.messageAdded':
278              print msg['params']['message']
279           return
280        def OnConsoleClose(self):
281           pass
282        inspector_backend.RegisterDomain('Console',
283                                         OnConsoleNotification, OnConsoleClose)
284        """
285     assert domain_name not in self._domain_handlers
286     self._domain_handlers[domain_name] = (notification_handler,
287                                           will_close_handler)
288
289   def UnregisterDomain(self, domain_name):
290     """Unregisters a previously registered domain."""
291     assert domain_name in self._domain_handlers
292     self._domain_handlers.pop(domain_name)
293
294   def CollectGarbage(self):
295     self._page.CollectGarbage()
296
297   def TakeJSHeapSnapshot(self, timeout=120):
298     snapshot = []
299
300     def OnNotification(res):
301       if res['method'] == 'HeapProfiler.addHeapSnapshotChunk':
302         snapshot.append(res['params']['chunk'])
303
304     def OnClose():
305       pass
306
307     self.RegisterDomain('HeapProfiler', OnNotification, OnClose)
308
309     self.SyncRequest({'method': 'Page.getResourceTree'}, timeout)
310     self.SyncRequest({'method': 'Debugger.enable'}, timeout)
311     self.SyncRequest({'method': 'HeapProfiler.takeHeapSnapshot'}, timeout)
312     snapshot = ''.join(snapshot)
313
314     self.UnregisterDomain('HeapProfiler')
315     return model.Model(snapshot)