Coverage for /usr/local/lib/python3.10/site-packages/opal_server-0.0.0-py3.10.egg/opal_server/debug_stats.py: 37%
24 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
1"""Read-only introspection of the git-fetcher in-memory caches.
3Used only by the off-by-default /internal stats endpoint so tests can
4observe the cache growth that the memory-leak fix (PR2) eliminates.
5"""
6import os
7from pathlib import Path
8from typing import Dict, List, Optional
10from fastapi import FastAPI, params
11from opal_server.git_fetcher import GitPolicyFetcher
14def _read_rss_kb() -> int:
15 """Resident set size of this process in kilobytes (Linux), else 0."""
16 try:
17 for line in Path("/proc/self/status").read_text().splitlines():
18 if line.startswith("VmRSS:"):
19 return int(line.split()[1])
20 except (OSError, ValueError, IndexError):
21 return 0
22 return 0
25def git_fetcher_cache_stats() -> Dict:
26 """Sizes + keys of the three process-global GitPolicyFetcher caches, RSS,
27 and the worker pid (per-process caches: the pid identifies WHICH worker
28 answered, so multi-worker bed tests can assert per-worker drain)."""
29 # Snapshot each cache once (dict.copy() is a single C-level operation,
30 # atomic under the GIL): this handler runs on a Starlette worker thread
31 # while the caches are mutated on the event-loop/executor threads, so
32 # iterating the live dicts can raise "dictionary changed size during
33 # iteration", and reading len() and keys() separately can return a
34 # self-contradictory count/keys pair.
35 repo_locks = GitPolicyFetcher.repo_locks.copy()
36 repos = GitPolicyFetcher.repos.copy()
37 repos_last_fetched = GitPolicyFetcher.repos_last_fetched.copy()
38 return {
39 "pid": os.getpid(),
40 "repo_locks": len(repo_locks),
41 "repos": len(repos),
42 "repos_last_fetched": len(repos_last_fetched),
43 "rss_kb": _read_rss_kb(),
44 "repo_locks_keys": sorted(repo_locks.keys()),
45 "repos_keys": sorted(repos.keys()),
46 "repos_last_fetched_keys": sorted(repos_last_fetched.keys()),
47 }
50def register_internal_stats_route(
51 app: FastAPI,
52 enabled: bool,
53 dependencies: Optional[List[params.Depends]] = None,
54) -> None:
55 """Mount GET /internal/git-fetcher-cache-stats only when enabled.
57 ``dependencies`` are applied to the route (e.g. the server's
58 ``JWTAuthenticator``) so the endpoint is protected when JWT verification
59 is enabled. When verification is disabled — as in the test bed, which
60 leaves ``OPAL_AUTH_PUBLIC_KEY`` unset — the authenticator is a no-op and
61 the route stays reachable without a token.
62 """
63 if not enabled: 63 ↛ 71line 63 didn't jump to line 71 because the condition on line 63 was always true
64 return
66 # Deliberately a sync def: Starlette runs it in its own threadpool, which is
67 # independent of the default loop executor opal uses for git fetches
68 # (run_sync -> run_in_executor(None, ...)). So this endpoint keeps answering
69 # even when hung clones saturate the fetch executor — which is exactly the
70 # condition the offline-repo test observes through it.
71 @app.get(
72 "/internal/git-fetcher-cache-stats",
73 include_in_schema=False,
74 dependencies=dependencies or [],
75 )
76 def _git_fetcher_cache_stats() -> Dict:
77 return git_fetcher_cache_stats()