From 96d4d82716a29ca94ece807ef944c3c061cd2308 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Mon, 10 Aug 2026 23:20:40 +0800 Subject: [PATCH] perf: cache compute.host_ip + add UDPLink.create timing log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit host_ip resolved socket.gethostbyname on every access with no cache. get_ip_on_same_subnet touches host_ip 2-4 times per link, so opening a 2500-link project issued thousands of blocking DNS calls inline on the event loop — freezing all concurrent link coroutines each time. This is the most likely cause of the 12-link/s throughput (1000x below what Pool(concurrency=100) should deliver) and the burst+pause pattern. - compute.host_ip: cache the resolution in _host_ip_cache, invalidate on host setter change - UDPLink.create: timing log splitting get_ip / ports / nio so the next project-open confirms where time actually goes --- gns3server/controller/compute.py | 15 +++++++++++---- gns3server/controller/udp_link.py | 13 +++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) 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.