api: load wildpc.yaml once per listing, not once per item
/services and /jobs called get_wildpc_root() + load_config() *inside* the loop over registry.all(), so every item paid a full reparse of wildpc.yaml and all 77 program/deployment YAMLs (~55ms registry + ~103ms config). With 38 services and 12 jobs on this node that was 6.8s and 3.0s per request, uncached — the dashboard's /services and /scheduled pages each fire one of these plus /status, which is where their >10s load came from. Hoist the load to a single _try_load_config() call per request; the source backfill and the non-deployed pass both read that one config. Responses are byte-identical to before; both endpoints now serve in ~0.27s.
This commit is contained in:
@@ -233,6 +233,23 @@ def _make_systemd_info(name: str, timer: bool = False) -> SystemdInfo:
|
||||
return SystemdInfo(unit_name=unit_name, unit_path=unit_path, timer=timer)
|
||||
|
||||
|
||||
def _try_load_config(root: Path | None) -> object | None:
|
||||
"""Load wildpc.yaml for `root`, or None when there's no config to read.
|
||||
|
||||
Listing endpoints call this **once** and reuse the result: load_config()
|
||||
reparses every program/deployment YAML, so a per-item load turns a listing
|
||||
into an N x full-config-parse (seconds, not milliseconds).
|
||||
"""
|
||||
if root is None:
|
||||
return None
|
||||
from wildpc_core.config import load_config
|
||||
|
||||
try:
|
||||
return load_config(root)
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
|
||||
def _backfill_source(name: str, config: object) -> str | None:
|
||||
"""Resolve source path from program ref in config."""
|
||||
if name in config.programs:
|
||||
@@ -429,6 +446,10 @@ def list_services(include_remote: bool = False) -> list[ServiceSummary]:
|
||||
summaries: list[ServiceSummary] = []
|
||||
seen: set[str] = set()
|
||||
|
||||
# One config load for the whole listing — the source backfill and the
|
||||
# non-deployed pass below both read it.
|
||||
config = _try_load_config(get_wildpc_root())
|
||||
|
||||
# Services page shows services (systemd) AND statics (caddy) — both are
|
||||
# exposed, URL-reachable "services". Not jobs, tools, or remotes.
|
||||
for _kind, name, deployed in registry.all():
|
||||
@@ -437,33 +458,19 @@ def list_services(include_remote: bool = False) -> list[ServiceSummary]:
|
||||
s = _service_from_deployed(name, deployed)
|
||||
s.node = hostname
|
||||
# Backfill source
|
||||
root = get_wildpc_root()
|
||||
if root and s.source is None:
|
||||
try:
|
||||
from wildpc_core.config import load_config
|
||||
|
||||
config = load_config(root)
|
||||
s.source = _backfill_source(name, config)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
if config is not None and s.source is None:
|
||||
s.source = _backfill_source(name, config)
|
||||
summaries.append(s)
|
||||
seen.add(name)
|
||||
|
||||
# Non-deployed from wildpc.yaml
|
||||
root = get_wildpc_root()
|
||||
if root:
|
||||
try:
|
||||
from wildpc_core.config import load_config
|
||||
|
||||
config = load_config(root)
|
||||
for name, svc in config.services.items():
|
||||
if name not in seen:
|
||||
s = _service_from_spec(name, svc, config)
|
||||
s.node = hostname
|
||||
summaries.append(s)
|
||||
seen.add(name)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
if config is not None:
|
||||
for name, svc in config.services.items():
|
||||
if name not in seen:
|
||||
s = _service_from_spec(name, svc, config)
|
||||
s.node = hostname
|
||||
summaries.append(s)
|
||||
seen.add(name)
|
||||
|
||||
# Remote
|
||||
if include_remote:
|
||||
@@ -553,39 +560,28 @@ def list_jobs(include_remote: bool = False) -> list[JobSummary]:
|
||||
summaries: list[JobSummary] = []
|
||||
seen: set[str] = set()
|
||||
|
||||
# One config load for the whole listing (see list_services).
|
||||
config = _try_load_config(get_wildpc_root())
|
||||
|
||||
# Deployed jobs (scheduled)
|
||||
for _kind, name, deployed in registry.all():
|
||||
if not deployed.schedule:
|
||||
continue
|
||||
s = _job_from_deployed(name, deployed)
|
||||
s.node = hostname
|
||||
root = get_wildpc_root()
|
||||
if root and s.source is None:
|
||||
try:
|
||||
from wildpc_core.config import load_config
|
||||
|
||||
config = load_config(root)
|
||||
s.source = _backfill_source(name, config)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
if config is not None and s.source is None:
|
||||
s.source = _backfill_source(name, config)
|
||||
summaries.append(s)
|
||||
seen.add(name)
|
||||
|
||||
# Non-deployed from wildpc.yaml
|
||||
root = get_wildpc_root()
|
||||
if root:
|
||||
try:
|
||||
from wildpc_core.config import load_config
|
||||
|
||||
config = load_config(root)
|
||||
for name, job in config.jobs.items():
|
||||
if name not in seen:
|
||||
s = _job_from_spec(name, job, config)
|
||||
s.node = hostname
|
||||
summaries.append(s)
|
||||
seen.add(name)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
if config is not None:
|
||||
for name, job in config.jobs.items():
|
||||
if name not in seen:
|
||||
s = _job_from_spec(name, job, config)
|
||||
s.node = hostname
|
||||
summaries.append(s)
|
||||
seen.add(name)
|
||||
|
||||
# Remote
|
||||
if include_remote:
|
||||
|
||||
Reference in New Issue
Block a user