mud/scripts/profile_lostpig.py

97 lines
2.5 KiB
Python
Executable file

#!/usr/bin/env -S uv run --script
"""Profile Lost Pig V8 execution — find performance bottlenecks.
Runs the interpreter under cProfile for a few commands, then reports
the top functions by cumulative time and total time, plus callers of
expensive functions.
"""
# ruff: noqa: E402
import contextlib
import cProfile
import pstats
import sys
from pathlib import Path
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root / "src"))
from mudlib.zmachine import ZMachine, zstream, zui
from mudlib.zmachine.trivialzui import TrivialAudio, TrivialFilesystem, TrivialScreen
from mudlib.zmachine.zcpu import ZCpuQuit, ZCpuRestart
story_path = project_root / "content" / "stories" / "LostPig.z8"
if not story_path.exists():
print(f"ERROR: {story_path} not found")
sys.exit(1)
story_bytes = story_path.read_bytes()
print(f"Loaded LostPig.z8: {len(story_bytes)} bytes, version {story_bytes[0]}")
class AutoInputStream(zstream.ZInputStream):
"""Input stream that auto-feeds commands then quits."""
def __init__(self):
super().__init__()
self._commands = ["look", "look", "inventory"]
self._command_index = 0
def read_line(self, *args, **kwargs):
if self._command_index >= len(self._commands):
raise ZCpuQuit
cmd = self._commands[self._command_index]
self._command_index += 1
print(f"> {cmd}")
return cmd
def read_char(self, timed_input_routine=None, timed_input_interval=0):
# Return enter key (13) when read_char is called
return 13
audio = TrivialAudio()
screen = TrivialScreen()
keyboard = AutoInputStream()
filesystem = TrivialFilesystem()
ui = zui.ZUI(audio, screen, keyboard, filesystem)
zm = ZMachine(story_bytes, ui)
print("Running under cProfile...\n")
profiler = cProfile.Profile()
profiler.enable()
with contextlib.suppress(ZCpuQuit, ZCpuRestart):
zm.run()
profiler.disable()
print("\n" + "=" * 80)
print("PROFILING RESULTS")
print("=" * 80 + "\n")
# Create stats object
stats = pstats.Stats(profiler)
stats.strip_dirs()
stats.sort_stats("cumulative")
print("TOP 40 FUNCTIONS BY CUMULATIVE TIME:")
print("-" * 80)
stats.print_stats(40)
print("\n" + "=" * 80)
print("TOP 40 FUNCTIONS BY TOTAL TIME:")
print("-" * 80)
stats.sort_stats("time")
stats.print_stats(40)
print("\n" + "=" * 80)
print("TOP 40 CALLERS OF MOST EXPENSIVE FUNCTIONS:")
print("-" * 80)
stats.sort_stats("cumulative")
stats.print_callers(40)
print("\n" + "=" * 80)
print("PROFILING COMPLETE")
print("=" * 80)