10529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch# Copyright 2014 The Chromium Authors. All rights reserved. 20529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch# Use of this source code is governed by a BSD-style license that can be 30529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch# found in the LICENSE file. 40529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 50529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochimport json 60529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochimport logging 70529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochimport socket 80529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochimport time 90529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 100529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochfrom telemetry.core.backends.chrome import websocket 110529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 120529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 13116680a4aac90f2aa7413d9095a592090648e557Ben Murdochclass DispatchNotificationsUntilDoneTimeoutException(Exception): 14116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch """Exception that can be thrown from DispatchNotificationsUntilDone to 15116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch indicate timeout exception of the function. 16116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch """ 17116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 18116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch def __init__(self, elapsed_time): 19116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch super(DispatchNotificationsUntilDoneTimeoutException, self).__init__() 20116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch self.elapsed_time = elapsed_time 21116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 22116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 230529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochclass InspectorWebsocket(object): 240529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 250529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch def __init__(self, notification_handler=None, error_handler=None): 260529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch """Create a websocket handler for communicating with Inspectors. 270529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 280529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch Args: 290529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch notification_handler: A callback for notifications received as a result of 30116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch calling DispatchNotifications() or DispatchNotificationsUntilDone(). 310529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch Must accept a single JSON object containing the Inspector's 32116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch notification. May return True to indicate the dispatching is done for 33116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch DispatchNotificationsUntilDone. 340529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch error_handler: A callback for errors in communicating with the Inspector. 350529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch Must accept a single numeric parameter indicated the time elapsed before 360529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch the error. 370529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch """ 380529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._socket = None 390529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._cur_socket_timeout = 0 400529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._next_request_id = 0 410529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._notification_handler = notification_handler 420529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._error_handler = error_handler 431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci self._all_data_received = False 440529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 450529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch def Connect(self, url, timeout=10): 460529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch assert not self._socket 470529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._socket = websocket.create_connection(url, timeout=timeout) 480529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._cur_socket_timeout = 0 490529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._next_request_id = 0 500529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 510529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch def Disconnect(self): 520529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if self._socket: 530529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._socket.close() 540529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._socket = None 550529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 560529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch def SendAndIgnoreResponse(self, req): 570529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch req['id'] = self._next_request_id 580529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._next_request_id += 1 590529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch data = json.dumps(req) 600529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._socket.send(data) 611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if logging.getLogger().isEnabledFor(logging.DEBUG): 621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci logging.debug('sent [%s]', json.dumps(req, indent=2, sort_keys=True)) 630529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 640529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch def SyncRequest(self, req, timeout=10): 650529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self.SendAndIgnoreResponse(req) 660529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 670529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch while self._socket: 680529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch res = self._Receive(timeout) 690529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if 'id' in res and res['id'] == req['id']: 700529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch return res 710529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 720529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch def DispatchNotifications(self, timeout=10): 730529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._Receive(timeout) 740529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 75116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch def DispatchNotificationsUntilDone(self, timeout): 76116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch """Dispatch notifications until notification_handler return True. 770529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 78116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch Args: 791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci timeout: a number that respresents the timeout in seconds. 800529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci Raises: 821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci DispatchNotificationsUntilDoneTimeoutException if more than |timeout| has 831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci seconds has passed since the last time any data is received or since this 841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci function is called, whichever happens later, to when the next attempt to 851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci receive data fails due to some WebSocketException. 861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci """ 871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci self._all_data_received = False 88116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch if timeout < self._cur_socket_timeout: 89116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch self._SetTimeout(timeout) 901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci timeout_start_time = time.time() 910529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch while self._socket: 920529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch try: 931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if self._Receive(timeout): 941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci timeout_start_time = time.time() 951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if self._all_data_received: 960529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch break 970529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch except websocket.WebSocketTimeoutException: 980529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch pass 991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci elapsed_time = time.time() - timeout_start_time 100116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch if elapsed_time > timeout: 101116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch raise DispatchNotificationsUntilDoneTimeoutException(elapsed_time) 1020529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 1030529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch def _SetTimeout(self, timeout): 1040529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if self._cur_socket_timeout != timeout: 1050529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._socket.settimeout(timeout) 1060529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._cur_socket_timeout = timeout 1070529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 1080529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch def _Receive(self, timeout=10): 1090529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._SetTimeout(timeout) 1100529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch start_time = time.time() 1110529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch try: 1121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if self._socket: 1131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci self._all_data_received = False 1140529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch data = self._socket.recv() 1150529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch res = json.loads(data) 1161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if logging.getLogger().isEnabledFor(logging.DEBUG): 1171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci logging.debug('got [%s]', json.dumps(res, indent=2, sort_keys=True)) 1180529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if 'method' in res and self._notification_handler(res): 1191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci self._all_data_received = True 1200529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch return None 1210529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch return res 1220529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch except (socket.error, websocket.WebSocketException): 1230529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch elapsed_time = time.time() - start_time 1240529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch self._error_handler(elapsed_time) 125