diff --git a/gns3server/controller/compute.py b/gns3server/controller/compute.py index d32a1a84a..16d6037df 100644 --- a/gns3server/controller/compute.py +++ b/gns3server/controller/compute.py @@ -98,6 +98,10 @@ class Compute: self.name = name # Cache of interfaces on remote host self._interfaces_cache = None + # Cached resolution of self._host — socket.gethostbyname is a blocking + # call; resolving it on every host_ip access (several times per link + # via get_ip_on_same_subnet) freezes the event loop for all coroutines. + self._host_ip_cache = None self._connection_failure = 0 def _session(self): @@ -224,14 +228,17 @@ class Compute: """ Return the IP associated to the host """ - try: - return socket.gethostbyname(self._host) - except socket.gaierror: - return "0.0.0.0" + if self._host_ip_cache is None: + try: + self._host_ip_cache = socket.gethostbyname(self._host) + except socket.gaierror: + self._host_ip_cache = "0.0.0.0" + return self._host_ip_cache @host.setter def host(self, host): self._host = host + self._host_ip_cache = None # invalidate; re-resolve on next access if self._console_host is None: self._console_host = host diff --git a/gns3server/controller/udp_link.py b/gns3server/controller/udp_link.py index 5094beccc..680fcba51 100644 --- a/gns3server/controller/udp_link.py +++ b/gns3server/controller/udp_link.py @@ -17,6 +17,8 @@ import asyncio +import time +import logging from .controller_error import ControllerError, ControllerNotFoundError from .link import Link, _UNSET @@ -32,6 +34,9 @@ _MARKER_CAPABLE_TYPES = frozenset({ }) +log = logging.getLogger(__name__) + + class UDPLink(Link): def __init__(self, project, link_id=None): super().__init__(project, link_id=link_id) @@ -96,10 +101,12 @@ class UDPLink(Link): port_number2 = self._nodes[1]["port_number"] # Get an IP allowing communication between both host + _t0 = time.perf_counter() try: (node1_host, node2_host) = await node1.compute.get_ip_on_same_subnet(node2.compute) except ValueError as e: raise ControllerError(f"Cannot get an IP address on same subnet: {e}") + _t1 = time.perf_counter() # Reserve a UDP port on both sides in parallel. Pre-allocated ports # (used during batch project loading) are popped from memory; otherwise @@ -114,6 +121,7 @@ class UDPLink(Link): self._node1_port, self._node2_port = await asyncio.gather( _allocate_port(node1.compute), _allocate_port(node2.compute) ) + _t2 = time.perf_counter() node1_filters, node2_filters = self._get_node_filters(node1, node2) node1_markers, node2_markers = self._get_node_markers(node1, node2) @@ -171,6 +179,11 @@ class UDPLink(Link): if cleanup: await asyncio.gather(*cleanup, return_exceptions=True) raise errors[0] + _t3 = time.perf_counter() + log.info( + "UDPLink.create timing get_ip=%.3fms ports=%.3fms nio=%.3fms total=%.3fms", + 1000 * (_t1 - _t0), 1000 * (_t2 - _t1), 1000 * (_t3 - _t2), 1000 * (_t3 - _t0) + ) self._created = True # New links automatically inherit every active project-level marker # definition so the user doesn't have to reconfigure.