mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-08-27 20:40:13 +03:00
marker: batch topology dumps in bulk fan-out (per-def on 500+ links was ~1 minute)
Every per-link marker operation (start_marker / stop_marker / update_marker) called Project.dump() -- a full topology serialization + file write. A definition fan-out over 500 links therefore wrote the whole topology 500+ times (each blocking the event loop), which dominated the observed ~1 minute; the NIO round-trips themselves were negligible. Add a dump: bool = True parameter to the three per-link operations and inherit_marker (matching the existing dump param on Link.add_node). The bulk paths -- definition create fan-out, definition-update sync and re-fan-out, definition-delete cleanup, pause/resume, new-link inheritance -- pass dump=False and their caller dumps once after. apply_defs_to_new_link suppresses per-def dumps too: link create / project open dump once after, so opening a 500-link project with N definitions no longer does 500xN topology writes.
This commit is contained in:
parent
078cf92aef
commit
ad8bac8328
@ -114,7 +114,7 @@ class Link:
|
||||
"""
|
||||
return self._markers
|
||||
|
||||
async def inherit_marker(self, def_name, marker_def):
|
||||
async def inherit_marker(self, def_name, marker_def, dump=True):
|
||||
"""
|
||||
Apply a project-level marker definition to this link.
|
||||
|
||||
@ -147,6 +147,7 @@ class Link:
|
||||
highlight_duration=marker_def.get("highlight_duration"),
|
||||
enabled=not marker_def.get("paused", False),
|
||||
inherited_from=def_name,
|
||||
dump=dump,
|
||||
)
|
||||
|
||||
def _persist_markers(self):
|
||||
|
||||
@ -939,9 +939,15 @@ class Project:
|
||||
raise ControllerError(f"Marker definition '{name}' not found")
|
||||
self._marker_definitions[name]["paused"] = True
|
||||
marker_name = f"global-{name}"
|
||||
for link in list(self._links.values()):
|
||||
if marker_name in link.markers and link.markers[marker_name].get("inherited_from") == name:
|
||||
await link.update_marker(marker_name, enabled=False, inherited=True)
|
||||
affected = [
|
||||
link for link in self._links.values()
|
||||
if marker_name in link.markers and link.markers[marker_name].get("inherited_from") == name
|
||||
]
|
||||
await self._marker_apply_concurrently(
|
||||
affected,
|
||||
lambda link: link.update_marker(marker_name, enabled=False, inherited=True, dump=False),
|
||||
lambda link, e: f"Failed to pause marker {marker_name} on link {link.id}: {e}",
|
||||
)
|
||||
self.dump()
|
||||
self.emit_notification("project.updated", self.asdict())
|
||||
|
||||
@ -952,9 +958,15 @@ class Project:
|
||||
raise ControllerError(f"Marker definition '{name}' not found")
|
||||
self._marker_definitions[name]["paused"] = False
|
||||
marker_name = f"global-{name}"
|
||||
for link in list(self._links.values()):
|
||||
if marker_name in link.markers and link.markers[marker_name].get("inherited_from") == name:
|
||||
await link.update_marker(marker_name, enabled=True, inherited=True)
|
||||
affected = [
|
||||
link for link in self._links.values()
|
||||
if marker_name in link.markers and link.markers[marker_name].get("inherited_from") == name
|
||||
]
|
||||
await self._marker_apply_concurrently(
|
||||
affected,
|
||||
lambda link: link.update_marker(marker_name, enabled=True, inherited=True, dump=False),
|
||||
lambda link, e: f"Failed to resume marker {marker_name} on link {link.id}: {e}",
|
||||
)
|
||||
self.dump()
|
||||
self.emit_notification("project.updated", self.asdict())
|
||||
|
||||
@ -1058,7 +1070,7 @@ class Project:
|
||||
# needs a full re-fan-out: drop every copy, then re-apply.
|
||||
await self._marker_apply_concurrently(
|
||||
affected,
|
||||
lambda link: link.stop_marker(f"global-{name}", inherited=True),
|
||||
lambda link: link.stop_marker(f"global-{name}", inherited=True, dump=False),
|
||||
lambda link, e: f"Failed to remove inherited marker global-{name} from link {link.id}: {e}",
|
||||
)
|
||||
await self._apply_def_to_all_links(name)
|
||||
@ -1068,7 +1080,8 @@ class Project:
|
||||
affected,
|
||||
lambda link: link.update_marker(
|
||||
f"global-{name}", bpf=d["bpf"], tag=d.get("tag"), direction=d.get("direction"),
|
||||
color=d.get("color"), highlight_duration=d.get("highlight_duration"), inherited=True
|
||||
color=d.get("color"), highlight_duration=d.get("highlight_duration"), inherited=True,
|
||||
dump=False
|
||||
),
|
||||
lambda link, e: f"Failed to sync marker global-{name} on link {link.id}: {e}",
|
||||
)
|
||||
@ -1110,9 +1123,12 @@ class Project:
|
||||
"""
|
||||
|
||||
d = self._marker_definitions[def_name]
|
||||
# dump=False: per-link topology writes are the dominant cost on large
|
||||
# projects — the callers (create/update_marker_definition) dump once
|
||||
# after the fan-out.
|
||||
await self._marker_apply_concurrently(
|
||||
list(self._links.values()),
|
||||
lambda link: link.inherit_marker(def_name, d),
|
||||
lambda link: link.inherit_marker(def_name, d, dump=False),
|
||||
lambda link, e: f"Marker definition '{def_name}' could not be applied to link {link.id}: {e}",
|
||||
)
|
||||
|
||||
@ -1129,7 +1145,9 @@ class Project:
|
||||
|
||||
for def_name, d in self._marker_definitions.items():
|
||||
try:
|
||||
await link.inherit_marker(def_name, d)
|
||||
# dump=False: the caller (link create / project open) dumps once
|
||||
# after; per-def dumps here would be N full topology writes.
|
||||
await link.inherit_marker(def_name, d, dump=False)
|
||||
except ControllerError as e:
|
||||
log.warning(
|
||||
"Marker definition '%s' could not be applied to new link %s: %s",
|
||||
|
||||
@ -353,7 +353,7 @@ class UDPLink(Link):
|
||||
# explicitly deletes a marker via the REST API, and a marker is torn
|
||||
# down automatically only when its link is deleted.
|
||||
|
||||
async def start_marker(self, name, bpf, tag=None, direction=None, data_link_type="DLT_EN10MB", capture_node_id=None, color=None, highlight_duration=None, enabled=True, inherited_from=None):
|
||||
async def start_marker(self, name, bpf, tag=None, direction=None, data_link_type="DLT_EN10MB", capture_node_id=None, color=None, highlight_duration=None, enabled=True, inherited_from=None, dump=True):
|
||||
"""
|
||||
Attach a traffic-insight marker to this link.
|
||||
|
||||
@ -412,9 +412,12 @@ class UDPLink(Link):
|
||||
if self._created:
|
||||
await self.update()
|
||||
self._project.emit_notification("link.updated", self.asdict())
|
||||
self._project.dump()
|
||||
# Bulk fan-out passes dump=False: N per-link topology writes on a
|
||||
# 500-link project are the dominant cost — the caller dumps once after.
|
||||
if dump:
|
||||
self._project.dump()
|
||||
|
||||
async def stop_marker(self, name, inherited=False):
|
||||
async def stop_marker(self, name, inherited=False, dump=True):
|
||||
"""
|
||||
Remove a traffic-insight marker from this link.
|
||||
|
||||
@ -455,9 +458,10 @@ class UDPLink(Link):
|
||||
except Exception:
|
||||
pass # best-effort: old compute without the route leaves the file
|
||||
self._project.emit_notification("link.updated", self.asdict())
|
||||
self._project.dump()
|
||||
if dump:
|
||||
self._project.dump()
|
||||
|
||||
async def update_marker(self, name, bpf=None, tag=None, enabled=None, direction=_UNSET, color=None, highlight_duration=None, inherited=False):
|
||||
async def update_marker(self, name, bpf=None, tag=None, enabled=None, direction=_UNSET, color=None, highlight_duration=None, inherited=False, dump=True):
|
||||
"""
|
||||
Update an existing marker's fields and push to uBridge fine-grained — no
|
||||
full NIO reapply, so sibling markers' pcaps stay open. bpf/tag/direction
|
||||
@ -536,4 +540,5 @@ class UDPLink(Link):
|
||||
# correct in _markers; the next NIO reapply converges uBridge.
|
||||
pass
|
||||
self._project.emit_notification("link.updated", self.asdict())
|
||||
self._project.dump()
|
||||
if dump:
|
||||
self._project.dump()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user