Coverage for src/asgi_profiler/__main__.py: 68%
33 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-22 16:59 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-22 16:59 +0000
1"""Serve the viewer against a `profiler.db` file, with no application.
3 python -m asgi_profiler profiler.db
5Capturing in staging and reading the file on your laptop should not require
6booting the application that produced it. The viewer is already a
7self-contained ASGI app over a `Storage`, so this is mostly argument parsing.
8"""
10from __future__ import annotations
12import argparse
13import sys
14from pathlib import Path
16from .config import ProfilerConfig
17from .storage import IncompatibleCapture, SQLiteStorage
18from .viewer import build_viewer
21def main(argv: list[str] | None = None) -> int:
22 # Reachable two ways, and each needs a different usage line. Left to
23 # argparse, `python -m asgi_profiler` would print `usage: __main__.py`,
24 # because that is what `-m` puts in argv[0]; hard-coded, the console
25 # script would print a command the user did not type.
26 if Path(sys.argv[0]).stem == "__main__": 26 ↛ 27line 26 didn't jump to line 27 because the condition on line 26 was never true
27 prog = "python -m asgi_profiler"
28 else:
29 prog = "asgi-profiler"
30 parser = argparse.ArgumentParser(
31 prog=prog,
32 description="Browse an asgi-profiler SQLite capture.",
33 )
34 parser.add_argument("database", type=Path, help="path to a profiler.db file")
35 parser.add_argument("--host", default="127.0.0.1")
36 parser.add_argument("--port", type=int, default=8080)
37 parser.add_argument("--page-size", type=int, default=50, help="rows per page (default: 50)")
38 args = parser.parse_args(argv)
40 if not args.database.exists():
41 parser.error(f"no such file: {args.database}")
43 # The capture is checked before the server dependency, so a bad file is
44 # reported as a bad file rather than as a missing uvicorn.
45 #
46 # Read-only: opening a capture to look at it must never be able to rewrite
47 # or drop it.
48 try:
49 storage = SQLiteStorage(args.database, background=False, read_only=True)
50 except IncompatibleCapture as exc:
51 parser.error(str(exc))
53 try:
54 import uvicorn
55 except ImportError: # pragma: no cover - depends on the environment
56 storage.close()
57 parser.error("uvicorn is required to serve the viewer: pip install uvicorn")
59 config = ProfilerConfig(mount_path="", page_size=args.page_size)
60 app = build_viewer(storage, config)
62 print(f"{storage.count()} requests in {args.database}")
63 print(f"viewer on http://{args.host}:{args.port}/")
64 try:
65 uvicorn.run(app, host=args.host, port=args.port, log_level="warning")
66 finally:
67 storage.close()
68 return 0
71if __name__ == "__main__": # pragma: no cover
72 sys.exit(main())