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

Unified Diff: chrome/test/pyautolib/remote_inspector_client.py

Issue 222873002: Remove pyauto tests. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: sync Created 6 years, 9 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/test/pyautolib/remote_host.py ('k') | chrome/test/pyautolib/timer_queue.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/test/pyautolib/remote_inspector_client.py
===================================================================
--- chrome/test/pyautolib/remote_inspector_client.py (revision 261231)
+++ chrome/test/pyautolib/remote_inspector_client.py (working copy)
@@ -1,1211 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Chrome remote inspector utility for pyauto tests.
-
-This script provides a python interface that acts as a front-end for Chrome's
-remote inspector module, communicating via sockets to interact with Chrome in
-the same way that the Developer Tools does. This -- in theory -- should allow
-a pyauto test to do anything that Chrome's Developer Tools does, as long as the
-appropriate communication with the remote inspector is implemented in this
-script.
-
-This script assumes that Chrome is already running on the local machine with
-flag '--remote-debugging-port=9222' to enable remote debugging on port 9222.
-
-To use this module, first create an instance of class RemoteInspectorClient;
-doing this sets up a connection to Chrome's remote inspector. Then call the
-appropriate functions on that object to perform the desired actions with the
-remote inspector. When done, call Stop() on the RemoteInspectorClient object
-to stop communication with the remote inspector.
-
-For example, to take v8 heap snapshots from a pyauto test:
-
-import remote_inspector_client
-my_client = remote_inspector_client.RemoteInspectorClient()
-snapshot_info = my_client.HeapSnapshot(include_summary=True)
-// Do some stuff...
-new_snapshot_info = my_client.HeapSnapshot(include_summary=True)
-my_client.Stop()
-
-It is expected that a test will only use one instance of RemoteInspectorClient
-at a time. If a second instance is instantiated, a RuntimeError will be raised.
-RemoteInspectorClient could be made into a singleton in the future if the need
-for it arises.
-"""
-
-import asyncore
-import datetime
-import logging
-import optparse
-import pprint
-import re
-import simplejson
-import socket
-import sys
-import threading
-import time
-import urllib2
-import urlparse
-
-
-class _DevToolsSocketRequest(object):
- """A representation of a single DevToolsSocket request.
-
- A DevToolsSocket request is used for communication with a remote Chrome
- instance when interacting with the renderer process of a given webpage.
- Requests and results are passed as specially-formatted JSON messages,
- according to a communication protocol defined in WebKit. The string
- representation of this request will be a JSON message that is properly
- formatted according to the communication protocol.
-
- Public Attributes:
- method: The string method name associated with this request.
- id: A unique integer id associated with this request.
- params: A dictionary of input parameters associated with this request.
- results: A dictionary of relevant results obtained from the remote Chrome
- instance that are associated with this request.
- is_fulfilled: A boolean indicating whether or not this request has been sent
- and all relevant results for it have been obtained (i.e., this value is
- True only if all results for this request are known).
- is_fulfilled_condition: A threading.Condition for waiting for the request to
- be fulfilled.
- """
-
- def __init__(self, method, params, message_id):
- """Initialize.
-
- Args:
- method: The string method name for this request.
- message_id: An integer id for this request, which is assumed to be unique
- from among all requests.
- """
- self.method = method
- self.id = message_id
- self.params = params
- self.results = {}
- self.is_fulfilled = False
- self.is_fulfilled_condition = threading.Condition()
-
- def __repr__(self):
- json_dict = {}
- json_dict['method'] = self.method
- json_dict['id'] = self.id
- if self.params:
- json_dict['params'] = self.params
- return simplejson.dumps(json_dict, separators=(',', ':'))
-
-
-class _DevToolsSocketClient(asyncore.dispatcher):
- """Client that communicates with a remote Chrome instance via sockets.
-
- This class works in conjunction with the _RemoteInspectorThread class to
- communicate with a remote Chrome instance following the remote debugging
- communication protocol in WebKit. This class performs the lower-level work
- of socket communication.
-
- Public Attributes:
- handshake_done: A boolean indicating whether or not the client has completed
- the required protocol handshake with the remote Chrome instance.
- inspector_thread: An instance of the _RemoteInspectorThread class that is
- working together with this class to communicate with a remote Chrome
- instance.
- """
-
- def __init__(self, verbose, show_socket_messages, hostname, port, path):
- """Initialize.
-
- Args:
- verbose: A boolean indicating whether or not to use verbose logging.
- show_socket_messages: A boolean indicating whether or not to show the
- socket messages sent/received when communicating with the remote
- Chrome instance.
- hostname: The string hostname of the DevToolsSocket to which to connect.
- port: The integer port number of the DevToolsSocket to which to connect.
- path: The string path of the DevToolsSocket to which to connect.
- """
- asyncore.dispatcher.__init__(self)
-
- self._logger = logging.getLogger('_DevToolsSocketClient')
- self._logger.setLevel([logging.WARNING, logging.DEBUG][verbose])
-
- self._show_socket_messages = show_socket_messages
-
- self._read_buffer = ''
- self._write_buffer = ''
-
- self._socket_buffer_lock = threading.Lock()
-
- self.handshake_done = False
- self.inspector_thread = None
-
- # Connect to the remote Chrome instance and initiate the protocol handshake.
- self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
- self.connect((hostname, port))
-
- fields = [
- 'Upgrade: WebSocket',
- 'Connection: Upgrade',
- 'Host: %s:%d' % (hostname, port),
- 'Origin: http://%s:%d' % (hostname, port),
- 'Sec-WebSocket-Key1: 4k0L66E ZU 8 5 <18 <TK 7 7',
- 'Sec-WebSocket-Key2: s2 20 `# 4| 3 9 U_ 1299',
- ]
- handshake_msg = ('GET %s HTTP/1.1\r\n%s\r\n\r\n\x47\x30\x22\x2D\x5A\x3F'
- '\x47\x58' % (path, '\r\n'.join(fields)))
- self._Write(handshake_msg.encode('utf-8'))
-
- def SendMessage(self, msg):
- """Causes a request message to be sent to the remote Chrome instance.
-
- Args:
- msg: A string message to be sent; assumed to be a JSON message in proper
- format according to the remote debugging protocol in WebKit.
- """
- # According to the communication protocol, each request message sent over
- # the wire must begin with '\x00' and end with '\xff'.
- self._Write('\x00' + msg.encode('utf-8') + '\xff')
-
- def _Write(self, msg):
- """Causes a raw message to be sent to the remote Chrome instance.
-
- Args:
- msg: A raw string message to be sent.
- """
- self._write_buffer += msg
- self.handle_write()
-
- def handle_write(self):
- """Called if a writable socket can be written; overridden from asyncore."""
- self._socket_buffer_lock.acquire()
- if self._write_buffer:
- sent = self.send(self._write_buffer)
- if self._show_socket_messages:
- msg_type = ['Handshake', 'Message'][self._write_buffer[0] == '\x00' and
- self._write_buffer[-1] == '\xff']
- msg = ('========================\n'
- 'Sent %s:\n'
- '========================\n'
- '%s\n'
- '========================') % (msg_type,
- self._write_buffer[:sent-1])
- print msg
- self._write_buffer = self._write_buffer[sent:]
- self._socket_buffer_lock.release()
-
- def handle_read(self):
- """Called when a socket can be read; overridden from asyncore."""
- self._socket_buffer_lock.acquire()
- if self.handshake_done:
- # Process a message reply from the remote Chrome instance.
- self._read_buffer += self.recv(4096)
- pos = self._read_buffer.find('\xff')
- while pos >= 0:
- pos += len('\xff')
- data = self._read_buffer[:pos-len('\xff')]
- pos2 = data.find('\x00')
- if pos2 >= 0:
- data = data[pos2 + 1:]
- self._read_buffer = self._read_buffer[pos:]
- if self._show_socket_messages:
- msg = ('========================\n'
- 'Received Message:\n'
- '========================\n'
- '%s\n'
- '========================') % data
- print msg
- if self.inspector_thread:
- self.inspector_thread.NotifyReply(data)
- pos = self._read_buffer.find('\xff')
- else:
- # Process a handshake reply from the remote Chrome instance.
- self._read_buffer += self.recv(4096)
- pos = self._read_buffer.find('\r\n\r\n')
- if pos >= 0:
- pos += len('\r\n\r\n')
- data = self._read_buffer[:pos]
- self._read_buffer = self._read_buffer[pos:]
- self.handshake_done = True
- if self._show_socket_messages:
- msg = ('=========================\n'
- 'Received Handshake Reply:\n'
- '=========================\n'
- '%s\n'
- '=========================') % data
- print msg
- self._socket_buffer_lock.release()
-
- def handle_close(self):
- """Called when the socket is closed; overridden from asyncore."""
- if self._show_socket_messages:
- msg = ('=========================\n'
- 'Socket closed.\n'
- '=========================')
- print msg
- self.close()
-
- def writable(self):
- """Determines if writes can occur for this socket; overridden from asyncore.
-
- Returns:
- True, if there is something to write to the socket, or
- False, otherwise.
- """
- return len(self._write_buffer) > 0
-
- def handle_expt(self):
- """Called when out-of-band data exists; overridden from asyncore."""
- self.handle_error()
-
- def handle_error(self):
- """Called when an exception is raised; overridden from asyncore."""
- if self._show_socket_messages:
- msg = ('=========================\n'
- 'Socket error.\n'
- '=========================')
- print msg
- self.close()
- self.inspector_thread.ClientSocketExceptionOccurred()
- asyncore.dispatcher.handle_error(self)
-
-
-class _RemoteInspectorThread(threading.Thread):
- """Manages communication using Chrome's remote inspector protocol.
-
- This class works in conjunction with the _DevToolsSocketClient class to
- communicate with a remote Chrome instance following the remote inspector
- communication protocol in WebKit. This class performs the higher-level work
- of managing request and reply messages, whereas _DevToolsSocketClient handles
- the lower-level work of socket communication.
- """
-
- def __init__(self, url, tab_index, tab_filter, verbose, show_socket_messages,
- agent_name):
- """Initialize.
-
- Args:
- url: The base URL to connent to.
- tab_index: The integer index of the tab in the remote Chrome instance to
- use for snapshotting.
- tab_filter: When specified, is run over tabs of the remote Chrome
- instances to choose which one to connect to.
- verbose: A boolean indicating whether or not to use verbose logging.
- show_socket_messages: A boolean indicating whether or not to show the
- socket messages sent/received when communicating with the remote
- Chrome instance.
- """
- threading.Thread.__init__(self)
- self._logger = logging.getLogger('_RemoteInspectorThread')
- self._logger.setLevel([logging.WARNING, logging.DEBUG][verbose])
-
- self._killed = False
- self._requests = []
- self._action_queue = []
- self._action_queue_condition = threading.Condition()
- self._action_specific_callback = None # Callback only for current action.
- self._action_specific_callback_lock = threading.Lock()
- self._general_callbacks = [] # General callbacks that can be long-lived.
- self._general_callbacks_lock = threading.Lock()
- self._condition_to_wait = None
- self._agent_name = agent_name
-
- # Create a DevToolsSocket client and wait for it to complete the remote
- # debugging protocol handshake with the remote Chrome instance.
- result = self._IdentifyDevToolsSocketConnectionInfo(
- url, tab_index, tab_filter)
- self._client = _DevToolsSocketClient(
- verbose, show_socket_messages, result['host'], result['port'],
- result['path'])
- self._client.inspector_thread = self
- while asyncore.socket_map:
- if self._client.handshake_done or self._killed:
- break
- asyncore.loop(timeout=1, count=1, use_poll=True)
-
- def ClientSocketExceptionOccurred(self):
- """Notifies that the _DevToolsSocketClient encountered an exception."""
- self.Kill()
-
- def NotifyReply(self, msg):
- """Notifies of a reply message received from the remote Chrome instance.
-
- Args:
- msg: A string reply message received from the remote Chrome instance;
- assumed to be a JSON message formatted according to the remote
- debugging communication protocol in WebKit.
- """
- reply_dict = simplejson.loads(msg)
-
- # Notify callbacks of this message received from the remote inspector.
- self._action_specific_callback_lock.acquire()
- if self._action_specific_callback:
- self._action_specific_callback(reply_dict)
- self._action_specific_callback_lock.release()
-
- self._general_callbacks_lock.acquire()
- if self._general_callbacks:
- for callback in self._general_callbacks:
- callback(reply_dict)
- self._general_callbacks_lock.release()
-
- if 'result' in reply_dict:
- # This is the result message associated with a previously-sent request.
- request = self.GetRequestWithId(reply_dict['id'])
- if request:
- request.is_fulfilled_condition.acquire()
- request.is_fulfilled_condition.notify()
- request.is_fulfilled_condition.release()
-
- def run(self):
- """Start this thread; overridden from threading.Thread."""
- while not self._killed:
- self._action_queue_condition.acquire()
- if self._action_queue:
- # There's a request to the remote inspector that needs to be processed.
- messages, callback = self._action_queue.pop(0)
- self._action_specific_callback_lock.acquire()
- self._action_specific_callback = callback
- self._action_specific_callback_lock.release()
-
- # Prepare the request list.
- for message_id, message in enumerate(messages):
- self._requests.append(
- _DevToolsSocketRequest(message[0], message[1], message_id))
-
- # Send out each request. Wait until each request is complete before
- # sending the next request.
- for request in self._requests:
- self._FillInParams(request)
- self._client.SendMessage(str(request))
-
- request.is_fulfilled_condition.acquire()
- self._condition_to_wait = request.is_fulfilled_condition
- request.is_fulfilled_condition.wait()
- request.is_fulfilled_condition.release()
-
- if self._killed:
- self._client.close()
- return
-
- # Clean up so things are ready for the next request.
- self._requests = []
-
- self._action_specific_callback_lock.acquire()
- self._action_specific_callback = None
- self._action_specific_callback_lock.release()
-
- # Wait until there is something to process.
- self._condition_to_wait = self._action_queue_condition
- self._action_queue_condition.wait()
- self._action_queue_condition.release()
- self._client.close()
-
- def Kill(self):
- """Notify this thread that it should stop executing."""
- self._killed = True
- # The thread might be waiting on a condition.
- if self._condition_to_wait:
- self._condition_to_wait.acquire()
- self._condition_to_wait.notify()
- self._condition_to_wait.release()
-
- def PerformAction(self, request_messages, reply_message_callback):
- """Notify this thread of an action to perform using the remote inspector.
-
- Args:
- request_messages: A list of strings representing the requests to make
- using the remote inspector.
- reply_message_callback: A callable to be invoked any time a message is
- received from the remote inspector while the current action is
- being performed. The callable should accept a single argument,
- which is a dictionary representing a message received.
- """
- self._action_queue_condition.acquire()
- self._action_queue.append((request_messages, reply_message_callback))
- self._action_queue_condition.notify()
- self._action_queue_condition.release()
-
- def AddMessageCallback(self, callback):
- """Add a callback to invoke for messages received from the remote inspector.
-
- Args:
- callback: A callable to be invoked any time a message is received from the
- remote inspector. The callable should accept a single argument, which
- is a dictionary representing a message received.
- """
- self._general_callbacks_lock.acquire()
- self._general_callbacks.append(callback)
- self._general_callbacks_lock.release()
-
- def RemoveMessageCallback(self, callback):
- """Remove a callback from the set of those to invoke for messages received.
-
- Args:
- callback: A callable to remove from consideration.
- """
- self._general_callbacks_lock.acquire()
- self._general_callbacks.remove(callback)
- self._general_callbacks_lock.release()
-
- def GetRequestWithId(self, request_id):
- """Identifies the request with the specified id.
-
- Args:
- request_id: An integer request id; should be unique for each request.
-
- Returns:
- A request object associated with the given id if found, or
- None otherwise.
- """
- found_request = [x for x in self._requests if x.id == request_id]
- if found_request:
- return found_request[0]
- return None
-
- def GetFirstUnfulfilledRequest(self, method):
- """Identifies the first unfulfilled request with the given method name.
-
- An unfulfilled request is one for which all relevant reply messages have
- not yet been received from the remote inspector.
-
- Args:
- method: The string method name of the request for which to search.
-
- Returns:
- The first request object in the request list that is not yet fulfilled
- and is also associated with the given method name, or
- None if no such request object can be found.
- """
- for request in self._requests:
- if not request.is_fulfilled and request.method == method:
- return request
- return None
-
- def _GetLatestRequestOfType(self, ref_req, method):
- """Identifies the latest specified request before a reference request.
-
- This function finds the latest request with the specified method that
- occurs before the given reference request.
-
- Args:
- ref_req: A reference request from which to start looking.
- method: The string method name of the request for which to search.
-
- Returns:
- The latest _DevToolsSocketRequest object with the specified method,
- if found, or None otherwise.
- """
- start_looking = False
- for request in self._requests[::-1]:
- if request.id == ref_req.id:
- start_looking = True
- elif start_looking:
- if request.method == method:
- return request
- return None
-
- def _FillInParams(self, request):
- """Fills in parameters for requests as necessary before the request is sent.
-
- Args:
- request: The _DevToolsSocketRequest object associated with a request
- message that is about to be sent.
- """
- if request.method == self._agent_name +'.takeHeapSnapshot':
- # We always want detailed v8 heap snapshot information.
- request.params = {'detailed': True}
- elif request.method == self._agent_name + '.getHeapSnapshot':
- # To actually request the snapshot data from a previously-taken snapshot,
- # we need to specify the unique uid of the snapshot we want.
- # The relevant uid should be contained in the last
- # 'Profiler.takeHeapSnapshot' request object.
- last_req = self._GetLatestRequestOfType(request,
- self._agent_name + '.takeHeapSnapshot')
- if last_req and 'uid' in last_req.results:
- request.params = {'uid': last_req.results['uid']}
- elif request.method == self._agent_name + '.getProfile':
- # TODO(eustas): Remove this case after M27 is released.
- last_req = self._GetLatestRequestOfType(request,
- self._agent_name + '.takeHeapSnapshot')
- if last_req and 'uid' in last_req.results:
- request.params = {'type': 'HEAP', 'uid': last_req.results['uid']}
-
- @staticmethod
- def _IdentifyDevToolsSocketConnectionInfo(url, tab_index, tab_filter):
- """Identifies DevToolsSocket connection info from a remote Chrome instance.
-
- Args:
- url: The base URL to connent to.
- tab_index: The integer index of the tab in the remote Chrome instance to
- which to connect.
- tab_filter: When specified, is run over tabs of the remote Chrome instance
- to choose which one to connect to.
-
- Returns:
- A dictionary containing the DevToolsSocket connection info:
- {
- 'host': string,
- 'port': integer,
- 'path': string,
- }
-
- Raises:
- RuntimeError: When DevToolsSocket connection info cannot be identified.
- """
- try:
- f = urllib2.urlopen(url + '/json')
- result = f.read()
- logging.debug(result)
- result = simplejson.loads(result)
- except urllib2.URLError, e:
- raise RuntimeError(
- 'Error accessing Chrome instance debugging port: ' + str(e))
-
- if tab_filter:
- connect_to = filter(tab_filter, result)[0]
- else:
- if tab_index >= len(result):
- raise RuntimeError(
- 'Specified tab index %d doesn\'t exist (%d tabs found)' %
- (tab_index, len(result)))
- connect_to = result[tab_index]
-
- logging.debug(simplejson.dumps(connect_to))
-
- if 'webSocketDebuggerUrl' not in connect_to:
- raise RuntimeError('No socket URL exists for the specified tab.')
-
- socket_url = connect_to['webSocketDebuggerUrl']
- parsed = urlparse.urlparse(socket_url)
- # On ChromeOS, the "ws://" scheme may not be recognized, leading to an
- # incorrect netloc (and empty hostname and port attributes) in |parsed|.
- # Change the scheme to "http://" to fix this.
- if not parsed.hostname or not parsed.port:
- socket_url = 'http' + socket_url[socket_url.find(':'):]
- parsed = urlparse.urlparse(socket_url)
- # Warning: |parsed.scheme| is incorrect after this point.
- return ({'host': parsed.hostname,
- 'port': parsed.port,
- 'path': parsed.path})
-
-
-class _RemoteInspectorDriverThread(threading.Thread):
- """Drives the communication service with the remote inspector."""
-
- def __init__(self):
- """Initialize."""
- threading.Thread.__init__(self)
-
- def run(self):
- """Drives the communication service with the remote inspector."""
- try:
- while asyncore.socket_map:
- asyncore.loop(timeout=1, count=1, use_poll=True)
- except KeyboardInterrupt:
- pass
-
-
-class _V8HeapSnapshotParser(object):
- """Parses v8 heap snapshot data."""
- _CHILD_TYPES = ['context', 'element', 'property', 'internal', 'hidden',
- 'shortcut', 'weak']
- _NODE_TYPES = ['hidden', 'array', 'string', 'object', 'code', 'closure',
- 'regexp', 'number', 'native', 'synthetic']
-
- @staticmethod
- def ParseSnapshotData(raw_data):
- """Parses raw v8 heap snapshot data and returns the summarized results.
-
- The raw heap snapshot data is represented as a JSON object with the
- following keys: 'snapshot', 'nodes', and 'strings'.
-
- The 'snapshot' value provides the 'title' and 'uid' attributes for the
- snapshot. For example:
- { u'title': u'org.webkit.profiles.user-initiated.1', u'uid': 1}
-
- The 'nodes' value is a list of node information from the v8 heap, with a
- special first element that describes the node serialization layout (see
- HeapSnapshotJSONSerializer::SerializeNodes). All other list elements
- contain information about nodes in the v8 heap, according to the
- serialization layout.
-
- The 'strings' value is a list of strings, indexed by values in the 'nodes'
- list to associate nodes with strings.
-
- Args:
- raw_data: A string representing the raw v8 heap snapshot data.
-
- Returns:
- A dictionary containing the summarized v8 heap snapshot data:
- {
- 'total_v8_node_count': integer, # Total number of nodes in the v8 heap.
- 'total_shallow_size': integer, # Total heap size, in bytes.
- }
- """
- total_node_count = 0
- total_shallow_size = 0
- constructors = {}
-
- # TODO(dennisjeffrey): The following line might be slow, especially on
- # ChromeOS. Investigate faster alternatives.
- heap = simplejson.loads(raw_data)
-
- index = 1 # Bypass the special first node list item.
- node_list = heap['nodes']
- while index < len(node_list):
- node_type = node_list[index]
- node_name = node_list[index + 1]
- node_id = node_list[index + 2]
- node_self_size = node_list[index + 3]
- node_retained_size = node_list[index + 4]
- node_dominator = node_list[index + 5]
- node_children_count = node_list[index + 6]
- index += 7
-
- node_children = []
- for i in xrange(node_children_count):
- child_type = node_list[index]
- child_type_string = _V8HeapSnapshotParser._CHILD_TYPES[int(child_type)]
- child_name_index = node_list[index + 1]
- child_to_node = node_list[index + 2]
- index += 3
-
- child_info = {
- 'type': child_type_string,
- 'name_or_index': child_name_index,
- 'to_node': child_to_node,
- }
- node_children.append(child_info)
-
- # Get the constructor string for this node so nodes can be grouped by
- # constructor.
- # See HeapSnapshot.js: WebInspector.HeapSnapshotNode.prototype.
- type_string = _V8HeapSnapshotParser._NODE_TYPES[int(node_type)]
- constructor_name = None
- if type_string == 'hidden':
- constructor_name = '(system)'
- elif type_string == 'object':
- constructor_name = heap['strings'][int(node_name)]
- elif type_string == 'native':
- pos = heap['strings'][int(node_name)].find('/')
- if pos >= 0:
- constructor_name = heap['strings'][int(node_name)][:pos].rstrip()
- else:
- constructor_name = heap['strings'][int(node_name)]
- elif type_string == 'code':
- constructor_name = '(compiled code)'
- else:
- constructor_name = '(' + type_string + ')'
-
- node_obj = {
- 'type': type_string,
- 'name': heap['strings'][int(node_name)],
- 'id': node_id,
- 'self_size': node_self_size,
- 'retained_size': node_retained_size,
- 'dominator': node_dominator,
- 'children_count': node_children_count,
- 'children': node_children,
- }
-
- if constructor_name not in constructors:
- constructors[constructor_name] = []
- constructors[constructor_name].append(node_obj)
-
- total_node_count += 1
- total_shallow_size += node_self_size
-
- # TODO(dennisjeffrey): Have this function also return more detailed v8
- # heap snapshot data when a need for it arises (e.g., using |constructors|).
- result = {}
- result['total_v8_node_count'] = total_node_count
- result['total_shallow_size'] = total_shallow_size
- return result
-
-
-# TODO(dennisjeffrey): The "verbose" option used in this file should re-use
-# pyauto's verbose flag.
-class RemoteInspectorClient(object):
- """Main class for interacting with Chrome's remote inspector.
-
- Upon initialization, a socket connection to Chrome's remote inspector will
- be established. Users of this class should call Stop() to close the
- connection when it's no longer needed.
-
- Public Methods:
- Stop: Close the connection to the remote inspector. Should be called when
- a user is done using this module.
- HeapSnapshot: Takes a v8 heap snapshot and returns the summarized data.
- GetMemoryObjectCounts: Retrieves memory object count information.
- CollectGarbage: Forces a garbage collection.
- StartTimelineEventMonitoring: Starts monitoring for timeline events.
- StopTimelineEventMonitoring: Stops monitoring for timeline events.
- """
-
- # TODO(dennisjeffrey): Allow a user to specify a window index too (not just a
- # tab index), when running through PyAuto.
- def __init__(self, tab_index=0, tab_filter=None,
- verbose=False, show_socket_messages=False,
- url='http://localhost:9222'):
- """Initialize.
-
- Args:
- tab_index: The integer index of the tab in the remote Chrome instance to
- which to connect. Defaults to 0 (the first tab).
- tab_filter: When specified, is run over tabs of the remote Chrome
- instance to choose which one to connect to.
- verbose: A boolean indicating whether or not to use verbose logging.
- show_socket_messages: A boolean indicating whether or not to show the
- socket messages sent/received when communicating with the remote
- Chrome instance.
- """
- self._tab_index = tab_index
- self._tab_filter = tab_filter
- self._verbose = verbose
- self._show_socket_messages = show_socket_messages
-
- self._timeline_started = False
-
- logging.basicConfig()
- self._logger = logging.getLogger('RemoteInspectorClient')
- self._logger.setLevel([logging.WARNING, logging.DEBUG][verbose])
-
- # Creating _RemoteInspectorThread might raise an exception. This prevents an
- # AttributeError in the destructor.
- self._remote_inspector_thread = None
- self._remote_inspector_driver_thread = None
-
- self._version = self._GetVersion(url)
-
- # TODO(loislo): Remove this hack after M28 is released.
- self._agent_name = 'Profiler'
- if self._IsBrowserDayNumberGreaterThan(1470):
- self._agent_name = 'HeapProfiler'
-
- # Start up a thread for long-term communication with the remote inspector.
- self._remote_inspector_thread = _RemoteInspectorThread(
- url, tab_index, tab_filter, verbose, show_socket_messages,
- self._agent_name)
- self._remote_inspector_thread.start()
- # At this point, a connection has already been made to the remote inspector.
-
- # This thread calls asyncore.loop, which activates the channel service.
- self._remote_inspector_driver_thread = _RemoteInspectorDriverThread()
- self._remote_inspector_driver_thread.start()
-
- def __del__(self):
- """Called on destruction of this object."""
- self.Stop()
-
- def Stop(self):
- """Stop/close communication with the remote inspector."""
- if self._remote_inspector_thread:
- self._remote_inspector_thread.Kill()
- self._remote_inspector_thread.join()
- self._remote_inspector_thread = None
- if self._remote_inspector_driver_thread:
- self._remote_inspector_driver_thread.join()
- self._remote_inspector_driver_thread = None
-
- def HeapSnapshot(self, include_summary=False):
- """Takes a v8 heap snapshot.
-
- Returns:
- A dictionary containing information for a single v8 heap
- snapshot that was taken.
- {
- 'url': string, # URL of the webpage that was snapshotted.
- 'raw_data': string, # The raw data as JSON string.
- 'total_v8_node_count': integer, # Total number of nodes in the v8 heap.
- # Only if |include_summary| is True.
- 'total_heap_size': integer, # Total v8 heap size (number of bytes).
- # Only if |include_summary| is True.
- }
- """
- HEAP_SNAPSHOT_MESSAGES = [
- ('Page.getResourceTree', {}),
- ('Debugger.enable', {}),
- (self._agent_name + '.clearProfiles', {}),
- (self._agent_name + '.takeHeapSnapshot', {}),
- (self._agent_name + '.getHeapSnapshot', {}),
- ]
-
- self._current_heap_snapshot = []
- self._url = ''
- self._collected_heap_snapshot_data = {}
-
- done_condition = threading.Condition()
-
- def HandleReply(reply_dict):
- """Processes a reply message received from the remote Chrome instance.
-
- Args:
- reply_dict: A dictionary object representing the reply message received
- from the remote inspector.
- """
- if 'result' in reply_dict:
- # This is the result message associated with a previously-sent request.
- request = self._remote_inspector_thread.GetRequestWithId(
- reply_dict['id'])
- if 'frameTree' in reply_dict['result']:
- self._url = reply_dict['result']['frameTree']['frame']['url']
- elif request.method == self._agent_name + '.getHeapSnapshot':
- # A heap snapshot has been completed. Analyze and output the data.
- self._logger.debug('Heap snapshot taken: %s', self._url)
- # TODO(dennisjeffrey): Parse the heap snapshot on-the-fly as the data
- # is coming in over the wire, so we can avoid storing the entire
- # snapshot string in memory.
- raw_snapshot_data = ''.join(self._current_heap_snapshot)
- self._collected_heap_snapshot_data = {
- 'url': self._url,
- 'raw_data': raw_snapshot_data}
- if include_summary:
- self._logger.debug('Now analyzing heap snapshot...')
- parser = _V8HeapSnapshotParser()
- time_start = time.time()
- self._logger.debug('Raw snapshot data size: %.2f MB',
- len(raw_snapshot_data) / (1024.0 * 1024.0))
- result = parser.ParseSnapshotData(raw_snapshot_data)
- self._logger.debug('Time to parse data: %.2f sec',
- time.time() - time_start)
- count = result['total_v8_node_count']
- self._collected_heap_snapshot_data['total_v8_node_count'] = count
- total_size = result['total_shallow_size']
- self._collected_heap_snapshot_data['total_heap_size'] = total_size
-
- done_condition.acquire()
- done_condition.notify()
- done_condition.release()
- elif 'method' in reply_dict:
- # This is an auxiliary message sent from the remote Chrome instance.
- if reply_dict['method'] == self._agent_name + '.addProfileHeader':
- snapshot_req = (
- self._remote_inspector_thread.GetFirstUnfulfilledRequest(
- self._agent_name + '.takeHeapSnapshot'))
- if snapshot_req:
- snapshot_req.results['uid'] = reply_dict['params']['header']['uid']
- elif reply_dict['method'] == self._agent_name + '.addHeapSnapshotChunk':
- self._current_heap_snapshot.append(reply_dict['params']['chunk'])
-
- # Tell the remote inspector to take a v8 heap snapshot, then wait until
- # the snapshot information is available to return.
- self._remote_inspector_thread.PerformAction(HEAP_SNAPSHOT_MESSAGES,
- HandleReply)
-
- done_condition.acquire()
- done_condition.wait()
- done_condition.release()
-
- return self._collected_heap_snapshot_data
-
- def EvaluateJavaScript(self, expression):
- """Evaluates a JavaScript expression and returns the result.
-
- Sends a message containing the expression to the remote Chrome instance we
- are connected to, and evaluates it in the context of the tab we are
- connected to. Blocks until the result is available and returns it.
-
- Returns:
- A dictionary representing the result.
- """
- EVALUATE_MESSAGES = [
- ('Runtime.evaluate', { 'expression': expression,
- 'objectGroup': 'group',
- 'returnByValue': True }),
- ('Runtime.releaseObjectGroup', { 'objectGroup': 'group' })
- ]
-
- self._result = None
- done_condition = threading.Condition()
-
- def HandleReply(reply_dict):
- """Processes a reply message received from the remote Chrome instance.
-
- Args:
- reply_dict: A dictionary object representing the reply message received
- from the remote Chrome instance.
- """
- if 'result' in reply_dict and 'result' in reply_dict['result']:
- self._result = reply_dict['result']['result']['value']
-
- done_condition.acquire()
- done_condition.notify()
- done_condition.release()
-
- # Tell the remote inspector to evaluate the given expression, then wait
- # until that information is available to return.
- self._remote_inspector_thread.PerformAction(EVALUATE_MESSAGES,
- HandleReply)
-
- done_condition.acquire()
- done_condition.wait()
- done_condition.release()
-
- return self._result
-
- def GetMemoryObjectCounts(self):
- """Retrieves memory object count information.
-
- Returns:
- A dictionary containing the memory object count information:
- {
- 'DOMNodeCount': integer, # Total number of DOM nodes.
- 'EventListenerCount': integer, # Total number of event listeners.
- }
- """
- MEMORY_COUNT_MESSAGES = [
- ('Memory.getDOMCounters', {})
- ]
-
- self._event_listener_count = None
- self._dom_node_count = None
-
- done_condition = threading.Condition()
- def HandleReply(reply_dict):
- """Processes a reply message received from the remote Chrome instance.
-
- Args:
- reply_dict: A dictionary object representing the reply message received
- from the remote Chrome instance.
- """
- if 'result' in reply_dict:
- self._event_listener_count = reply_dict['result']['jsEventListeners']
- self._dom_node_count = reply_dict['result']['nodes']
-
- done_condition.acquire()
- done_condition.notify()
- done_condition.release()
-
- # Tell the remote inspector to collect memory count info, then wait until
- # that information is available to return.
- self._remote_inspector_thread.PerformAction(MEMORY_COUNT_MESSAGES,
- HandleReply)
-
- done_condition.acquire()
- done_condition.wait()
- done_condition.release()
-
- return {
- 'DOMNodeCount': self._dom_node_count,
- 'EventListenerCount': self._event_listener_count,
- }
-
- def CollectGarbage(self):
- """Forces a garbage collection."""
- COLLECT_GARBAGE_MESSAGES = [
- ('Profiler.collectGarbage', {})
- ]
-
- # Tell the remote inspector to do a garbage collect. We can return
- # immediately, since there is no result for which to wait.
- self._remote_inspector_thread.PerformAction(COLLECT_GARBAGE_MESSAGES, None)
-
- def StartTimelineEventMonitoring(self, event_callback):
- """Starts timeline event monitoring.
-
- Args:
- event_callback: A callable to invoke whenever a timeline event is observed
- from the remote inspector. The callable should take a single input,
- which is a dictionary containing the detailed information of a
- timeline event.
- """
- if self._timeline_started:
- self._logger.warning('Timeline monitoring already started.')
- return
- TIMELINE_MESSAGES = [
- ('Timeline.start', {})
- ]
-
- self._event_callback = event_callback
-
- done_condition = threading.Condition()
- def HandleReply(reply_dict):
- """Processes a reply message received from the remote Chrome instance.
-
- Args:
- reply_dict: A dictionary object representing the reply message received
- from the remote Chrome instance.
- """
- if 'result' in reply_dict:
- done_condition.acquire()
- done_condition.notify()
- done_condition.release()
- if reply_dict.get('method') == 'Timeline.eventRecorded':
- self._event_callback(reply_dict['params']['record'])
-
- # Tell the remote inspector to start the timeline.
- self._timeline_callback = HandleReply
- self._remote_inspector_thread.AddMessageCallback(self._timeline_callback)
- self._remote_inspector_thread.PerformAction(TIMELINE_MESSAGES, None)
-
- done_condition.acquire()
- done_condition.wait()
- done_condition.release()
-
- self._timeline_started = True
-
- def StopTimelineEventMonitoring(self):
- """Stops timeline event monitoring."""
- if not self._timeline_started:
- self._logger.warning('Timeline monitoring already stopped.')
- return
- TIMELINE_MESSAGES = [
- ('Timeline.stop', {})
- ]
-
- done_condition = threading.Condition()
- def HandleReply(reply_dict):
- """Processes a reply message received from the remote Chrome instance.
-
- Args:
- reply_dict: A dictionary object representing the reply message received
- from the remote Chrome instance.
- """
- if 'result' in reply_dict:
- done_condition.acquire()
- done_condition.notify()
- done_condition.release()
-
- # Tell the remote inspector to stop the timeline.
- self._remote_inspector_thread.RemoveMessageCallback(self._timeline_callback)
- self._remote_inspector_thread.PerformAction(TIMELINE_MESSAGES, HandleReply)
-
- done_condition.acquire()
- done_condition.wait()
- done_condition.release()
-
- self._timeline_started = False
-
- def _ConvertByteCountToHumanReadableString(self, num_bytes):
- """Converts an integer number of bytes into a human-readable string.
-
- Args:
- num_bytes: An integer number of bytes.
-
- Returns:
- A human-readable string representation of the given number of bytes.
- """
- if num_bytes < 1024:
- return '%d B' % num_bytes
- elif num_bytes < 1048576:
- return '%.2f KB' % (num_bytes / 1024.0)
- else:
- return '%.2f MB' % (num_bytes / 1048576.0)
-
- @staticmethod
- def _GetVersion(endpoint):
- """Fetches version information from a remote Chrome instance.
-
- Args:
- endpoint: The base URL to connent to.
-
- Returns:
- A dictionary containing Browser and Content version information:
- {
- 'Browser': {
- 'major': integer,
- 'minor': integer,
- 'fix': integer,
- 'day': integer
- },
- 'Content': {
- 'name': string,
- 'major': integer,
- 'minor': integer
- }
- }
-
- Raises:
- RuntimeError: When Browser version info can't be fetched or parsed.
- """
- try:
- f = urllib2.urlopen(endpoint + '/json/version')
- result = f.read();
- result = simplejson.loads(result)
- except urllib2.URLError, e:
- raise RuntimeError(
- 'Error accessing Chrome instance debugging port: ' + str(e))
-
- if 'Browser' not in result:
- raise RuntimeError('Browser version is not specified.')
-
- parsed = re.search('^Chrome\/(\d+).(\d+).(\d+).(\d+)', result['Browser'])
- if parsed is None:
- raise RuntimeError('Browser-Version cannot be parsed.')
- try:
- day = int(parsed.group(3))
- browser_info = {
- 'major': int(parsed.group(1)),
- 'minor': int(parsed.group(2)),
- 'day': day,
- 'fix': int(parsed.group(4)),
- }
- except ValueError:
- raise RuntimeError('Browser-Version cannot be parsed.')
-
- if 'WebKit-Version' not in result:
- raise RuntimeError('Content-Version is not specified.')
-
- parsed = re.search('^(\d+)\.(\d+)', result['WebKit-Version'])
- if parsed is None:
- raise RuntimeError('Content-Version cannot be parsed.')
-
- try:
- platform_info = {
- 'name': 'Blink' if day > 1464 else 'WebKit',
- 'major': int(parsed.group(1)),
- 'minor': int(parsed.group(2)),
- }
- except ValueError:
- raise RuntimeError('WebKit-Version cannot be parsed.')
-
- return {
- 'browser': browser_info,
- 'platform': platform_info
- }
-
- def _IsContentVersionNotOlderThan(self, major, minor):
- """Compares remote Browser Content version with specified one.
-
- Args:
- major: Major Webkit version.
- minor: Minor Webkit version.
-
- Returns:
- True if remote Content version is same or newer than specified,
- False otherwise.
-
- Raises:
- RuntimeError: If remote Content version hasn't been fetched yet.
- """
- if not hasattr(self, '_version'):
- raise RuntimeError('Browser version has not been fetched yet.')
- version = self._version['platform']
-
- if version['major'] < major:
- return False
- elif version['major'] == major and version['minor'] < minor:
- return False
- else:
- return True
-
- def _IsBrowserDayNumberGreaterThan(self, day_number):
- """Compares remote Chromium day number with specified one.
-
- Args:
- day_number: Forth part of the chromium version.
-
- Returns:
- True if remote Chromium day number is same or newer than specified,
- False otherwise.
-
- Raises:
- RuntimeError: If remote Chromium version hasn't been fetched yet.
- """
- if not hasattr(self, '_version'):
- raise RuntimeError('Browser revision has not been fetched yet.')
- version = self._version['browser']
-
- return version['day'] > day_number
« no previous file with comments | « chrome/test/pyautolib/remote_host.py ('k') | chrome/test/pyautolib/timer_queue.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698