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

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: 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, endpoint, tab_index, verbose, show_socket_messages):
281 """Initialize. 282 """Initialize.
282 283
283 Args: 284 Args:
285 endpoint: The base URL to connent to.
yurys 2012/12/20 08:04:48 may be rename it to url?
eustas 2012/12/20 08:27:30 Done.
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(
310 endpoint, tab_index)
308 self._client = _DevToolsSocketClient( 311 self._client = _DevToolsSocketClient(
309 verbose, show_socket_messages, result['host'], result['port'], 312 verbose, show_socket_messages, result['host'], result['port'],
310 result['path']) 313 result['path'])
311 self._client.inspector_thread = self 314 self._client.inspector_thread = self
312 while asyncore.socket_map: 315 while asyncore.socket_map:
313 if self._client.handshake_done or self._killed: 316 if self._client.handshake_done or self._killed:
314 break 317 break
315 asyncore.loop(timeout=1, count=1, use_poll=True) 318 asyncore.loop(timeout=1, count=1, use_poll=True)
316 319
317 def ClientSocketExceptionOccurred(self): 320 def ClientSocketExceptionOccurred(self):
(...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after
499 def _FillInParams(self, request): 502 def _FillInParams(self, request):
500 """Fills in parameters for requests as necessary before the request is sent. 503 """Fills in parameters for requests as necessary before the request is sent.
501 504
502 Args: 505 Args:
503 request: The _DevToolsSocketRequest object associated with a request 506 request: The _DevToolsSocketRequest object associated with a request
504 message that is about to be sent. 507 message that is about to be sent.
505 """ 508 """
506 if request.method == 'Profiler.takeHeapSnapshot': 509 if request.method == 'Profiler.takeHeapSnapshot':
507 # We always want detailed v8 heap snapshot information. 510 # We always want detailed v8 heap snapshot information.
508 request.params = {'detailed': True} 511 request.params = {'detailed': True}
509 elif request.method == 'Profiler.getProfile': 512 elif request.method == 'Profiler.getHeapSnapshot':
510 # To actually request the snapshot data from a previously-taken snapshot, 513 # To actually request the snapshot data from a previously-taken snapshot,
511 # we need to specify the unique uid of the snapshot we want. 514 # we need to specify the unique uid of the snapshot we want.
512 # The relevant uid should be contained in the last 515 # The relevant uid should be contained in the last
513 # 'Profiler.takeHeapSnapshot' request object. 516 # 'Profiler.takeHeapSnapshot' request object.
514 last_req = self._GetLatestRequestOfType(request, 517 last_req = self._GetLatestRequestOfType(request,
515 'Profiler.takeHeapSnapshot') 518 'Profiler.takeHeapSnapshot')
516 if last_req and 'uid' in last_req.results: 519 if last_req and 'uid' in last_req.results:
520 request.params = {'uid': last_req.results['uid']}
521 elif request.method == 'Profiler.getProfile':
522 # TODO(eustas): Remove this case after M25 is released.
yurys 2012/12/20 08:04:48 This probably should be removed after M26 as M25 h
eustas 2012/12/20 08:27:30 Done.
523 last_req = self._GetLatestRequestOfType(request,
524 'Profiler.takeHeapSnapshot')
525 if last_req and 'uid' in last_req.results:
517 request.params = {'type': 'HEAP', 'uid': last_req.results['uid']} 526 request.params = {'type': 'HEAP', 'uid': last_req.results['uid']}
518 527
519 @staticmethod 528 @staticmethod
520 def _IdentifyDevToolsSocketConnectionInfo(tab_index): 529 def _IdentifyDevToolsSocketConnectionInfo(endpoint, tab_index):
521 """Identifies DevToolsSocket connection info from a remote Chrome instance. 530 """Identifies DevToolsSocket connection info from a remote Chrome instance.
522 531
523 Args: 532 Args:
533 endpoint: The base URL to connent to.
524 tab_index: The integer index of the tab in the remote Chrome instance to 534 tab_index: The integer index of the tab in the remote Chrome instance to
525 which to connect. 535 which to connect.
526 536
527 Returns: 537 Returns:
528 A dictionary containing the DevToolsSocket connection info: 538 A dictionary containing the DevToolsSocket connection info:
529 { 539 {
530 'host': string, 540 'host': string,
531 'port': integer, 541 'port': integer,
532 'path': string, 542 'path': string,
533 } 543 }
534 544
535 Raises: 545 Raises:
536 RuntimeError: When DevToolsSocket connection info cannot be identified. 546 RuntimeError: When DevToolsSocket connection info cannot be identified.
537 """ 547 """
538 try: 548 try:
539 # TODO(dennisjeffrey): Do not assume port 9222. The port should be passed 549 f = urllib2.urlopen(endpoint + '/json')
540 # as input to this function.
541 f = urllib2.urlopen('http://localhost:9222/json')
542 result = f.read(); 550 result = f.read();
543 result = simplejson.loads(result) 551 result = simplejson.loads(result)
544 except urllib2.URLError, e: 552 except urllib2.URLError, e:
545 raise RuntimeError( 553 raise RuntimeError(
546 'Error accessing Chrome instance debugging port: ' + str(e)) 554 'Error accessing Chrome instance debugging port: ' + str(e))
547 555
548 if tab_index >= len(result): 556 if tab_index >= len(result):
549 raise RuntimeError( 557 raise RuntimeError(
550 'Specified tab index %d doesn\'t exist (%d tabs found)' % 558 'Specified tab index %d doesn\'t exist (%d tabs found)' %
551 (tab_index, len(result))) 559 (tab_index, len(result)))
(...skipping 266 matching lines...) Expand 10 before | Expand all | Expand 10 after
818 826
819 logging.basicConfig() 827 logging.basicConfig()
820 self._logger = logging.getLogger('RemoteInspectorClient') 828 self._logger = logging.getLogger('RemoteInspectorClient')
821 self._logger.setLevel([logging.WARNING, logging.DEBUG][verbose]) 829 self._logger.setLevel([logging.WARNING, logging.DEBUG][verbose])
822 830
823 # Creating _RemoteInspectorThread might raise an exception. This prevents an 831 # Creating _RemoteInspectorThread might raise an exception. This prevents an
824 # AttributeError in the destructor. 832 # AttributeError in the destructor.
825 self._remote_inspector_thread = None 833 self._remote_inspector_thread = None
826 self._remote_inspector_driver_thread = None 834 self._remote_inspector_driver_thread = None
827 835
836 # TODO(dennisjeffrey): Do not assume port 9222. The port should be passed
837 # as input to this function.
838 endpoint = 'http://localhost:9222'
839
840 self._webkit_version = self._GetWebkitVersion(endpoint)
841
828 # Start up a thread for long-term communication with the remote inspector. 842 # Start up a thread for long-term communication with the remote inspector.
829 self._remote_inspector_thread = _RemoteInspectorThread( 843 self._remote_inspector_thread = _RemoteInspectorThread(
830 tab_index, verbose, show_socket_messages) 844 endpoint, tab_index, verbose, show_socket_messages)
831 self._remote_inspector_thread.start() 845 self._remote_inspector_thread.start()
832 # At this point, a connection has already been made to the remote inspector. 846 # At this point, a connection has already been made to the remote inspector.
833 847
834 # This thread calls asyncore.loop, which activates the channel service. 848 # This thread calls asyncore.loop, which activates the channel service.
835 self._remote_inspector_driver_thread = _RemoteInspectorDriverThread() 849 self._remote_inspector_driver_thread = _RemoteInspectorDriverThread()
836 self._remote_inspector_driver_thread.start() 850 self._remote_inspector_driver_thread.start()
837 851
838 def __del__(self): 852 def __del__(self):
839 """Called on destruction of this object.""" 853 """Called on destruction of this object."""
840 self.Stop() 854 self.Stop()
(...skipping 16 matching lines...) Expand all
857 snapshot that was taken. 871 snapshot that was taken.
858 { 872 {
859 'url': string, # URL of the webpage that was snapshotted. 873 'url': string, # URL of the webpage that was snapshotted.
860 'raw_data': string, # The raw data as JSON string. 874 'raw_data': string, # The raw data as JSON string.
861 'total_v8_node_count': integer, # Total number of nodes in the v8 heap. 875 'total_v8_node_count': integer, # Total number of nodes in the v8 heap.
862 # Only if |include_summary| is True. 876 # Only if |include_summary| is True.
863 'total_heap_size': integer, # Total v8 heap size (number of bytes). 877 'total_heap_size': integer, # Total v8 heap size (number of bytes).
864 # Only if |include_summary| is True. 878 # Only if |include_summary| is True.
865 } 879 }
866 """ 880 """
881 # TODO(eustas): Remove this hack after M25 is released.
yurys 2012/12/20 08:04:48 Again "... after M26..."
eustas 2012/12/20 08:27:30 Done.
882 if self._IsWebkitVersionNotOlderThan(537, 22):
883 get_heap_snapshot_method = 'Profiler.getHeapSnapshot'
884 else:
885 get_heap_snapshot_method = 'Profiler.getProfile'
886
867 HEAP_SNAPSHOT_MESSAGES = [ 887 HEAP_SNAPSHOT_MESSAGES = [
868 ('Page.getResourceTree', {}), 888 ('Page.getResourceTree', {}),
869 ('Debugger.enable', {}), 889 ('Debugger.enable', {}),
870 ('Profiler.clearProfiles', {}), 890 ('Profiler.clearProfiles', {}),
871 ('Profiler.takeHeapSnapshot', {}), 891 ('Profiler.takeHeapSnapshot', {}),
872 ('Profiler.getProfile', {}), 892 (get_heap_snapshot_method, {}),
873 ] 893 ]
874 894
875 self._current_heap_snapshot = [] 895 self._current_heap_snapshot = []
876 self._url = '' 896 self._url = ''
877 self._collected_heap_snapshot_data = {} 897 self._collected_heap_snapshot_data = {}
878 898
879 done_condition = threading.Condition() 899 done_condition = threading.Condition()
880 900
881 def HandleReply(reply_dict): 901 def HandleReply(reply_dict):
882 """Processes a reply message received from the remote Chrome instance. 902 """Processes a reply message received from the remote Chrome instance.
(...skipping 299 matching lines...) Expand 10 before | Expand all | Expand 10 after
1182 1202
1183 Returns: 1203 Returns:
1184 A human-readable string representation of the given number of bytes. 1204 A human-readable string representation of the given number of bytes.
1185 """ 1205 """
1186 if num_bytes < 1024: 1206 if num_bytes < 1024:
1187 return '%d B' % num_bytes 1207 return '%d B' % num_bytes
1188 elif num_bytes < 1048576: 1208 elif num_bytes < 1048576:
1189 return '%.2f KB' % (num_bytes / 1024.0) 1209 return '%.2f KB' % (num_bytes / 1024.0)
1190 else: 1210 else:
1191 return '%.2f MB' % (num_bytes / 1048576.0) 1211 return '%.2f MB' % (num_bytes / 1048576.0)
1212
1213 @staticmethod
1214 def _GetWebkitVersion(endpoint):
1215 """Fetches Webkit version information from a remote Chrome instance.
1216
1217 Args:
1218 endpoint: The base URL to connent to.
1219
1220 Returns:
1221 A dictionary containing Webkit version information:
1222 {
1223 'major': integer,
1224 'minor': integer,
1225 }
1226
1227 Raises:
1228 RuntimeError: When Webkit version info can't be fetched or parsed.
1229 """
1230 try:
1231 f = urllib2.urlopen(endpoint + '/json/version')
1232 result = f.read();
1233 result = simplejson.loads(result)
1234 except urllib2.URLError, e:
1235 raise RuntimeError(
1236 'Error accessing Chrome instance debugging port: ' + str(e))
1237
1238 if 'WebKit-Version' not in result:
1239 raise RuntimeError('WebKit-Version is not specified.')
1240
1241 parsed = re.search('^(\d+)\.(\d+)', result['WebKit-Version'])
1242 if parsed is None:
1243 raise RuntimeError('WebKit-Version cannot be parsed.')
1244
1245 try:
1246 info = {
1247 'major': int(parsed.group(1)),
1248 'minor': int(parsed.group(2)),
1249 }
1250 except ValueError:
1251 raise RuntimeError('WebKit-Version cannot be parsed.')
1252
1253 return info
1254
1255 def _IsWebkitVersionNotOlderThan(self, major, minor):
1256 """Compares remote Webkit version with specified one.
1257
1258 Args:
1259 major: Major Webkit version.
1260 minor: Minor Webkit version.
1261
1262 Returns:
1263 True if remote Webkit version is same or newer than specified,
1264 False otherwise.
1265
1266 Raises:
1267 RuntimeError: If remote Webkit version hasn't been fetched yet.
1268 """
1269 version = self._webkit_version
1270 if version is None:
1271 raise RuntimeError('WebKit version has not been fetched yet.')
1272
1273 if version['major'] < major:
1274 return False
1275 elif version['major'] == major and version['minor'] < minor:
yurys 2012/12/20 08:04:48 version['minor'] < minor -> version['minor'] <= mi
eustas 2012/12/20 08:27:30 Not older than == same or newer. So if minor and
yurys 2012/12/20 12:19:26 You are right.
1276 return False
1277 else:
1278 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