
SCP:SL server owners have always had a hard time answering one question: what is actually lagging my server? MeowDebugger is a plugin that answers that question directly — it instruments every method on the server at runtime and tells you exactly what's taking time, how often it's called, and what effect it's having on your TPS.
It works on both EXILED and LabAPI, and can profile everything from your plugins down to the base game assembly.
The core idea is straightforward: every method you want to profile gets wrapped with a timer. When the method starts, a timestamp is recorded. When it ends — whether normally or via exception — the elapsed time is captured and stored.
The wrapping happens through HarmonyLib, which lets you inject code into methods at runtime without modifying the original source. MeowDebugger uses a Prefix and Finalizer patch on every method it targets:
private static void Prefix(MethodBase __originalMethod, out long __state)
{
MethodMetrics.Enter(__originalMethod);
__state = Stopwatch.GetTimestamp();
}
private static Exception Finalizer(MethodBase __originalMethod, long __state, Exception __exception)
{
long elapsed = Stopwatch.GetTimestamp() - __state;
MethodMetrics.Exit(__originalMethod, elapsed);
return __exception;
}
The Finalizer is used specifically because it runs even if the method throws — so you never miss a call, even from crashing code. Timing uses Stopwatch.GetTimestamp() rather than DateTime.Now, which gives high-resolution tick-level precision.
On startup, Patcher.cs collects every eligible assembly — the game's own Assembly-CSharp, all loaded plugins, and their dependencies — and iterates through every type and method to apply the Harmony patches. It's selective about what it patches to avoid crashes and redundant work:
<>c__DisplayClass state machine junkMoveNext is skipped — coroutine internals would produce meaningless data anywayThe config gives you full control over what gets profiled through two lists:
BlacklistAssemblies — entire DLLs to exclude (e.g. 0Harmony, CedModV3, system libs)WhitelistNamespaces — only patch types whose namespace matches (e.g. InventorySystem, CommandSystem)This means you can surgically target just the subsystem you suspect is misbehaving rather than profiling the entire server at once.
Every patched method reports into MethodMetrics, which keeps a thread-safe ConcurrentDictionary<MethodBase, Stats> of running statistics. Each Stats object tracks:
That last point is the key differentiator. By recording TPS at method entry and exit, MeowDebugger can tell you not just that a method was slow in absolute time, but whether it caused the server tick rate to drop while it ran. A method that takes 2ms but tanks TPS every time it runs is much more dangerous than one that takes 5ms on a thread that doesn't block the main loop.
The Stats class uses Interlocked operations for the count and total, with a small lock only for min/max updates, keeping the overhead minimal even under heavy call volume.
Every method in a report gets a Danger score from 1–10 that is color-coded green through red. It's calculated by comparing a method's total accumulated time to the worst offender in the current window:
danger = ceil( (methodTotalTicks / maxTotalTicks) * 10 )
So a score of 10 means this method used the most time out of everything profiled. The score renders with an inline color tag in the Remote Admin output, making it fast to visually scan a report without having to parse raw millisecond values.
All interaction goes through the reporter command, accessible from both Remote Admin and the server console:
| Command | What It Does |
|---|---|
reporter |
Prints the top 10 slowest methods from the current window, then resets |
reporter [N] |
Same but returns the top N methods |
reporter [method name] |
Reports only that method and its inner callees |
reporter flame |
Exports a flame graph .txt file |
reporter enable / disable |
Toggles metric collection on/off live |
The reporter flame command exports a speedscope-compatible flame graph — a semicolon-delimited call stack file where each line represents a unique call path and its exclusive CPU time in microseconds. You load it into speedscope to get a full interactive visualization of where your server is spending its time.
There's also a timed profiler command (RunForXSecondsProfiler) that automatically enables collection, waits a set number of seconds, and then dumps a report — useful for capturing a snapshot during a specific activity without babysitting the console.
Beyond the in-game text report, MeowDebugger can export data in a few formats:
.txt) — speedscope-compatible, shows full call stacks with exclusive time per pathDrop the .dll into the right folder for your framework:
LabAPI:
%AppData%\SCP Secret Laboratory\LabAPI\plugins\global~/.config/SCP Secret Laboratory/LabAPI/plugins/globalEXILED:
%AppData%\EXILED\Plugins~/.config/EXILED/PluginsGrab the latest build from the releases page. The EXILED build is labeled with -EXILED in the filename.