add --cprofile flag with periodic dump
This commit is contained in:
@@ -22,6 +22,26 @@ tuimble --host mumble.example.com --user myname
|
|||||||
- **toggle** — press to start, press again to stop (default)
|
- **toggle** — press to start, press again to stop (default)
|
||||||
- **hold** — hold key to transmit, release to stop (requires evdev)
|
- **hold** — hold key to transmit, release to stop (requires evdev)
|
||||||
|
|
||||||
|
## Profiling
|
||||||
|
|
||||||
|
```sh
|
||||||
|
tuimble --cprofile # saves to ~/.config/tuimble/profile.prof
|
||||||
|
tuimble --cprofile /tmp/tuimble.prof # saves to custom path
|
||||||
|
```
|
||||||
|
|
||||||
|
Profile data is dumped every 30 seconds and on exit, so snapshots
|
||||||
|
are available even after a crash or `kill`. Output is standard `.prof`
|
||||||
|
format:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 -m pstats /tmp/tuimble.prof # interactive explorer
|
||||||
|
# or install snakeviz for a browser-based flamegraph:
|
||||||
|
# pip install snakeviz && snakeviz /tmp/tuimble.prof
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: cProfile captures the main thread only. Background workers
|
||||||
|
started with `@work(thread=True)` are not included.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
See `~/.config/tuimble/config.toml`. All fields are optional;
|
See `~/.config/tuimble/config.toml`. All fields are optional;
|
||||||
|
|||||||
@@ -1,13 +1,67 @@
|
|||||||
"""Entry point for tuimble."""
|
"""Entry point for tuimble."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="tuimble",
|
||||||
|
description="TUI client for Mumble",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--cprofile",
|
||||||
|
nargs="?",
|
||||||
|
const=None,
|
||||||
|
default=False,
|
||||||
|
metavar="FILE",
|
||||||
|
help="run under cProfile, saving to FILE "
|
||||||
|
"(default: ~/.config/tuimble/profile.prof)",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
from tuimble.app import TuimbleApp
|
from tuimble.app import TuimbleApp
|
||||||
|
|
||||||
app = TuimbleApp()
|
app = TuimbleApp()
|
||||||
app.run()
|
|
||||||
|
if args.cprofile is not False:
|
||||||
|
_run_profiled(app, args.cprofile)
|
||||||
|
else:
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
|
||||||
|
def _run_profiled(app, dest):
|
||||||
|
"""Run the app under cProfile with periodic 30s dumps."""
|
||||||
|
import cProfile
|
||||||
|
from pathlib import Path
|
||||||
|
from threading import Event, Thread
|
||||||
|
|
||||||
|
if dest is None:
|
||||||
|
from tuimble.config import CONFIG_DIR
|
||||||
|
|
||||||
|
dest = CONFIG_DIR / "profile.prof"
|
||||||
|
else:
|
||||||
|
dest = Path(dest)
|
||||||
|
|
||||||
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
prof = cProfile.Profile()
|
||||||
|
stop = Event()
|
||||||
|
|
||||||
|
def _periodic_dump():
|
||||||
|
while not stop.wait(30):
|
||||||
|
prof.dump_stats(str(dest))
|
||||||
|
|
||||||
|
dumper = Thread(target=_periodic_dump, daemon=True)
|
||||||
|
dumper.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
prof.enable()
|
||||||
|
app.run()
|
||||||
|
finally:
|
||||||
|
prof.disable()
|
||||||
|
stop.set()
|
||||||
|
prof.dump_stats(str(dest))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user