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

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: Updated target Webkit revision Created 7 years, 11 months 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 219 matching lines...) Expand 10 before | Expand all | Expand 10 after
273 class _RemoteInspectorThread(threading.Thread): 274 class _RemoteInspectorThread(threading.Thread):
274 """Manages communication using Chrome's remote inspector protocol. 275 """Manages communication using Chrome's remote inspector protocol.
275 276
276 This class works in conjunction with the _DevToolsSocketClient class to 277 This class works in conjunction with the _DevToolsSocketClient class to
277 communicate with a remote Chrome instance following the remote inspector 278 communicate with a remote Chrome instance following the remote inspector
278 communication protocol in WebKit. This class performs the higher-level work 279 communication protocol in WebKit. This class performs the higher-level work
279 of managing request and reply messages, whereas _DevToolsSocketClient handles 280 of managing request and reply messages, whereas _DevToolsSocketClient handles
280 the lower-level work of socket communication. 281 the lower-level work of socket communication.
281 """ 282 """
282 283
283 def __init__(self, tab_index, tab_filter, verbose, show_socket_messages): 284 def __init__(self, url, tab_index, tab_filter, verbose, show_socket_messages):
284 """Initialize. 285 """Initialize.
285 286
286 Args: 287 Args:
288 url: The base URL to connent to.
287 tab_index: The integer index of the tab in the remote Chrome instance to 289 tab_index: The integer index of the tab in the remote Chrome instance to
288 use for snapshotting. 290 use for snapshotting.
289 tab_filter: When specified, is run over tabs of the remote Chrome 291 tab_filter: When specified, is run over tabs of the remote Chrome
290 instances to choose which one to connect to. 292 instances to choose which one to connect to.
291 verbose: A boolean indicating whether or not to use verbose logging. 293 verbose: A boolean indicating whether or not to use verbose logging.
292 show_socket_messages: A boolean indicating whether or not to show the 294 show_socket_messages: A boolean indicating whether or not to show the
293 socket messages sent/received when communicating with the remote 295 socket messages sent/received when communicating with the remote
294 Chrome instance. 296 Chrome instance.
295 """ 297 """
296 threading.Thread.__init__(self) 298 threading.Thread.__init__(self)
297 self._logger = logging.getLogger('_RemoteInspectorThread') 299 self._logger = logging.getLogger('_RemoteInspectorThread')
298 self._logger.setLevel([logging.WARNING, logging.DEBUG][verbose]) 300 self._logger.setLevel([logging.WARNING, logging.DEBUG][verbose])
299 301
300 self._killed = False 302 self._killed = False
301 self._requests = [] 303 self._requests = []
302 self._action_queue = [] 304 self._action_queue = []
303 self._action_queue_condition = threading.Condition() 305 self._action_queue_condition = threading.Condition()
304 self._action_specific_callback = None # Callback only for current action. 306 self._action_specific_callback = None # Callback only for current action.
305 self._action_specific_callback_lock = threading.Lock() 307 self._action_specific_callback_lock = threading.Lock()
306 self._general_callbacks = [] # General callbacks that can be long-lived. 308 self._general_callbacks = [] # General callbacks that can be long-lived.
307 self._general_callbacks_lock = threading.Lock() 309 self._general_callbacks_lock = threading.Lock()
308 self._condition_to_wait = None 310 self._condition_to_wait = None
309 311
310 # Create a DevToolsSocket client and wait for it to complete the remote 312 # Create a DevToolsSocket client and wait for it to complete the remote
311 # debugging protocol handshake with the remote Chrome instance. 313 # debugging protocol handshake with the remote Chrome instance.
312 result = self._IdentifyDevToolsSocketConnectionInfo(tab_index, tab_filter) 314 result = self._IdentifyDevToolsSocketConnectionInfo(
315 url, tab_index, tab_filter)
313 self._client = _DevToolsSocketClient( 316 self._client = _DevToolsSocketClient(
314 verbose, show_socket_messages, result['host'], result['port'], 317 verbose, show_socket_messages, result['host'], result['port'],
315 result['path']) 318 result['path'])
316 self._client.inspector_thread = self 319 self._client.inspector_thread = self
317 while asyncore.socket_map: 320 while asyncore.socket_map:
318 if self._client.handshake_done or self._killed: 321 if self._client.handshake_done or self._killed:
319 break 322 break
320 asyncore.loop(timeout=1, count=1, use_poll=True) 323 asyncore.loop(timeout=1, count=1, use_poll=True)
321 324
322 def ClientSocketExceptionOccurred(self): 325 def ClientSocketExceptionOccurred(self):
(...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after
504 def _FillInParams(self, request): 507 def _FillInParams(self, request):
505 """Fills in parameters for requests as necessary before the request is sent. 508 """Fills in parameters for requests as necessary before the request is sent.
506 509
507 Args: 510 Args:
508 request: The _DevToolsSocketRequest object associated with a request 511 request: The _DevToolsSocketRequest object associated with a request
509 message that is about to be sent. 512 message that is about to be sent.
510 """ 513 """
511 if request.method == 'Profiler.takeHeapSnapshot': 514 if request.method == 'Profiler.takeHeapSnapshot':
512 # We always want detailed v8 heap snapshot information. 515 # We always want detailed v8 heap snapshot information.
513 request.params = {'detailed': True} 516 request.params = {'detailed': True}
514 elif request.method == 'Profiler.getProfile': 517 elif request.method == 'Profiler.getHeapSnapshot':
515 # To actually request the snapshot data from a previously-taken snapshot, 518 # To actually request the snapshot data from a previously-taken snapshot,
516 # we need to specify the unique uid of the snapshot we want. 519 # we need to specify the unique uid of the snapshot we want.
517 # The relevant uid should be contained in the last 520 # The relevant uid should be contained in the last
518 # 'Profiler.takeHeapSnapshot' request object. 521 # 'Profiler.takeHeapSnapshot' request object.
519 last_req = self._GetLatestRequestOfType(request, 522 last_req = self._GetLatestRequestOfType(request,
520 'Profiler.takeHeapSnapshot') 523 'Profiler.takeHeapSnapshot')
521 if last_req and 'uid' in last_req.results: 524 if last_req and 'uid' in last_req.results:
525 request.params = {'uid': last_req.results['uid']}
526 elif request.method == 'Profiler.getProfile':
527 # TODO(eustas): Remove this case after M27 is released.
528 last_req = self._GetLatestRequestOfType(request,
529 'Profiler.takeHeapSnapshot')
530 if last_req and 'uid' in last_req.results:
522 request.params = {'type': 'HEAP', 'uid': last_req.results['uid']} 531 request.params = {'type': 'HEAP', 'uid': last_req.results['uid']}
523 532
524 @staticmethod 533 @staticmethod
525 def _IdentifyDevToolsSocketConnectionInfo(tab_index, tab_filter): 534 def _IdentifyDevToolsSocketConnectionInfo(url, tab_index, tab_filter):
526 """Identifies DevToolsSocket connection info from a remote Chrome instance. 535 """Identifies DevToolsSocket connection info from a remote Chrome instance.
527 536
528 Args: 537 Args:
538 url: The base URL to connent to.
529 tab_index: The integer index of the tab in the remote Chrome instance to 539 tab_index: The integer index of the tab in the remote Chrome instance to
530 which to connect. 540 which to connect.
531 tab_filter: When specified, is run over tabs of the remote Chrome instance 541 tab_filter: When specified, is run over tabs of the remote Chrome instance
532 to choose which one to connect to. 542 to choose which one to connect to.
533 543
534 Returns: 544 Returns:
535 A dictionary containing the DevToolsSocket connection info: 545 A dictionary containing the DevToolsSocket connection info:
536 { 546 {
537 'host': string, 547 'host': string,
538 'port': integer, 548 'port': integer,
539 'path': string, 549 'path': string,
540 } 550 }
541 551
542 Raises: 552 Raises:
543 RuntimeError: When DevToolsSocket connection info cannot be identified. 553 RuntimeError: When DevToolsSocket connection info cannot be identified.
544 """ 554 """
545 try: 555 try:
546 # TODO(dennisjeffrey): Do not assume port 9222. The port should be passed 556 f = urllib2.urlopen(url + '/json')
547 # as input to this function.
548 f = urllib2.urlopen('http://localhost:9222/json')
549 result = f.read() 557 result = f.read()
550 logging.debug(result) 558 logging.debug(result)
551 result = simplejson.loads(result) 559 result = simplejson.loads(result)
552 except urllib2.URLError, e: 560 except urllib2.URLError, e:
553 raise RuntimeError( 561 raise RuntimeError(
554 'Error accessing Chrome instance debugging port: ' + str(e)) 562 'Error accessing Chrome instance debugging port: ' + str(e))
555 563
556 if tab_filter: 564 if tab_filter:
557 connect_to = filter(tab_filter, result)[0] 565 connect_to = filter(tab_filter, result)[0]
558 else: 566 else:
(...skipping 278 matching lines...) Expand 10 before | Expand all | Expand 10 after
837 845
838 logging.basicConfig() 846 logging.basicConfig()
839 self._logger = logging.getLogger('RemoteInspectorClient') 847 self._logger = logging.getLogger('RemoteInspectorClient')
840 self._logger.setLevel([logging.WARNING, logging.DEBUG][verbose]) 848 self._logger.setLevel([logging.WARNING, logging.DEBUG][verbose])
841 849
842 # Creating _RemoteInspectorThread might raise an exception. This prevents an 850 # Creating _RemoteInspectorThread might raise an exception. This prevents an
843 # AttributeError in the destructor. 851 # AttributeError in the destructor.
844 self._remote_inspector_thread = None 852 self._remote_inspector_thread = None
845 self._remote_inspector_driver_thread = None 853 self._remote_inspector_driver_thread = None
846 854
855 # TODO(dennisjeffrey): Do not assume port 9222. The port should be passed
856 # as input to this function.
857 url = 'http://localhost:9222'
858
859 self._webkit_version = self._GetWebkitVersion(url)
860
847 # Start up a thread for long-term communication with the remote inspector. 861 # Start up a thread for long-term communication with the remote inspector.
848 self._remote_inspector_thread = _RemoteInspectorThread( 862 self._remote_inspector_thread = _RemoteInspectorThread(
849 tab_index, tab_filter, verbose, show_socket_messages) 863 url, tab_index, tab_filter, verbose, show_socket_messages)
850 self._remote_inspector_thread.start() 864 self._remote_inspector_thread.start()
851 # At this point, a connection has already been made to the remote inspector. 865 # At this point, a connection has already been made to the remote inspector.
852 866
853 # This thread calls asyncore.loop, which activates the channel service. 867 # This thread calls asyncore.loop, which activates the channel service.
854 self._remote_inspector_driver_thread = _RemoteInspectorDriverThread() 868 self._remote_inspector_driver_thread = _RemoteInspectorDriverThread()
855 self._remote_inspector_driver_thread.start() 869 self._remote_inspector_driver_thread.start()
856 870
857 def __del__(self): 871 def __del__(self):
858 """Called on destruction of this object.""" 872 """Called on destruction of this object."""
859 self.Stop() 873 self.Stop()
(...skipping 16 matching lines...) Expand all
876 snapshot that was taken. 890 snapshot that was taken.
877 { 891 {
878 'url': string, # URL of the webpage that was snapshotted. 892 'url': string, # URL of the webpage that was snapshotted.
879 'raw_data': string, # The raw data as JSON string. 893 'raw_data': string, # The raw data as JSON string.
880 'total_v8_node_count': integer, # Total number of nodes in the v8 heap. 894 'total_v8_node_count': integer, # Total number of nodes in the v8 heap.
881 # Only if |include_summary| is True. 895 # Only if |include_summary| is True.
882 'total_heap_size': integer, # Total v8 heap size (number of bytes). 896 'total_heap_size': integer, # Total v8 heap size (number of bytes).
883 # Only if |include_summary| is True. 897 # Only if |include_summary| is True.
884 } 898 }
885 """ 899 """
900 # TODO(eustas): Remove this hack after M27 is released.
901 if self._IsWebkitVersionNotOlderThan(537, 27):
902 get_heap_snapshot_method = 'Profiler.getHeapSnapshot'
903 else:
904 get_heap_snapshot_method = 'Profiler.getProfile'
905
886 HEAP_SNAPSHOT_MESSAGES = [ 906 HEAP_SNAPSHOT_MESSAGES = [
887 ('Page.getResourceTree', {}), 907 ('Page.getResourceTree', {}),
888 ('Debugger.enable', {}), 908 ('Debugger.enable', {}),
889 ('Profiler.clearProfiles', {}), 909 ('Profiler.clearProfiles', {}),
890 ('Profiler.takeHeapSnapshot', {}), 910 ('Profiler.takeHeapSnapshot', {}),
891 ('Profiler.getProfile', {}), 911 (get_heap_snapshot_method, {}),
892 ] 912 ]
893 913
894 self._current_heap_snapshot = [] 914 self._current_heap_snapshot = []
895 self._url = '' 915 self._url = ''
896 self._collected_heap_snapshot_data = {} 916 self._collected_heap_snapshot_data = {}
897 917
898 done_condition = threading.Condition() 918 done_condition = threading.Condition()
899 919
900 def HandleReply(reply_dict): 920 def HandleReply(reply_dict):
901 """Processes a reply message received from the remote Chrome instance. 921 """Processes a reply message received from the remote Chrome instance.
(...skipping 299 matching lines...) Expand 10 before | Expand all | Expand 10 after
1201 1221
1202 Returns: 1222 Returns:
1203 A human-readable string representation of the given number of bytes. 1223 A human-readable string representation of the given number of bytes.
1204 """ 1224 """
1205 if num_bytes < 1024: 1225 if num_bytes < 1024:
1206 return '%d B' % num_bytes 1226 return '%d B' % num_bytes
1207 elif num_bytes < 1048576: 1227 elif num_bytes < 1048576:
1208 return '%.2f KB' % (num_bytes / 1024.0) 1228 return '%.2f KB' % (num_bytes / 1024.0)
1209 else: 1229 else:
1210 return '%.2f MB' % (num_bytes / 1048576.0) 1230 return '%.2f MB' % (num_bytes / 1048576.0)
1231
1232 @staticmethod
1233 def _GetWebkitVersion(endpoint):
1234 """Fetches Webkit version information from a remote Chrome instance.
1235
1236 Args:
1237 endpoint: The base URL to connent to.
1238
1239 Returns:
1240 A dictionary containing Webkit version information:
1241 {
1242 'major': integer,
1243 'minor': integer,
1244 }
1245
1246 Raises:
1247 RuntimeError: When Webkit version info can't be fetched or parsed.
1248 """
1249 try:
1250 f = urllib2.urlopen(endpoint + '/json/version')
1251 result = f.read();
1252 result = simplejson.loads(result)
1253 except urllib2.URLError, e:
1254 raise RuntimeError(
1255 'Error accessing Chrome instance debugging port: ' + str(e))
1256
1257 if 'WebKit-Version' not in result:
1258 raise RuntimeError('WebKit-Version is not specified.')
1259
1260 parsed = re.search('^(\d+)\.(\d+)', result['WebKit-Version'])
1261 if parsed is None:
1262 raise RuntimeError('WebKit-Version cannot be parsed.')
1263
1264 try:
1265 info = {
1266 'major': int(parsed.group(1)),
1267 'minor': int(parsed.group(2)),
1268 }
1269 except ValueError:
1270 raise RuntimeError('WebKit-Version cannot be parsed.')
1271
1272 return info
1273
1274 def _IsWebkitVersionNotOlderThan(self, major, minor):
1275 """Compares remote Webkit version with specified one.
1276
1277 Args:
1278 major: Major Webkit version.
1279 minor: Minor Webkit version.
1280
1281 Returns:
1282 True if remote Webkit version is same or newer than specified,
1283 False otherwise.
1284
1285 Raises:
1286 RuntimeError: If remote Webkit version hasn't been fetched yet.
1287 """
1288 if not hasattr(self, '_webkit_version'):
1289 raise RuntimeError('WebKit version has not been fetched yet.')
1290 version = self._webkit_version
1291
1292 if version['major'] < major:
1293 return False
1294 elif version['major'] == major and version['minor'] < minor:
1295 return False
1296 else:
1297 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