Index: tools/telemetry/telemetry/internal/backends/chrome_inspector/devtools_client_backend.py |
diff --git a/tools/telemetry/telemetry/internal/backends/chrome_inspector/devtools_client_backend.py b/tools/telemetry/telemetry/internal/backends/chrome_inspector/devtools_client_backend.py |
deleted file mode 100644 |
index 123629ec359410a4f73061838cca5d6bca3e6eea..0000000000000000000000000000000000000000 |
--- a/tools/telemetry/telemetry/internal/backends/chrome_inspector/devtools_client_backend.py |
+++ /dev/null |
@@ -1,475 +0,0 @@ |
-# Copyright 2014 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. |
- |
-import logging |
-import re |
-import socket |
-import sys |
- |
-from telemetry.core import exceptions |
-from telemetry import decorators |
-from telemetry.internal.backends import browser_backend |
-from telemetry.internal.backends.chrome_inspector import devtools_http |
-from telemetry.internal.backends.chrome_inspector import inspector_backend |
-from telemetry.internal.backends.chrome_inspector import inspector_websocket |
-from telemetry.internal.backends.chrome_inspector import memory_backend |
-from telemetry.internal.backends.chrome_inspector import tracing_backend |
-from telemetry.internal.backends.chrome_inspector import websocket |
-from telemetry.internal.platform.tracing_agent import chrome_tracing_agent |
-from telemetry.internal.platform.tracing_agent import ( |
- chrome_tracing_devtools_manager) |
-from telemetry.timeline import trace_data as trace_data_module |
- |
- |
-BROWSER_INSPECTOR_WEBSOCKET_URL = 'ws://127.0.0.1:%i/devtools/browser' |
- |
- |
-class TabNotFoundError(exceptions.Error): |
- pass |
- |
- |
-def IsDevToolsAgentAvailable(port, app_backend): |
- """Returns True if a DevTools agent is available on the given port.""" |
- if (isinstance(app_backend, browser_backend.BrowserBackend) and |
- app_backend.supports_tracing): |
- inspector_websocket_instance = inspector_websocket.InspectorWebsocket() |
- try: |
- if not _IsInspectorWebsocketAvailable(inspector_websocket_instance, port): |
- return False |
- finally: |
- inspector_websocket_instance.Disconnect() |
- |
- devtools_http_instance = devtools_http.DevToolsHttp(port) |
- try: |
- return _IsDevToolsAgentAvailable(devtools_http_instance) |
- finally: |
- devtools_http_instance.Disconnect() |
- |
- |
-def _IsInspectorWebsocketAvailable(inspector_websocket_instance, port): |
- try: |
- inspector_websocket_instance.Connect(BROWSER_INSPECTOR_WEBSOCKET_URL % port) |
- except websocket.WebSocketException: |
- return False |
- except socket.error: |
- return False |
- except Exception as e: |
- sys.stderr.write('Unidentified exception while checking if wesocket is' |
- 'available on port %i. Exception message: %s\n' % |
- (port, e.message)) |
- return False |
- else: |
- return True |
- |
- |
-# TODO(nednguyen): Find a more reliable way to check whether the devtool agent |
-# is still alive. |
-def _IsDevToolsAgentAvailable(devtools_http_instance): |
- try: |
- devtools_http_instance.Request('') |
- except devtools_http.DevToolsClientConnectionError: |
- return False |
- else: |
- return True |
- |
- |
-class DevToolsClientBackend(object): |
- """An object that communicates with Chrome's devtools. |
- |
- This class owns a map of InspectorBackends. It is responsible for creating |
- them and destroying them. |
- """ |
- def __init__(self, devtools_port, remote_devtools_port, app_backend): |
- """Creates a new DevToolsClientBackend. |
- |
- A DevTools agent must exist on the given devtools_port. |
- |
- Args: |
- devtools_port: The port to use to connect to DevTools agent. |
- remote_devtools_port: In some cases (e.g., app running on |
- Android device, devtools_port is the forwarded port on the |
- host platform. We also need to know the remote_devtools_port |
- so that we can uniquely identify the DevTools agent. |
- app_backend: For the app that contains the DevTools agent. |
- """ |
- self._devtools_port = devtools_port |
- self._remote_devtools_port = remote_devtools_port |
- self._devtools_http = devtools_http.DevToolsHttp(devtools_port) |
- self._browser_inspector_websocket = None |
- self._tracing_backend = None |
- self._memory_backend = None |
- self._app_backend = app_backend |
- self._devtools_context_map_backend = _DevToolsContextMapBackend( |
- self._app_backend, self) |
- |
- if not self.supports_tracing: |
- return |
- chrome_tracing_devtools_manager.RegisterDevToolsClient( |
- self, self._app_backend.platform_backend) |
- |
- # Telemetry has started Chrome tracing if there is trace config, so start |
- # tracing on this newly created devtools client if needed. |
- trace_config = (self._app_backend.platform_backend |
- .tracing_controller_backend.GetChromeTraceConfig()) |
- if not trace_config: |
- self._CreateTracingBackendIfNeeded(is_tracing_running=False) |
- return |
- |
- if self.support_startup_tracing: |
- self._CreateTracingBackendIfNeeded(is_tracing_running=True) |
- return |
- |
- self._CreateTracingBackendIfNeeded(is_tracing_running=False) |
- self.StartChromeTracing( |
- trace_config=trace_config, |
- custom_categories=trace_config.tracing_category_filter.filter_string) |
- |
- @property |
- def remote_port(self): |
- return self._remote_devtools_port |
- |
- @property |
- def supports_tracing(self): |
- if not isinstance(self._app_backend, browser_backend.BrowserBackend): |
- return False |
- return self._app_backend.supports_tracing |
- |
- @property |
- def supports_overriding_memory_pressure_notifications(self): |
- if not isinstance(self._app_backend, browser_backend.BrowserBackend): |
- return False |
- return self._app_backend.supports_overriding_memory_pressure_notifications |
- |
- |
- @property |
- def is_tracing_running(self): |
- if not self.supports_tracing: |
- return False |
- if not self._tracing_backend: |
- return False |
- return self._tracing_backend.is_tracing_running |
- |
- @property |
- def support_startup_tracing(self): |
- # Startup tracing with --trace-config-file flag was not supported until |
- # Chromium branch number 2512 (see crrev.com/1309243004 and |
- # crrev.com/1353583002). |
- if not chrome_tracing_agent.ChromeTracingAgent.IsStartupTracingSupported( |
- self._app_backend.platform_backend): |
- return False |
- # TODO(zhenw): Remove this once stable Chrome and reference browser have |
- # passed 2512. |
- return self.GetChromeBranchNumber() >= 2512 |
- |
- def IsAlive(self): |
- """Whether the DevTools server is available and connectable.""" |
- return (self._devtools_http and |
- _IsDevToolsAgentAvailable(self._devtools_http)) |
- |
- def Close(self): |
- if self._tracing_backend: |
- self._tracing_backend.Close() |
- self._tracing_backend = None |
- if self._memory_backend: |
- self._memory_backend.Close() |
- self._memory_backend = None |
- |
- if self._devtools_context_map_backend: |
- self._devtools_context_map_backend.Clear() |
- |
- # Close the browser inspector socket last (in case the backend needs to |
- # interact with it before closing). |
- if self._browser_inspector_websocket: |
- self._browser_inspector_websocket.Disconnect() |
- self._browser_inspector_websocket = None |
- |
- assert self._devtools_http |
- self._devtools_http.Disconnect() |
- self._devtools_http = None |
- |
- |
- @decorators.Cache |
- def GetChromeBranchNumber(self): |
- # Detect version information. |
- resp = self._devtools_http.RequestJson('version') |
- if 'Protocol-Version' in resp: |
- if 'Browser' in resp: |
- branch_number_match = re.search(r'Chrome/\d+\.\d+\.(\d+)\.\d+', |
- resp['Browser']) |
- else: |
- branch_number_match = re.search( |
- r'Chrome/\d+\.\d+\.(\d+)\.\d+ (Mobile )?Safari', |
- resp['User-Agent']) |
- |
- if branch_number_match: |
- branch_number = int(branch_number_match.group(1)) |
- if branch_number: |
- return branch_number |
- |
- # Branch number can't be determined, so fail any branch number checks. |
- return 0 |
- |
- def _ListInspectableContexts(self): |
- return self._devtools_http.RequestJson('') |
- |
- def RequestNewTab(self, timeout): |
- """Creates a new tab. |
- |
- Returns: |
- A JSON string as returned by DevTools. Example: |
- { |
- "description": "", |
- "devtoolsFrontendUrl": |
- "/devtools/inspector.html?ws=host:port/devtools/page/id-string", |
- "id": "id-string", |
- "title": "Page Title", |
- "type": "page", |
- "url": "url", |
- "webSocketDebuggerUrl": "ws://host:port/devtools/page/id-string" |
- } |
- |
- Raises: |
- devtools_http.DevToolsClientConnectionError |
- """ |
- return self._devtools_http.Request('new', timeout=timeout) |
- |
- def CloseTab(self, tab_id, timeout): |
- """Closes the tab with the given id. |
- |
- Raises: |
- devtools_http.DevToolsClientConnectionError |
- TabNotFoundError |
- """ |
- try: |
- return self._devtools_http.Request('close/%s' % tab_id, |
- timeout=timeout) |
- except devtools_http.DevToolsClientUrlError: |
- error = TabNotFoundError( |
- 'Unable to close tab, tab id not found: %s' % tab_id) |
- raise error, None, sys.exc_info()[2] |
- |
- def ActivateTab(self, tab_id, timeout): |
- """Activates the tab with the given id. |
- |
- Raises: |
- devtools_http.DevToolsClientConnectionError |
- TabNotFoundError |
- """ |
- try: |
- return self._devtools_http.Request('activate/%s' % tab_id, |
- timeout=timeout) |
- except devtools_http.DevToolsClientUrlError: |
- error = TabNotFoundError( |
- 'Unable to activate tab, tab id not found: %s' % tab_id) |
- raise error, None, sys.exc_info()[2] |
- |
- def GetUrl(self, tab_id): |
- """Returns the URL of the tab with |tab_id|, as reported by devtools. |
- |
- Raises: |
- devtools_http.DevToolsClientConnectionError |
- """ |
- for c in self._ListInspectableContexts(): |
- if c['id'] == tab_id: |
- return c['url'] |
- return None |
- |
- def IsInspectable(self, tab_id): |
- """Whether the tab with |tab_id| is inspectable, as reported by devtools. |
- |
- Raises: |
- devtools_http.DevToolsClientConnectionError |
- """ |
- contexts = self._ListInspectableContexts() |
- return tab_id in [c['id'] for c in contexts] |
- |
- def GetUpdatedInspectableContexts(self): |
- """Returns an updated instance of _DevToolsContextMapBackend.""" |
- contexts = self._ListInspectableContexts() |
- self._devtools_context_map_backend._Update(contexts) |
- return self._devtools_context_map_backend |
- |
- def _CreateTracingBackendIfNeeded(self, is_tracing_running=False): |
- assert self.supports_tracing |
- if not self._tracing_backend: |
- self._CreateAndConnectBrowserInspectorWebsocketIfNeeded() |
- self._tracing_backend = tracing_backend.TracingBackend( |
- self._browser_inspector_websocket, is_tracing_running) |
- |
- def _CreateMemoryBackendIfNeeded(self): |
- assert self.supports_overriding_memory_pressure_notifications |
- if not self._memory_backend: |
- self._CreateAndConnectBrowserInspectorWebsocketIfNeeded() |
- self._memory_backend = memory_backend.MemoryBackend( |
- self._browser_inspector_websocket) |
- |
- def _CreateAndConnectBrowserInspectorWebsocketIfNeeded(self): |
- if not self._browser_inspector_websocket: |
- self._browser_inspector_websocket = ( |
- inspector_websocket.InspectorWebsocket()) |
- self._browser_inspector_websocket.Connect( |
- BROWSER_INSPECTOR_WEBSOCKET_URL % self._devtools_port) |
- |
- def IsChromeTracingSupported(self): |
- if not self.supports_tracing: |
- return False |
- self._CreateTracingBackendIfNeeded() |
- return self._tracing_backend.IsTracingSupported() |
- |
- def StartChromeTracing( |
- self, trace_config, custom_categories=None, timeout=10): |
- """ |
- Args: |
- trace_config: An tracing_config.TracingConfig instance. |
- custom_categories: An optional string containing a list of |
- comma separated categories that will be traced |
- instead of the default category set. Example: use |
- "webkit,cc,disabled-by-default-cc.debug" to trace only |
- those three event categories. |
- """ |
- assert trace_config and trace_config.enable_chrome_trace |
- self._CreateTracingBackendIfNeeded() |
- return self._tracing_backend.StartTracing( |
- trace_config, custom_categories, timeout) |
- |
- def StopChromeTracing(self, trace_data_builder, timeout=30): |
- assert self.is_tracing_running |
- try: |
- context_map = self.GetUpdatedInspectableContexts() |
- for context in context_map.contexts: |
- if context['type'] not in ['iframe', 'page', 'webview']: |
- continue |
- context_id = context['id'] |
- backend = context_map.GetInspectorBackend(context_id) |
- success = backend.EvaluateJavaScript( |
- "console.time('" + backend.id + "');" + |
- "console.timeEnd('" + backend.id + "');" + |
- "console.time.toString().indexOf('[native code]') != -1;") |
- if not success: |
- raise Exception('Page stomped on console.time') |
- trace_data_builder.AddEventsTo( |
- trace_data_module.TAB_ID_PART, [backend.id]) |
- finally: |
- self._tracing_backend.StopTracing(trace_data_builder, timeout) |
- |
- def DumpMemory(self, timeout=30): |
- """Dumps memory. |
- |
- Returns: |
- GUID of the generated dump if successful, None otherwise. |
- |
- Raises: |
- TracingTimeoutException: If more than |timeout| seconds has passed |
- since the last time any data is received. |
- TracingUnrecoverableException: If there is a websocket error. |
- TracingUnexpectedResponseException: If the response contains an error |
- or does not contain the expected result. |
- """ |
- self._CreateTracingBackendIfNeeded() |
- return self._tracing_backend.DumpMemory(timeout) |
- |
- def SetMemoryPressureNotificationsSuppressed(self, suppressed, timeout=30): |
- """Enable/disable suppressing memory pressure notifications. |
- |
- Args: |
- suppressed: If true, memory pressure notifications will be suppressed. |
- timeout: The timeout in seconds. |
- |
- Raises: |
- MemoryTimeoutException: If more than |timeout| seconds has passed |
- since the last time any data is received. |
- MemoryUnrecoverableException: If there is a websocket error. |
- MemoryUnexpectedResponseException: If the response contains an error |
- or does not contain the expected result. |
- """ |
- self._CreateMemoryBackendIfNeeded() |
- return self._memory_backend.SetMemoryPressureNotificationsSuppressed( |
- suppressed, timeout) |
- |
- def SimulateMemoryPressureNotification(self, pressure_level, timeout=30): |
- """Simulate a memory pressure notification. |
- |
- Args: |
- pressure level: The memory pressure level of the notification ('moderate' |
- or 'critical'). |
- timeout: The timeout in seconds. |
- |
- Raises: |
- MemoryTimeoutException: If more than |timeout| seconds has passed |
- since the last time any data is received. |
- MemoryUnrecoverableException: If there is a websocket error. |
- MemoryUnexpectedResponseException: If the response contains an error |
- or does not contain the expected result. |
- """ |
- self._CreateMemoryBackendIfNeeded() |
- return self._memory_backend.SimulateMemoryPressureNotification( |
- pressure_level, timeout) |
- |
- |
-class _DevToolsContextMapBackend(object): |
- def __init__(self, app_backend, devtools_client): |
- self._app_backend = app_backend |
- self._devtools_client = devtools_client |
- self._contexts = None |
- self._inspector_backends_dict = {} |
- |
- @property |
- def contexts(self): |
- """The most up to date contexts data. |
- |
- Returned in the order returned by devtools agent.""" |
- return self._contexts |
- |
- def GetContextInfo(self, context_id): |
- for context in self._contexts: |
- if context['id'] == context_id: |
- return context |
- raise KeyError('Cannot find a context with id=%s' % context_id) |
- |
- def GetInspectorBackend(self, context_id): |
- """Gets an InspectorBackend instance for the given context_id. |
- |
- This lazily creates InspectorBackend for the context_id if it does |
- not exist yet. Otherwise, it will return the cached instance.""" |
- if context_id in self._inspector_backends_dict: |
- return self._inspector_backends_dict[context_id] |
- |
- for context in self._contexts: |
- if context['id'] == context_id: |
- new_backend = inspector_backend.InspectorBackend( |
- self._app_backend.app, self._devtools_client, context) |
- self._inspector_backends_dict[context_id] = new_backend |
- return new_backend |
- |
- raise KeyError('Cannot find a context with id=%s' % context_id) |
- |
- def _Update(self, contexts): |
- # Remove InspectorBackend that is not in the current inspectable |
- # contexts list. |
- context_ids = [context['id'] for context in contexts] |
- for context_id in self._inspector_backends_dict.keys(): |
- if context_id not in context_ids: |
- backend = self._inspector_backends_dict[context_id] |
- backend.Disconnect() |
- del self._inspector_backends_dict[context_id] |
- |
- valid_contexts = [] |
- for context in contexts: |
- # If the context does not have webSocketDebuggerUrl, skip it. |
- # If an InspectorBackend is already created for the tab, |
- # webSocketDebuggerUrl will be missing, and this is expected. |
- context_id = context['id'] |
- if context_id not in self._inspector_backends_dict: |
- if 'webSocketDebuggerUrl' not in context: |
- logging.debug('webSocketDebuggerUrl missing, removing %s' |
- % context_id) |
- continue |
- valid_contexts.append(context) |
- self._contexts = valid_contexts |
- |
- def Clear(self): |
- for backend in self._inspector_backends_dict.values(): |
- backend.Disconnect() |
- self._inspector_backends_dict = {} |
- self._contexts = None |