diff --git a/docs/USAGE.md b/docs/USAGE.md index 6fcbb05..e695685 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -17,6 +17,7 @@ derp --config /path/to/derp.toml --verbose | `-c, --config PATH` | Config file path | | `-v, --verbose` | Debug logging | | `--cprofile [PATH]` | Enable cProfile, dump to PATH [derp.prof] | +| `--tracemalloc [N]` | Enable tracemalloc, capture N frames deep [10] | | `-V, --version` | Print version | | `-h, --help` | Show help | diff --git a/src/derp/cli.py b/src/derp/cli.py index 793dae1..77cd753 100644 --- a/src/derp/cli.py +++ b/src/derp/cli.py @@ -40,6 +40,14 @@ def build_parser() -> argparse.ArgumentParser: const="derp.prof", help="enable cProfile; dump stats to PATH [derp.prof]", ) + p.add_argument( + "--tracemalloc", + metavar="NFRAMES", + nargs="?", + const=10, + type=int, + help="enable tracemalloc; capture NFRAMES deep [10]", + ) p.add_argument( "-V", "--version", action="version", @@ -70,6 +78,23 @@ def _shutdown(bot: Bot) -> None: asyncio.get_running_loop().create_task(bot.conn.close()) +def _dump_tracemalloc(log: logging.Logger, limit: int = 25) -> None: + """Log top memory allocations from tracemalloc snapshot.""" + import tracemalloc + + snapshot = tracemalloc.take_snapshot() + snapshot = snapshot.filter_traces([ + tracemalloc.Filter(False, ""), + tracemalloc.Filter(False, ""), + tracemalloc.Filter(False, ""), + ]) + stats = snapshot.statistics("traceback") + total = sum(s.size for s in stats) + log.info("tracemalloc top %d (total tracked: %.1f KiB)", limit, total / 1024) + for i, stat in enumerate(stats[:limit], 1): + log.info("#%d %.1f KiB %s", i, stat.size / 1024, stat.traceback.format()[0]) + + def main(argv: list[str] | None = None) -> int: """Main entry point.""" parser = build_parser() @@ -93,6 +118,12 @@ def main(argv: list[str] | None = None) -> int: bot = Bot(config, registry) bot.load_plugins() + if args.tracemalloc: + import tracemalloc + + tracemalloc.start(args.tracemalloc) + log.info("tracemalloc enabled, nframes=%d", args.tracemalloc) + if args.cprofile: import cProfile @@ -102,6 +133,9 @@ def main(argv: list[str] | None = None) -> int: else: _run(bot) + if args.tracemalloc: + _dump_tracemalloc(log) + return 0