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

1"""Read-only introspection of the git-fetcher in-memory caches. 

2 

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 

9 

10from fastapi import FastAPI, params 

11from opal_server.git_fetcher import GitPolicyFetcher 

12 

13 

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 

23 

24 

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 } 

48 

49 

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. 

56 

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 

65 

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()