feat: add --tracemalloc flag for memory profiling
Starts tracemalloc before the event loop and dumps the top 25 allocations on shutdown. Accepts optional nframes depth (default 10). Can be combined with --cprofile. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ derp --config /path/to/derp.toml --verbose
|
|||||||
| `-c, --config PATH` | Config file path |
|
| `-c, --config PATH` | Config file path |
|
||||||
| `-v, --verbose` | Debug logging |
|
| `-v, --verbose` | Debug logging |
|
||||||
| `--cprofile [PATH]` | Enable cProfile, dump to PATH [derp.prof] |
|
| `--cprofile [PATH]` | Enable cProfile, dump to PATH [derp.prof] |
|
||||||
|
| `--tracemalloc [N]` | Enable tracemalloc, capture N frames deep [10] |
|
||||||
| `-V, --version` | Print version |
|
| `-V, --version` | Print version |
|
||||||
| `-h, --help` | Show help |
|
| `-h, --help` | Show help |
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
const="derp.prof",
|
const="derp.prof",
|
||||||
help="enable cProfile; dump stats to PATH [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(
|
p.add_argument(
|
||||||
"-V", "--version",
|
"-V", "--version",
|
||||||
action="version",
|
action="version",
|
||||||
@@ -70,6 +78,23 @@ def _shutdown(bot: Bot) -> None:
|
|||||||
asyncio.get_running_loop().create_task(bot.conn.close())
|
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, "<frozen importlib._bootstrap>"),
|
||||||
|
tracemalloc.Filter(False, "<frozen importlib._bootstrap_external>"),
|
||||||
|
tracemalloc.Filter(False, "<unknown>"),
|
||||||
|
])
|
||||||
|
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:
|
def main(argv: list[str] | None = None) -> int:
|
||||||
"""Main entry point."""
|
"""Main entry point."""
|
||||||
parser = build_parser()
|
parser = build_parser()
|
||||||
@@ -93,6 +118,12 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
bot = Bot(config, registry)
|
bot = Bot(config, registry)
|
||||||
bot.load_plugins()
|
bot.load_plugins()
|
||||||
|
|
||||||
|
if args.tracemalloc:
|
||||||
|
import tracemalloc
|
||||||
|
|
||||||
|
tracemalloc.start(args.tracemalloc)
|
||||||
|
log.info("tracemalloc enabled, nframes=%d", args.tracemalloc)
|
||||||
|
|
||||||
if args.cprofile:
|
if args.cprofile:
|
||||||
import cProfile
|
import cProfile
|
||||||
|
|
||||||
@@ -102,6 +133,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
else:
|
else:
|
||||||
_run(bot)
|
_run(bot)
|
||||||
|
|
||||||
|
if args.tracemalloc:
|
||||||
|
_dump_tracemalloc(log)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user