| Index: tools/telemetry/telemetry/core/backends/chrome_inspector/devtools_http.py
 | 
| diff --git a/tools/telemetry/telemetry/core/backends/chrome_inspector/devtools_http.py b/tools/telemetry/telemetry/core/backends/chrome_inspector/devtools_http.py
 | 
| index f5fd8afdb9ef2449ad13925020e6d2d955f77526..50271048c8bf3fc0683cfdbf4248e0c68ba37800 100644
 | 
| --- a/tools/telemetry/telemetry/core/backends/chrome_inspector/devtools_http.py
 | 
| +++ b/tools/telemetry/telemetry/core/backends/chrome_inspector/devtools_http.py
 | 
| @@ -2,11 +2,11 @@
 | 
|  # Use of this source code is governed by a BSD-style license that can be
 | 
|  # found in the LICENSE file.
 | 
|  
 | 
| -import contextlib
 | 
| +import errno
 | 
|  import httplib
 | 
|  import json
 | 
|  import socket
 | 
| -import urllib2
 | 
| +import sys
 | 
|  
 | 
|  
 | 
|  class DevToolsClientConnectionError(Exception):
 | 
| @@ -18,13 +18,48 @@ class DevToolsClientUrlError(DevToolsClientConnectionError):
 | 
|  
 | 
|  
 | 
|  class DevToolsHttp(object):
 | 
| -  """A helper class to send and parse DevTools HTTP requests."""
 | 
| +  """A helper class to send and parse DevTools HTTP requests.
 | 
| +
 | 
| +  This class maintains a persistent http connection to Chrome devtools.
 | 
| +  Ideally, owners of instances of this class should call Disconnect() before
 | 
| +  disposing of the instance. Otherwise, the connection will not be closed until
 | 
| +  the instance is garbage collected.
 | 
| +  """
 | 
|  
 | 
|    def __init__(self, devtools_port):
 | 
|      self._devtools_port = devtools_port
 | 
| +    self._conn = None
 | 
| +
 | 
| +  def __del__(self):
 | 
| +    self.Disconnect()
 | 
| +
 | 
| +  def _Connect(self, timeout):
 | 
| +    """Attempts to establish a connection to Chrome devtools."""
 | 
| +    assert not self._conn
 | 
| +    try:
 | 
| +      host_port = '127.0.0.1:%i' % self._devtools_port
 | 
| +      self._conn = httplib.HTTPConnection(host_port, timeout=timeout)
 | 
| +    except (socket.error, httplib.HTTPException) as e:
 | 
| +      raise DevToolsClientConnectionError, (e,), sys.exc_info()[2]
 | 
| +
 | 
| +  def Disconnect(self):
 | 
| +    """Closes the HTTP connection."""
 | 
| +    if not self._conn:
 | 
| +      return
 | 
| +
 | 
| +    try:
 | 
| +      self._conn.close()
 | 
| +    except (socket.error, httplib.HTTPException) as e:
 | 
| +      raise DevToolsClientConnectionError, (e,), sys.exc_info()[2]
 | 
| +    finally:
 | 
| +      self._conn = None
 | 
|  
 | 
|    def Request(self, path, timeout=30):
 | 
| -    """
 | 
| +    """Sends a request to Chrome devtools.
 | 
| +
 | 
| +    This method lazily creates an HTTP connection, if one does not already
 | 
| +    exist.
 | 
| +
 | 
|      Args:
 | 
|        path: The DevTools URL path, without the /json/ prefix.
 | 
|        timeout: Timeout defaults to 30 seconds.
 | 
| @@ -32,18 +67,27 @@ class DevToolsHttp(object):
 | 
|      Raises:
 | 
|        DevToolsClientConnectionError: If the connection fails.
 | 
|      """
 | 
| -    url = 'http://127.0.0.1:%i/json' % self._devtools_port
 | 
| +    if not self._conn:
 | 
| +      self._Connect(timeout)
 | 
| +
 | 
| +    endpoint = '/json'
 | 
|      if path:
 | 
| -      url += '/' + path
 | 
| +      endpoint += '/' + path
 | 
| +    if self._conn.sock:
 | 
| +      self._conn.sock.settimeout(timeout)
 | 
| +    else:
 | 
| +      self._conn.timeout = timeout
 | 
| +
 | 
|      try:
 | 
| -      proxy_handler = urllib2.ProxyHandler({})  # Bypass any system proxy.
 | 
| -      opener = urllib2.build_opener(proxy_handler)
 | 
| -      with contextlib.closing(opener.open(url, timeout=timeout)) as req:
 | 
| -        return req.read()
 | 
| -    except urllib2.URLError as e:
 | 
| -      raise DevToolsClientUrlError(e)
 | 
| -    except (socket.error, httplib.BadStatusLine) as e:
 | 
| -      raise DevToolsClientConnectionError(e)
 | 
| +      # By default, httplib avoids going through the default system proxy.
 | 
| +      self._conn.request('GET', endpoint)
 | 
| +      response = self._conn.getresponse()
 | 
| +      return response.read()
 | 
| +    except (socket.error, httplib.HTTPException) as e:
 | 
| +      self.Disconnect()
 | 
| +      if isinstance(e, socket.error) and e.errno == errno.ECONNREFUSED:
 | 
| +        raise DevToolsClientUrlError, (e,), sys.exc_info()[2]
 | 
| +      raise DevToolsClientConnectionError, (e,), sys.exc_info()[2]
 | 
|  
 | 
|    def RequestJson(self, path, timeout=30):
 | 
|      """Sends a request and parse the response as JSON.
 | 
| 
 |