Files
hermes-agent/tests/hermes_cli/test_plugin_compat_scan_cache.py
teknium1 416a8177c2 perf(plugins): scan each plugin's source for removed imports once per process, not once per profile
A multiplex gateway runs plugin discovery for every served profile, and each
discovery ran plugin_compat.scan_plugin (an ast.parse + two ast.walk passes per
file) over every external plugin: on an 11-profile host that was ~0.4s per
profile of identical work on the gateway boot path, ahead of adapter connect.
The scan result is now cached process-wide on the plugin dir's (relpath,
mtime_ns, size) signature whenever the loaded manifest is used; a changed file
rescans, a caller-supplied manifest bypasses the cache.

Measured over the 11-profile MCP discovery loop on the reporting host:
3.78s -> 3.01s; per empty profile 0.25-0.30s -> 0.11s.
2026-09-15 15:11:33 -07:00

44 lines
1.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""``scan_plugin`` caches its AST scan on the plugin dir's file signature.
A multiplex gateway runs plugin discovery once per served profile; before the cache every profile
re-parsed every external plugin's source (~0.4s × profiles on the gateway boot path).
"""
from __future__ import annotations
from pathlib import Path
from hermes_cli import plugin_compat
def _install_plugin(root: Path) -> Path:
plugin = root / "demo-plugin"
plugin.mkdir()
(plugin / "__init__.py").write_text("from old.facade import thing\n", encoding="utf-8")
return plugin
def test_scan_plugin_parses_each_plugin_once_until_its_files_change(tmp_path, monkeypatch) -> None:
manifest = {"old.facade": {"thing": "new.home.thing"}}
monkeypatch.setattr(plugin_compat, "load_manifest", lambda: manifest)
monkeypatch.setattr(plugin_compat, "_scan_cache", {})
parses = []
real_scan_source = plugin_compat.scan_source
monkeypatch.setattr(plugin_compat, "scan_source",
lambda src, rel, m: parses.append(rel) or real_scan_source(src, rel, m))
plugin = _install_plugin(tmp_path)
first = plugin_compat.scan_plugin(plugin)
second = plugin_compat.scan_plugin(plugin) # a second profile discovering the same plugin
assert [h.old for h in first] == ["old.facade.thing"]
assert second == first
assert parses == ["__init__.py"], "second discovery must reuse the first scan"
(plugin / "__init__.py").write_text("from old.facade import thing, other\n", encoding="utf-8")
third = plugin_compat.scan_plugin(plugin) # size changed -> rescanned
assert parses == ["__init__.py", "__init__.py"]
assert [h.old for h in third] == ["old.facade.thing"]
# A caller-supplied manifest is never served from (or written to) the cache.
plugin_compat.scan_plugin(plugin, {"old.facade": {"other": "x"}})
assert len(parses) == 3