#!/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)