mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-08-27 12:30:13 +03:00
perf: cache compute.host_ip + add UDPLink.create timing log
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
This commit is contained in:
parent
b155980402
commit
96d4d82716
@ -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
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user