Upstream version 5.34.104.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 import json
5 import logging
6 import socket
7 import sys
8 import time
9
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 websocket
20 from telemetry.core.heap import model
21
22 class InspectorException(Exception):
23   pass
24
25 class InspectorBackend(object):
26   def __init__(self, browser, browser_backend, debugger_url, timeout=60):
27     assert debugger_url
28     self._browser = browser
29     self._browser_backend = browser_backend
30     self._debugger_url = debugger_url
31     self._socket = None
32     self._domain_handlers = {}
33     self._cur_socket_timeout = 0
34     self._next_request_id = 0
35
36     self._console = inspector_console.InspectorConsole(self)
37     self._memory = inspector_memory.InspectorMemory(self)
38     self._page = inspector_page.InspectorPage(self, timeout=timeout)
39     self._runtime = inspector_runtime.InspectorRuntime(self)
40     self._timeline = inspector_timeline.InspectorTimeline(self)
41     self._network = inspector_network.InspectorNetwork(self)
42
43   def __del__(self):
44     self.Disconnect()
45
46   def _Connect(self, timeout=10):
47     if self._socket:
48       return
49     try:
50       self._socket = websocket.create_connection(self._debugger_url,
51           timeout=timeout)
52     except (websocket.WebSocketException):
53       if self._browser_backend.IsBrowserRunning():
54         raise exceptions.TabCrashException(sys.exc_info()[1])
55       else:
56         raise exceptions.BrowserGoneException()
57
58     self._cur_socket_timeout = 0
59     self._next_request_id = 0
60
61   def Disconnect(self):
62     for _, handlers in self._domain_handlers.items():
63       _, will_close_handler = handlers
64       will_close_handler()
65     self._domain_handlers = {}
66
67     if self._socket:
68       self._socket.close()
69       self._socket = None
70
71   # General public methods.
72
73   @property
74   def browser(self):
75     return self._browser
76
77   @property
78   def url(self):
79     self.Disconnect()
80     return self._browser_backend.tab_list_backend.GetTabUrl(self._debugger_url)
81
82   def Activate(self):
83     self._Connect()
84     self._browser_backend.tab_list_backend.ActivateTab(self._debugger_url)
85
86   def Close(self):
87     self.Disconnect()
88     self._browser_backend.tab_list_backend.CloseTab(self._debugger_url)
89
90   # Public methods implemented in JavaScript.
91
92   @property
93   def screenshot_supported(self):
94     if self._runtime.Evaluate(
95         'window.chrome.gpuBenchmarking === undefined'):
96       return False
97
98     if self._runtime.Evaluate(
99         'window.chrome.gpuBenchmarking.beginWindowSnapshotPNG === undefined'):
100       return False
101
102     return (self._browser_backend.chrome_branch_number >= 1391 or
103             self._browser_backend.is_content_shell)
104
105   def Screenshot(self, timeout):
106     if self._runtime.Evaluate(
107         'window.chrome.gpuBenchmarking === undefined'):
108       raise Exception("Browser was not started with --enable-gpu-benchmarking")
109
110     if self._runtime.Evaluate(
111         'window.chrome.gpuBenchmarking.beginWindowSnapshotPNG === undefined'):
112       raise Exception("Browser does not support window snapshot API.")
113
114     self._runtime.Evaluate("""
115         if(!window.__telemetry) {
116           window.__telemetry = {}
117         }
118         window.__telemetry.snapshotComplete = false;
119         window.__telemetry.snapshotData = null;
120         window.chrome.gpuBenchmarking.beginWindowSnapshotPNG(
121           function(snapshot) {
122             window.__telemetry.snapshotData = snapshot;
123             window.__telemetry.snapshotComplete = true;
124           }
125         );
126     """)
127
128     def IsSnapshotComplete():
129       return self._runtime.Evaluate('window.__telemetry.snapshotComplete')
130
131     util.WaitFor(IsSnapshotComplete, timeout)
132
133     snap = self._runtime.Evaluate("""
134       (function() {
135         var data = window.__telemetry.snapshotData;
136         delete window.__telemetry.snapshotComplete;
137         delete window.__telemetry.snapshotData;
138         return data;
139       })()
140     """)
141     if snap:
142       return bitmap.Bitmap.FromBase64Png(snap['data'])
143     return None
144
145   # Console public methods.
146
147   @property
148   def message_output_stream(self):  # pylint: disable=E0202
149     return self._console.message_output_stream
150
151   @message_output_stream.setter
152   def message_output_stream(self, stream):  # pylint: disable=E0202
153     self._console.message_output_stream = stream
154
155   # Memory public methods.
156
157   def GetDOMStats(self, timeout):
158     dom_counters = self._memory.GetDOMCounters(timeout)
159     return {
160       'document_count': dom_counters['documents'],
161       'node_count': dom_counters['nodes'],
162       'event_listener_count': dom_counters['jsEventListeners']
163     }
164
165   # Page public methods.
166
167   def PerformActionAndWaitForNavigate(self, action_function, timeout):
168     self._page.PerformActionAndWaitForNavigate(action_function, timeout)
169
170   def Navigate(self, url, script_to_evaluate_on_commit, timeout):
171     self._page.Navigate(url, script_to_evaluate_on_commit, timeout)
172
173   def GetCookieByName(self, name, timeout):
174     return self._page.GetCookieByName(name, timeout)
175
176   # Runtime public methods.
177
178   def ExecuteJavaScript(self, expr, timeout):
179     self._runtime.Execute(expr, timeout)
180
181   def EvaluateJavaScript(self, expr, timeout):
182     return self._runtime.Evaluate(expr, timeout)
183
184   # Timeline public methods.
185
186   @property
187   def timeline_model(self):
188     return self._timeline.timeline_model
189
190   def StartTimelineRecording(self):
191     self._timeline.Start()
192
193   def StopTimelineRecording(self):
194     self._timeline.Stop()
195
196   # Network public methods.
197
198   def ClearCache(self):
199     self._network.ClearCache()
200
201   # Methods used internally by other backends.
202
203   def DispatchNotifications(self, timeout=10):
204     self._Connect(timeout)
205     self._SetTimeout(timeout)
206     res = self._ReceiveJsonData(timeout)
207     if 'method' in res:
208       self._HandleNotification(res)
209
210   def _ReceiveJsonData(self, timeout):
211     try:
212       start_time = time.time()
213       data = self._socket.recv()
214     except (socket.error, websocket.WebSocketException):
215       if self._browser_backend.tab_list_backend.DoesDebuggerUrlExist(
216           self._debugger_url):
217         elapsed_time = time.time() - start_time
218         raise util.TimeoutException(
219             'Received a socket error in the browser connection and the tab '
220             'still exists, assuming it timed out. '
221             'Timeout=%ds Elapsed=%ds Error=%s' % (
222                 timeout, elapsed_time, sys.exc_info()[1]))
223       raise exceptions.TabCrashException(
224           'Received a socket error in the browser connection and the tab no '
225           'longer exists, assuming it crashed. Error=%s' % sys.exc_info()[1])
226     res = json.loads(data)
227     logging.debug('got [%s]', data)
228     return res
229
230   def _HandleNotification(self, res):
231     if (res['method'] == 'Inspector.detached' and
232         res.get('params', {}).get('reason','') == 'replaced_with_devtools'):
233       self._WaitForInspectorToGoAwayAndReconnect()
234       return
235     if res['method'] == 'Inspector.targetCrashed':
236       raise exceptions.TabCrashException()
237
238     mname = res['method']
239     dot_pos = mname.find('.')
240     domain_name = mname[:dot_pos]
241     if domain_name in self._domain_handlers:
242       try:
243         self._domain_handlers[domain_name][0](res)
244       except Exception:
245         import traceback
246         traceback.print_exc()
247     else:
248       logging.debug('Unhandled inspector message: %s', res)
249
250   def SendAndIgnoreResponse(self, req):
251     self._Connect()
252     req['id'] = self._next_request_id
253     self._next_request_id += 1
254     data = json.dumps(req)
255     self._socket.send(data)
256     logging.debug('sent [%s]', data)
257
258   def _SetTimeout(self, timeout):
259     if self._cur_socket_timeout != timeout:
260       self._socket.settimeout(timeout)
261       self._cur_socket_timeout = timeout
262
263   def _WaitForInspectorToGoAwayAndReconnect(self):
264     sys.stderr.write('The connection to Chrome was lost to the Inspector UI.\n')
265     sys.stderr.write('Telemetry is waiting for the inspector to be closed...\n')
266     self._socket.close()
267     self._socket = None
268     def IsBack():
269       if not self._browser_backend.tab_list_backend.DoesDebuggerUrlExist(
270         self._debugger_url):
271         return False
272       try:
273         self._Connect()
274       except exceptions.TabCrashException, ex:
275         if ex.message.message.find('Handshake Status 500') == 0:
276           return False
277         raise
278       return True
279     util.WaitFor(IsBack, 512)
280     sys.stderr.write('\n')
281     sys.stderr.write('Inspector\'s UI closed. Telemetry will now resume.\n')
282
283   def SyncRequest(self, req, timeout=10):
284     self._Connect(timeout)
285     self._SetTimeout(timeout)
286     self.SendAndIgnoreResponse(req)
287
288     while True:
289       res = self._ReceiveJsonData(timeout)
290       if 'method' in res:
291         self._HandleNotification(res)
292         continue
293       if 'id' not in res or res['id'] != req['id']:
294         logging.debug('Dropped reply: %s', json.dumps(res))
295         continue
296       return res
297
298   def RegisterDomain(self,
299       domain_name, notification_handler, will_close_handler):
300     """Registers a given domain for handling notification methods.
301
302     For example, given inspector_backend:
303        def OnConsoleNotification(msg):
304           if msg['method'] == 'Console.messageAdded':
305              print msg['params']['message']
306           return
307        def OnConsoleClose(self):
308           pass
309        inspector_backend.RegisterDomain('Console',
310                                         OnConsoleNotification, OnConsoleClose)
311        """
312     assert domain_name not in self._domain_handlers
313     self._domain_handlers[domain_name] = (notification_handler,
314                                           will_close_handler)
315
316   def UnregisterDomain(self, domain_name):
317     """Unregisters a previously registered domain."""
318     assert domain_name in self._domain_handlers
319     self._domain_handlers.pop(domain_name)
320
321   def CollectGarbage(self):
322     self._page.CollectGarbage()
323
324   def TakeJSHeapSnapshot(self, timeout=120):
325     snapshot = []
326
327     def OnNotification(res):
328       if res['method'] == 'HeapProfiler.addHeapSnapshotChunk':
329         snapshot.append(res['params']['chunk'])
330
331     def OnClose():
332       pass
333
334     self.RegisterDomain('HeapProfiler', OnNotification, OnClose)
335
336     self.SyncRequest({'method': 'Page.getResourceTree'}, timeout)
337     self.SyncRequest({'method': 'Debugger.enable'}, timeout)
338     self.SyncRequest({'method': 'HeapProfiler.takeHeapSnapshot'}, timeout)
339     snapshot = ''.join(snapshot)
340
341     self.UnregisterDomain('HeapProfiler')
342     return model.Model(snapshot)