97 lines
2.5 KiB
Python
Executable file
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)
|