profiler: add per-thread cProfile support
threading.setprofile installs a bootstrap that creates a per-thread cProfile.Profile. Stats from all threads are merged on periodic dumps and at exit, capturing worker-thread hotspots (audio send loop, pitch shifting).
This commit is contained in:
@@ -31,10 +31,17 @@ def main():
|
||||
|
||||
|
||||
def _run_profiled(app, dest):
|
||||
"""Run the app under cProfile with periodic 30s dumps."""
|
||||
"""Run the app under cProfile with periodic 30s dumps.
|
||||
|
||||
Profiles both the main thread and all worker threads (including
|
||||
the audio send loop where PitchShifter.process runs). Each
|
||||
thread gets its own cProfile.Profile; stats are merged on dump.
|
||||
"""
|
||||
import cProfile
|
||||
import pstats
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from threading import Event, Thread
|
||||
from threading import Event, Lock, Thread
|
||||
|
||||
if dest is None:
|
||||
from tuimble.config import CONFIG_DIR
|
||||
@@ -47,10 +54,34 @@ def _run_profiled(app, dest):
|
||||
|
||||
prof = cProfile.Profile()
|
||||
stop = Event()
|
||||
thread_profiles: list[cProfile.Profile] = []
|
||||
lock = Lock()
|
||||
|
||||
def _thread_bootstrap(frame, event, arg):
|
||||
"""Installed via threading.setprofile; creates a per-thread profiler.
|
||||
|
||||
Called once per new thread as the profile function. Immediately
|
||||
creates and enables a cProfile.Profile whose C-level hook
|
||||
replaces this Python-level one for the remainder of the thread.
|
||||
"""
|
||||
tp = cProfile.Profile()
|
||||
with lock:
|
||||
thread_profiles.append(tp)
|
||||
tp.enable()
|
||||
|
||||
threading.setprofile(_thread_bootstrap)
|
||||
|
||||
def _dump_all():
|
||||
"""Merge main + thread profiles and write to disk."""
|
||||
stats = pstats.Stats(prof)
|
||||
with lock:
|
||||
for tp in thread_profiles:
|
||||
stats.add(tp)
|
||||
stats.dump_stats(str(dest))
|
||||
|
||||
def _periodic_dump():
|
||||
while not stop.wait(30):
|
||||
prof.dump_stats(str(dest))
|
||||
_dump_all()
|
||||
|
||||
dumper = Thread(target=_periodic_dump, daemon=True)
|
||||
dumper.start()
|
||||
@@ -60,8 +91,12 @@ def _run_profiled(app, dest):
|
||||
app.run()
|
||||
finally:
|
||||
prof.disable()
|
||||
threading.setprofile(None)
|
||||
with lock:
|
||||
for tp in thread_profiles:
|
||||
tp.disable()
|
||||
stop.set()
|
||||
prof.dump_stats(str(dest))
|
||||
_dump_all()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user