Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(336)

Side by Side Diff: chrome/test/pyautolib/remote_inspector_client.py

Issue 11615021: Adopt inspector protocol changes. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Take-2 Created 8 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Chrome remote inspector utility for pyauto tests. 6 """Chrome remote inspector utility for pyauto tests.
7 7
8 This script provides a python interface that acts as a front-end for Chrome's 8 This script provides a python interface that acts as a front-end for Chrome's
9 remote inspector module, communicating via sockets to interact with Chrome in 9 remote inspector module, communicating via sockets to interact with Chrome in
10 the same way that the Developer Tools does. This -- in theory -- should allow 10 the same way that the Developer Tools does. This -- in theory -- should allow
(...skipping 23 matching lines...) Expand all
34 at a time. If a second instance is instantiated, a RuntimeError will be raised. 34 at a time. If a second instance is instantiated, a RuntimeError will be raised.
35 RemoteInspectorClient could be made into a singleton in the future if the need 35 RemoteInspectorClient could be made into a singleton in the future if the need
36 for it arises. 36 for it arises.
37 """ 37 """
38 38
39 import asyncore 39 import asyncore
40 import datetime 40 import datetime
41 import logging 41 import logging
42 import optparse 42 import optparse
43 import pprint 43 import pprint
44 import re
44 import simplejson 45 import simplejson
45 import socket 46 import socket
46 import sys 47 import sys
47 import threading 48 import threading
48 import time 49 import time
49 import urllib2 50 import urllib2
50 import urlparse 51 import urlparse
51 52
52 53
53 class _DevToolsSocketRequest(object): 54 class _DevToolsSocketRequest(object):
(...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after
270 271
271 class _RemoteInspectorThread(threading.Thread): 272 class _RemoteInspectorThread(threading.Thread):
272 """Manages communication using Chrome's remote inspector protocol. 273 """Manages communication using Chrome's remote inspector protocol.
273 274
274 This class works in conjunction with the _DevToolsSocketClient class to 275 This class works in conjunction with the _DevToolsSocketClient class to
275 communicate with a remote Chrome instance following the remote inspector 276 communicate with a remote Chrome instance following the remote inspector
276 communication protocol in WebKit. This class performs the higher-level work 277 communication protocol in WebKit. This class performs the higher-level work
277 of managing request and reply messages, whereas _DevToolsSocketClient handles 278 of managing request and reply messages, whereas _DevToolsSocketClient handles
278 the lower-level work of socket communication. 279 the lower-level work of socket communication.
279 """ 280 """
280 def __init__(self, tab_index, verbose, show_socket_messages): 281 def __init__(self, url, tab_index, verbose, show_socket_messages):
281 """Initialize. 282 """Initialize.
282 283
283 Args: 284 Args:
285 url: The base URL to connent to.
284 tab_index: The integer index of the tab in the remote Chrome instance to 286 tab_index: The integer index of the tab in the remote Chrome instance to
285 use for snapshotting. 287 use for snapshotting.
286 verbose: A boolean indicating whether or not to use verbose logging. 288 verbose: A boolean indicating whether or not to use verbose logging.
287 show_socket_messages: A boolean indicating whether or not to show the 289 show_socket_messages: A boolean indicating whether or not to show the
288 socket messages sent/received when communicating with the remote 290 socket messages sent/received when communicating with the remote
289 Chrome instance. 291 Chrome instance.
290 """ 292 """
291 threading.Thread.__init__(self) 293 threading.Thread.__init__(self)
292 self._logger = logging.getLogger('_RemoteInspectorThread') 294 self._logger = logging.getLogger('_RemoteInspectorThread')
293 self._logger.setLevel([logging.WARNING, logging.DEBUG][verbose]) 295 self._logger.setLevel([logging.WARNING, logging.DEBUG][verbose])
294 296
295 self._killed = False 297 self._killed = False
296 self._requests = [] 298 self._requests = []
297 self._action_queue = [] 299 self._action_queue = []
298 self._action_queue_condition = threading.Condition() 300 self._action_queue_condition = threading.Condition()
299 self._action_specific_callback = None # Callback only for current action. 301 self._action_specific_callback = None # Callback only for current action.
300 self._action_specific_callback_lock = threading.Lock() 302 self._action_specific_callback_lock = threading.Lock()
301 self._general_callbacks = [] # General callbacks that can be long-lived. 303 self._general_callbacks = [] # General callbacks that can be long-lived.
302 self._general_callbacks_lock = threading.Lock() 304 self._general_callbacks_lock = threading.Lock()
303 self._condition_to_wait = None 305 self._condition_to_wait = None
304 306
305 # Create a DevToolsSocket client and wait for it to complete the remote 307 # Create a DevToolsSocket client and wait for it to complete the remote
306 # debugging protocol handshake with the remote Chrome instance. 308 # debugging protocol handshake with the remote Chrome instance.
307 result = self._IdentifyDevToolsSocketConnectionInfo(tab_index) 309 result = self._IdentifyDevToolsSocketConnectionInfo(url, tab_index)
308 self._client = _DevToolsSocketClient( 310 self._client = _DevToolsSocketClient(
309 verbose, show_socket_messages, result['host'], result['port'], 311 verbose, show_socket_messages, result['host'], result['port'],
310 result['path']) 312 result['path'])
311 self._client.inspector_thread = self 313 self._client.inspector_thread = self
312 while asyncore.socket_map: 314 while asyncore.socket_map:
313 if self._client.handshake_done or self._killed: 315 if self._client.handshake_done or self._killed:
314 break 316 break
315 asyncore.loop(timeout=1, count=1, use_poll=True) 317 asyncore.loop(timeout=1, count=1, use_poll=True)
316 318
317 def ClientSocketExceptionOccurred(self): 319 def ClientSocketExceptionOccurred(self):
(...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after
499 def _FillInParams(self, request): 501 def _FillInParams(self, request):
500 """Fills in parameters for requests as necessary before the request is sent. 502 """Fills in parameters for requests as necessary before the request is sent.
501 503
502 Args: 504 Args:
503 request: The _DevToolsSocketRequest object associated with a request 505 request: The _DevToolsSocketRequest object associated with a request
504 message that is about to be sent. 506 message that is about to be sent.
505 """ 507 """
506 if request.method == 'Profiler.takeHeapSnapshot': 508 if request.method == 'Profiler.takeHeapSnapshot':
507 # We always want detailed v8 heap snapshot information. 509 # We always want detailed v8 heap snapshot information.
508 request.params = {'detailed': True} 510 request.params = {'detailed': True}
509 elif request.method == 'Profiler.getProfile': 511 elif request.method == 'Profiler.getHeapSnapshot':
510 # To actually request the snapshot data from a previously-taken snapshot, 512 # To actually request the snapshot data from a previously-taken snapshot,
511 # we need to specify the unique uid of the snapshot we want. 513 # we need to specify the unique uid of the snapshot we want.
512 # The relevant uid should be contained in the last 514 # The relevant uid should be contained in the last
513 # 'Profiler.takeHeapSnapshot' request object. 515 # 'Profiler.takeHeapSnapshot' request object.
514 last_req = self._GetLatestRequestOfType(request, 516 last_req = self._GetLatestRequestOfType(request,
515 'Profiler.takeHeapSnapshot') 517 'Profiler.takeHeapSnapshot')
516 if last_req and 'uid' in last_req.results: 518 if last_req and 'uid' in last_req.results:
519 request.params = {'uid': last_req.results['uid']}
520 elif request.method == 'Profiler.getProfile':
521 # TODO(eustas): Remove this case after M26 is released.
522 last_req = self._GetLatestRequestOfType(request,
523 'Profiler.takeHeapSnapshot')
524 if last_req and 'uid' in last_req.results:
517 request.params = {'type': 'HEAP', 'uid': last_req.results['uid']} 525 request.params = {'type': 'HEAP', 'uid': last_req.results['uid']}
518 526
519 @staticmethod 527 @staticmethod
520 def _IdentifyDevToolsSocketConnectionInfo(tab_index): 528 def _IdentifyDevToolsSocketConnectionInfo(url, tab_index):
521 """Identifies DevToolsSocket connection info from a remote Chrome instance. 529 """Identifies DevToolsSocket connection info from a remote Chrome instance.
522 530
523 Args: 531 Args:
532 url: The base URL to connent to.
524 tab_index: The integer index of the tab in the remote Chrome instance to 533 tab_index: The integer index of the tab in the remote Chrome instance to
525 which to connect. 534 which to connect.
526 535
527 Returns: 536 Returns:
528 A dictionary containing the DevToolsSocket connection info: 537 A dictionary containing the DevToolsSocket connection info:
529 { 538 {
530 'host': string, 539 'host': string,
531 'port': integer, 540 'port': integer,
532 'path': string, 541 'path': string,
533 } 542 }
534 543
535 Raises: 544 Raises:
536 RuntimeError: When DevToolsSocket connection info cannot be identified. 545 RuntimeError: When DevToolsSocket connection info cannot be identified.
537 """ 546 """
538 try: 547 try:
539 # TODO(dennisjeffrey): Do not assume port 9222. The port should be passed 548 f = urllib2.urlopen(url + '/json')
540 # as input to this function.
541 f = urllib2.urlopen('http://localhost:9222/json')
542 result = f.read(); 549 result = f.read();
543 result = simplejson.loads(result) 550 result = simplejson.loads(result)
544 except urllib2.URLError, e: 551 except urllib2.URLError, e:
545 raise RuntimeError( 552 raise RuntimeError(
546 'Error accessing Chrome instance debugging port: ' + str(e)) 553 'Error accessing Chrome instance debugging port: ' + str(e))
547 554
548 if tab_index >= len(result): 555 if tab_index >= len(result):
549 raise RuntimeError( 556 raise RuntimeError(
550 'Specified tab index %d doesn\'t exist (%d tabs found)' % 557 'Specified tab index %d doesn\'t exist (%d tabs found)' %
551 (tab_index, len(result))) 558 (tab_index, len(result)))
(...skipping 266 matching lines...) Expand 10 before | Expand all | Expand 10 after
818 825
819 logging.basicConfig() 826 logging.basicConfig()
820 self._logger = logging.getLogger('RemoteInspectorClient') 827 self._logger = logging.getLogger('RemoteInspectorClient')
821 self._logger.setLevel([logging.WARNING, logging.DEBUG][verbose]) 828 self._logger.setLevel([logging.WARNING, logging.DEBUG][verbose])
822 829
823 # Creating _RemoteInspectorThread might raise an exception. This prevents an 830 # Creating _RemoteInspectorThread might raise an exception. This prevents an
824 # AttributeError in the destructor. 831 # AttributeError in the destructor.
825 self._remote_inspector_thread = None 832 self._remote_inspector_thread = None
826 self._remote_inspector_driver_thread = None 833 self._remote_inspector_driver_thread = None
827 834
835 # TODO(dennisjeffrey): Do not assume port 9222. The port should be passed
836 # as input to this function.
837 url = 'http://localhost:9222'
838
839 self._webkit_version = self._GetWebkitVersion(url)
840
828 # Start up a thread for long-term communication with the remote inspector. 841 # Start up a thread for long-term communication with the remote inspector.
829 self._remote_inspector_thread = _RemoteInspectorThread( 842 self._remote_inspector_thread = _RemoteInspectorThread(
830 tab_index, verbose, show_socket_messages) 843 url, tab_index, verbose, show_socket_messages)
831 self._remote_inspector_thread.start() 844 self._remote_inspector_thread.start()
832 # At this point, a connection has already been made to the remote inspector. 845 # At this point, a connection has already been made to the remote inspector.
833 846
834 # This thread calls asyncore.loop, which activates the channel service. 847 # This thread calls asyncore.loop, which activates the channel service.
835 self._remote_inspector_driver_thread = _RemoteInspectorDriverThread() 848 self._remote_inspector_driver_thread = _RemoteInspectorDriverThread()
836 self._remote_inspector_driver_thread.start() 849 self._remote_inspector_driver_thread.start()
837 850
838 def __del__(self): 851 def __del__(self):
839 """Called on destruction of this object.""" 852 """Called on destruction of this object."""
840 self.Stop() 853 self.Stop()
(...skipping 16 matching lines...) Expand all
857 snapshot that was taken. 870 snapshot that was taken.
858 { 871 {
859 'url': string, # URL of the webpage that was snapshotted. 872 'url': string, # URL of the webpage that was snapshotted.
860 'raw_data': string, # The raw data as JSON string. 873 'raw_data': string, # The raw data as JSON string.
861 'total_v8_node_count': integer, # Total number of nodes in the v8 heap. 874 'total_v8_node_count': integer, # Total number of nodes in the v8 heap.
862 # Only if |include_summary| is True. 875 # Only if |include_summary| is True.
863 'total_heap_size': integer, # Total v8 heap size (number of bytes). 876 'total_heap_size': integer, # Total v8 heap size (number of bytes).
864 # Only if |include_summary| is True. 877 # Only if |include_summary| is True.
865 } 878 }
866 """ 879 """
880 # TODO(eustas): Remove this hack after M26 is released.
881 if self._IsWebkitVersionNotOlderThan(537, 24):
882 get_heap_snapshot_method = 'Profiler.getHeapSnapshot'
883 else:
884 get_heap_snapshot_method = 'Profiler.getProfile'
885
867 HEAP_SNAPSHOT_MESSAGES = [ 886 HEAP_SNAPSHOT_MESSAGES = [
868 ('Page.getResourceTree', {}), 887 ('Page.getResourceTree', {}),
869 ('Debugger.enable', {}), 888 ('Debugger.enable', {}),
870 ('Profiler.clearProfiles', {}), 889 ('Profiler.clearProfiles', {}),
871 ('Profiler.takeHeapSnapshot', {}), 890 ('Profiler.takeHeapSnapshot', {}),
872 ('Profiler.getProfile', {}), 891 (get_heap_snapshot_method, {}),
873 ] 892 ]
874 893
875 self._current_heap_snapshot = [] 894 self._current_heap_snapshot = []
876 self._url = '' 895 self._url = ''
877 self._collected_heap_snapshot_data = {} 896 self._collected_heap_snapshot_data = {}
878 897
879 done_condition = threading.Condition() 898 done_condition = threading.Condition()
880 899
881 def HandleReply(reply_dict): 900 def HandleReply(reply_dict):
882 """Processes a reply message received from the remote Chrome instance. 901 """Processes a reply message received from the remote Chrome instance.
(...skipping 299 matching lines...) Expand 10 before | Expand all | Expand 10 after
1182 1201
1183 Returns: 1202 Returns:
1184 A human-readable string representation of the given number of bytes. 1203 A human-readable string representation of the given number of bytes.
1185 """ 1204 """
1186 if num_bytes < 1024: 1205 if num_bytes < 1024:
1187 return '%d B' % num_bytes 1206 return '%d B' % num_bytes
1188 elif num_bytes < 1048576: 1207 elif num_bytes < 1048576:
1189 return '%.2f KB' % (num_bytes / 1024.0) 1208 return '%.2f KB' % (num_bytes / 1024.0)
1190 else: 1209 else:
1191 return '%.2f MB' % (num_bytes / 1048576.0) 1210 return '%.2f MB' % (num_bytes / 1048576.0)
1211
1212 @staticmethod
1213 def _GetWebkitVersion(endpoint):
1214 """Fetches Webkit version information from a remote Chrome instance.
1215
1216 Args:
1217 endpoint: The base URL to connent to.
1218
1219 Returns:
1220 A dictionary containing Webkit version information:
1221 {
1222 'major': integer,
1223 'minor': integer,
1224 }
1225
1226 Raises:
1227 RuntimeError: When Webkit version info can't be fetched or parsed.
1228 """
1229 try:
1230 f = urllib2.urlopen(endpoint + '/json/version')
1231 result = f.read();
1232 result = simplejson.loads(result)
1233 except urllib2.URLError, e:
1234 raise RuntimeError(
1235 'Error accessing Chrome instance debugging port: ' + str(e))
1236
1237 if 'WebKit-Version' not in result:
1238 raise RuntimeError('WebKit-Version is not specified.')
1239
1240 parsed = re.search('^(\d+)\.(\d+)', result['WebKit-Version'])
1241 if parsed is None:
1242 raise RuntimeError('WebKit-Version cannot be parsed.')
1243
1244 try:
1245 info = {
1246 'major': int(parsed.group(1)),
1247 'minor': int(parsed.group(2)),
1248 }
1249 except ValueError:
1250 raise RuntimeError('WebKit-Version cannot be parsed.')
1251
1252 return info
1253
1254 def _IsWebkitVersionNotOlderThan(self, major, minor):
1255 """Compares remote Webkit version with specified one.
1256
1257 Args:
1258 major: Major Webkit version.
1259 minor: Minor Webkit version.
1260
1261 Returns:
1262 True if remote Webkit version is same or newer than specified,
1263 False otherwise.
1264
1265 Raises:
1266 RuntimeError: If remote Webkit version hasn't been fetched yet.
1267 """
1268 version = self._webkit_version
dennis_jeffrey 2012/12/20 18:22:49 will this work as expected if self._webkit_version
eustas 2012/12/21 05:51:21 Done.
1269 if version is None:
1270 raise RuntimeError('WebKit version has not been fetched yet.')
1271
1272 if version['major'] < major:
1273 return False
1274 elif version['major'] == major and version['minor'] < minor:
1275 return False
1276 else:
1277 return True
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698