Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
ValPerfMonitor
Testing tool: measures the CPU time and memory each mod and game system costs, with a test panel (F11), CSV logs and HTML reports.
| Date uploaded | 2 weeks ago |
| Version | 1.4.0 |
| Download link | BarryWhite-ValPerfMonitor-1.4.0.zip |
| Downloads | 84 |
| Dependency string | BarryWhite-ValPerfMonitor-1.4.0 |
This mod requires the following mods to function
denikson-BepInExPack_Valheim
BepInEx pack for Valheim. Preconfigured with the correct entry point for mods and preferred defaults for the community.
Preferred version: 5.4.2351README
ValPerfMonitor
A testing tool. It measures what each installed mod and each part of Valheim itself costs, and takes its own cost out of the results. Install it in a test profile, measure, then remove it.
It answers questions like: which mod costs the most frame time? Which one makes garbage collection stutter? What was that freeze? How long does each mod add to startup? Is something leaking memory?
Requirements
- Valheim 1.0 (Unity 6, Mono) with BepInExPack_Valheim 5.4.23 or newer.
- Client side only. It measures whatever mods are installed; it needs none of them.
Recommended: ValAgentBridge
The monitor works on its own, and ValAgentBridge works without it, but they go well together. ValAgentBridge lets an AI agent (or a script) drive the running game: start it, load a world, move the player, pin the weather. With both installed, the agent can run the monitor's tests, read the results and repeat them the same way every time, with no one at the keyboard. See Remote control for the commands the monitor adds, and examples/ for a test script to start from.
Installation
The release zip has two parts, and both are needed:
plugins/ValPerfMonitor.dll: the monitor. It shows up twice in the BepInEx log:ValPerfMonitorandValPerfMonitor early start, a small part that starts before every other mod so that errors while mods load are counted, and the startup split can time them when it is on.patchers/ValPerfMonitor.Preloader.dll: a tiny helper that starts before any mod loads, so each mod's startup time can be measured, the previous session's log can be saved before BepInEx empties it, and a session that didn't close normally is noticed. It patches nothing.
Gale / r2modman: Import → local mod and pick the zip. Manual: copy ValPerfMonitor.dll to BepInEx/plugins/ValPerfMonitor/ and ValPerfMonitor.Preloader.dll to BepInEx/patchers/ValPerfMonitor/.
Use a separate test profile. The monitor attaches thousands of small hooks while it measures; it is not meant to stay installed for normal play.
Quick start
- Launch, load a world, go to the spot you want to test. A few seconds after you load in, the monitor attaches its hooks: the game stutters for about 10 s (those frames are never counted).
- Press F11, type a label such as
all mods, choose Full suite, press Start, and stand still for about 4 minutes. - Change one thing (remove a mod, change a setting), go to the same spot, run the suite again with a new label.
- Open
BepInEx/ValPerfMonitor/compare.htmland pick the two runs.
For before/after comparisons, set the game's FPS limit to unlimited and vsync off: a frame cap hides CPU savings.
Capture modes
Each capture records in one mode. Every capture gets its own folder and report.
| Mode | What it records | Hooks attached |
|---|---|---|
| Baseline | True FPS, frame-time percentiles, garbage collections (GC), render counters, world context | none |
| Timing | CPU self-time per mod method and per game system (about 200 vanilla update loops) | all |
| Allocations | Memory each mod and game system allocates per frame (what drives GC stutter) | all |
| Trace | Timing, plus a full flame graph of every slow frame (open at speedscope.app) | all |
The modes are kept separate on purpose. Allocation counting and trace recording each add their own overhead, so mixing them would blur the timing numbers. The full suite (Shift+F11) runs all four back to back at the same spot, 60 s of counted time each.
Test panel (F11)
F11 opens the test panel. While it is open the mouse is free, the game ignores movement, camera and attack input (as when typing on a sign), and no frames are counted. Press F11 again or ✕ to close it.
- Run a test: choose Full suite, Single test (one mode for a set time), Continuous recording (until you press Stop), Most expensive objects or Memory census (both below), pick the mode, type a label (e.g.
all mods,without Seasons), set the duration, and press Start. The panel closes and the test runs. A timed test saves its results and returns to idle when it finishes. Tests need a loaded world, and a test stops (keeping what it recorded) if you log out or die. - Live view: the graph and the ranked table of mods and game systems. Click a row to see its heaviest methods.
- Results: captures finished this session (FPS, 1% low, heaviest mod) with a button for each report, plus Open comparison page and Open logs folder.
- Load times & memory: startup time (and each mod's share), every world load and logout this session, and the memory leak check.
- A Stop button in the panel header ends whatever is running and saves it.
- Optional live HUD: a small readout of the top mods while you play (toggle in the panel).
On launch it starts a continuous Timing recording by default (config Record on launch). Turn that off if you want it to wait idle until you start a test. While idle (nothing recording) the monitor costs next to nothing: its probes stay attached but do no work, and the once-a-minute checks pause. Only the leak check still runs every 5 minutes in a world (a short stutter; Leak check every (seconds) when not recording, 0 = off), plus the always-on warnings below, which cost almost nothing.
Hotkeys still work without the panel: Ctrl+F11 starts a new capture in the same mode, and Shift+F11 starts or stops the full suite.
Also recorded
- Load times. Launch to a settled main menu, split into steps, plus each mod's own loading time (needs
ValPerfMonitor.Preloader.dllinBepInEx\patchers, included). Every world load: until you are in the world, until the loading screen is gone, and until the game stops hitching (3 s without a frame over 100 ms). Every logout. - Loading steps. Startup, world load and logout split by the game's own big steps (item database, prefab registry, world file, locations, player spawn...), each including the mod patches on it, and the slow startup and world-load steps are split into each mod's own patches on them and the game's own code (
load_steps.csv,load_step_mods.csv). - A slow mod's startup, split into its own methods. Name mods in the config (
6 - Startup split: mod names or GUIDs, or "the N slowest mods from the last launch") and on the next launch every method of their own code is timed while they start up: total time (with what it calls) and own time. Results in the log,startup_methods.csvandperf/load_times; the timers are removed once the main menu has settled. Bundled helper libraries (ItemManager, ServerSync...) are not timed; their time shows in the mod method that calls them. - Coroutines. Mods' step-by-step background routines are timed like their other code (hook type
Coroutine). - Network messages. Every RPC by name, with the mod that handles it, how often and how many KB, plus the connection's total KB/s. In single player nothing crosses a network, but the messages still cost CPU.
- Dedicated server connection (on a server, whether or not you record). Once a second: network ping, ping through the game and how long the server took to answer it (its own frame time shows here), connection quality, upload and download KB/s, upload backlog (the game holds back world updates once 10 KB are waiting), world objects sent and received, and every 5 s how many of the objects and creatures around you your PC runs. Server hitches: no answer for over a second, marked "server busy" when the connection stayed good (the server itself froze: a world save, a mod's work) or "connection trouble". What fills your upload: every world object your PC sends, per type of object (sent per second, KB/s, changes per second), and the game or mod code that changed them, from a few call-stack samples a second (
upload.csv, the visit summary). A summary per visit in the log andserver_visits.csv; the live overlay (Full) and the F11 Live tab show a line on it;perf/connection. - Joining a server, step by step: connecting, handshake, password, accepted, first world data, in the world, settled, plus everything the server sent meanwhile per message and mod, including each mod's config sync (
join_times.csv, the log). Its hooks come off once you are in the world. - Server mode. On a dedicated server the monitor only logs, once a second: frame rate, worst frame, players, world objects, traffic and the worst player's ping (
server.csv), each player's ping, quality, traffic, backlog and position every 5 s (server_players.csv), every frame over 100 ms and whether a world save caused it (server_hitches.csv), world saves, and network messages per mod (server_network.csv). No mod timing, no panel, no tests. Every 10 minutes the server log gets a summary with the time the monitor used (7 - Dedicated serversettings). Files inBepInEx\ValPerfMonitor\server_<date>\on the server. - Memory leak check. Once a minute while recording, and every 5 minutes in a world while not recording (never during tests): game objects, textures, meshes, materials, sounds, scripts per mod, and memory. A leak is a count that keeps rising while the number of loaded objects stays about the same; when game objects or materials do that, a "Possible leak" warning appears on screen and in the log.
- Log floods. When one message is written more than 20 times in one second, a warning names the mod and the message. The main-thread time spent writing log lines is in
frames.csvand each capture's summary. - Pathfinding links. Navigation tiles and the links between them every 10 s (
pathfinding.csv), with a warning at 75% of Unity's limit of about 65,535 links. - Events. Teleports and portals, entering and leaving a world, season changes (Seasonality) and ValAgentBridge commands (
events.csv,frames.csv), named on slow frames shortly after them. Frames in which ValAgentBridge ran a command are left out of captures. - First-open stutter. Every opening of the inventory, the map, the build menu, a crafting station or a container is timed, so the first open of the session (often a one-off load of icons and lists) can be compared with later ones (
first_opens.csv,perf/first_opens). - Past session logs. BepInEx empties its log at every launch; the monitor first saves the previous one (crashed sessions too), so earlier sessions can still be read.
- Crashes. If a session didn't close normally, the next launch says so (log, and a note at the main menu): whether it crashed (with Unity's crash report and the code that was running, including the first mod in it) or was ended otherwise (froze and was closed, Task Manager, PC shut down), plus the last errors. Only a real crash names a mod, and that is where it happened, not always the cause. Kept in
last_crash.txt. - Errors per mod. Errors and warnings in the log, per mod: a mod's own messages by its name, game and Unity errors by the first mod in their stack trace. Errors per minute and the worst mod are in every report and in
compare.html. A mod throwing an error every frame costs CPU. - Most expensive objects (Run tab, about half a minute): each object's per-frame code is timed per object for 20 s, then objects and prefabs are ranked, e.g. all loaded torches together, or the one creature that costs the most. Times include the mod patches on that code; the game's central update loop shows as one row, as its own work can't be split per object. The normal recording pauses while it runs and is not saved as a run (its timers add a little to every call).
- Memory census (Run tab, a few seconds of stutter): counts every object kept in memory, grouped by mod, by static field or component type and by object type, plus Unity's game objects by scene and root object and materials by name, splits Harmony's copies of patched methods between the mods patching them, and times a full garbage collection. Each census is compared with the previous one this session, so a mod whose objects keep growing stands out. It ends a running recording first (saved), as its forced garbage collections would change that recording's numbers.
- Likely cause of every slow frame: garbage collection, world save, teleport, objects loading, a recent event (season change, entering a world, a ValAgentBridge command), one mod or game system, or engine/rendering.
Output: BepInEx\ValPerfMonitor\
compare.html: every capture from every session (A/B test turns are left out: their result is inab_tests.csv). Pick runs A and B to see FPS / 1% low changes, which mods were added or removed between them (and a warning when the game version differs), and per-mod changes. Start here after testing.runs.csv: the same data for Excel.session_logs\: a copy of each earlier session's BepInEx log (LogOutput.log), named by when that session ended, kept 14 days (Keep past session logs)._crashedor_not-closedin the name: that session didn't close normally.last_crash.txt: the report on the last one that didn't.<session>\log_errors.csv: every distinct error and warning this session, with its mod and count.<session>\object_costs.csv: the most expensive objects and prefabs.<session>\load_steps.csv: the loading steps per startup, world load and logout.startup_times.csv,world_load_times.csv,logout_times.csv: one row per launch / load / logout, across sessions.join_times.csv,server_visits.csv: one row per server join / visit.<session>\connection.csv: the server connection, one row per second.<session>\server_hitches.csv: every time the server did not answer for over a second, with the likely cause.<session>\upload.csv: what filled the upload, per type of object, with the code that changed it.<session>\join_steps_<time>.csv,join_traffic_<time>.csv: one join.<session>\startup_mods.csv: each mod's loading time.<session>\startup_methods.csv: the startup split, when it is on.<session>\memory.csv(withlive_after_gc_mb, mod and game memory at its lowest point after a garbage collection, andlocation_objects, game objects in locations and dungeon rooms, which exploring loads and the leak check leaves out),memory_by_mod.csv: the leak check.<session>\memory_census_<time>.txt: each memory census.<session>\events.csv: teleports, logins and logouts, season changes and ValAgentBridge commands.<session>\pathfinding.csv: navigation tiles and links every 10 s.<session>\first_opens.csv: every opening of the inventory, map, build menu, crafting stations and containers, with its frame time.<session>\<NN_Mode_label>\, one folder per capture:report.html: charts and sortable tables.frames.csv: per 10 s: FPS, p50/p95/p99/max frame time, monitor cost, GC, heap, draw calls, triangles, CPU/GPU time (when the game exposes it), biome, object count, nearby building pieces, weather, log lines written and the time spent writing them, and the events in those 10 s.timeline.csv: per 10 s, per mod or game system.summary_owners.csv/summary_methods.csv: ranked totals for the capture.spikes.csv: every frame over 50 ms with its likely cause, top 3 owners and a GC flag.network.csv(per 10 s) andsummary_network.csv: network messages.traces\*.speedscope.json(Trace mode only): drag the file onto https://www.speedscope.app.
How false readings are kept out
- Self time with overhead removed. Each method is charged only for its own work. The calibrated cost of the probes on anything it calls is subtracted, so a mod is never billed for another mod's or the monitor's work.
- Frame time is corrected. The monitor times its own main-thread work (overlay, rollups, logging) and subtracts it and the probe overhead from every frame. Reported FPS is the estimate without the monitor.
- Contaminated frames are excluded, not corrected. Frames are left out when the monitor attached hooks, wrote reports, used more than 1 ms itself, or was warming up (5 s after hooking while the game compiles the rebuilt methods, 2 s after a mode switch).
- Test tooling is left out. Frames in which ValAgentBridge ran a command (read from the bridge's own record with ValAgentBridge 1.8.0 and later) are not counted; each capture's summary lists them per command. The exception is an A/B test's own action script, whose frames are what it measures.
- GC is handled conservatively. A garbage collection in a frame where the monitor was busy (or its writer thread was building a file) is excluded. All disk writing happens on a background thread.
- Baseline cross-check. Compare the Baseline FPS with the Timing FPS from the same suite. A small gap means the correction worked.
- Self-checks at startup. The log reports each probe's measured cost, which allocation counter was found (and whether it is exact), and a warning if the probe itself allocates memory on this runtime.
Not measured
- Transpiler edits: they change game code in place, so they are counted but not timed.
- Background threads.
- GPU cost: no per-mod GPU number is possible, but render counters and GPU time (when available) show the graphics side.
If the game crashes while it hooks
Hooking a method makes the game compile it again, and on Unity's Mono runtime a few methods abort the game when that happens. The monitor writes the method it is about to hook to BepInEx/ValPerfMonitor/hooking-in-progress.txt first. After a crash, the next launch adds that method to skip-methods.txt and never hooks it again, so each bad method can crash the game at most once. Leave both files alone; delete a line from skip-methods.txt to retry that method.
Remote control (ValAgentBridge)
With ValAgentBridge installed and the game started through it, an agent can run tests: perf/status, perf/start_suite (label and seconds per mode, for that suite only), perf/stop, perf/results, perf/load_times, perf/split_startup (which mods to split on the next launch), perf/patches_on, perf/alloc_check, perf/slow_objects, perf/objects (what is loaded right now, by prefab), perf/worldgen_cost (how much a world-generation mod such as Riverheim adds to the alternative biome check during world load), perf/object_costs / perf/object_costs_result (the most expensive objects and prefabs), perf/log_errors (errors per mod this session, log floods, time spent writing the log), perf/memory_census / perf/memory_census_result (which mods, static fields and component types keep the most live objects and MB, how much of Harmony's patch memory each mod causes, what grew since the previous census, and how long a full garbage collection takes; also in F11 > Run), perf/start_capture / perf/stop_capture (a labelled recording in one call, with its result), perf/alarms (pathfinding links, the latest memory sample, log floods and recent events), perf/relog_test / perf/relog_result (log out and in a few times, 3 by default: errors that start only after a relog, and what grows every cycle; examples/relog-test.json), perf/first_opens (first against later opens of the inventory, map, build menu and crafting stations), perf/session_logs (past sessions: how each ended, their errors, search, last lines), and A/B tests with perf/ab_test / perf/ab_result (take a mod's patches off, or switch an object or every copy of a prefab off, and put it back in turns, to see whether it really costs frame time; rounds where the weather changed, the player moved or objects loaded are left out, and extra rounds are added when the answer is unclear; with script, a ValAgentBridge test script runs over and over in every turn, so the cost is measured while opening windows or building instead of standing still; examples/ab-actions-example.json). perf/alloc_check and perf/worldgen_cost refuse to run during a test, and the frames of the heavier tools are never counted. Without ValAgentBridge these do nothing. examples/ab-test-example.json is a test script to start from.
Configuration
BepInEx/config/valheimmods.ValPerfMonitor.cfg, created on first launch. Everything has a working default; the most useful settings:
Record on launch: start a continuous Timing recording when a world loads (on by default).Live overlay: Off / Compact / Full readout while playing (also Alt+F11).Seconds per mode: length of each step of the full suite.Excluded mod GUIDs: mods to leave unmeasured.Measure mod coroutines,Track network messages,Memory check every (seconds): switch the extra recordings off.Leak check every (seconds) when not recording(300): the leak check while nothing records; 0 = only while recording.Log flood: lines per second(20): when one message is written more often than this, a warning names the mod.Keep past session logs(on by default) andDays to keep past session logs(14): on each launch, save the previous session'sLogOutput.loginBepInEx/ValPerfMonitor/session_logs, named by when it ended, so earlier sessions (crashed ones too) can still be read. Copies older than the limit are deleted. Turn it off to keep only BepInEx's usual single log.Split startup of these mods,Split startup of the slowest mods: the startup split for the next launch (off by default).Fast hooking budget ms per frame(40) andHooking budget ms per frame(8): how much time per frame goes into attaching hooks, when they all go on at once (entering a world, tests) and for small top-ups while playing. Higher = done sooner, with a heavier stutter.
Build from source
Needs the .NET SDK (any recent version), Valheim, and a BepInEx profile (the DLLs are referenced from the game and the profile, never copied into this repository).
dotnet build -c Release
dotnet build -c Release -p:Deploy=true
python package.py
- The first builds both DLLs (
bin/Release/net472/andpreloader/bin/Release/net472/). Deploy=truealso copies them into the profile'spluginsandpatchersfolders.package.py(Python 3) makes the Thunderstore-format zip indist/.
Paths default to a Steam install and Gale's Default profile. Override them on the command line:
dotnet build -c Release -p:ValheimDir="D:\Games\Valheim" -p:GaleProfile="C:\path\to\profile"
GaleProfile is the folder that contains BepInEx. The project compiles against the real game DLLs (not publicized ones), so reaching a private game member is a compile error and goes through AccessTools instead.
Source layout
| Folder | What |
|---|---|
src/ |
The plugin: Plugin.cs (frame loop, captures, suite), Probe.cs (the per-call hook), Instrumenter.cs (finds and hooks methods), SafetyGuard.cs (crash protection while hooking), Stats.cs, Capture.cs, Report.cs (CSV and HTML), Overlay.cs (F11 panel and live HUD), LoadTimes.cs, LoadSteps.cs and LoadPatches.cs (loading split by step and by mod), StartupDetail.cs (startup split and the early-start plugin), SpawnParts.cs and ObjectLoads.cs (slow spawns, loaded objects), NetworkStats.cs, MemoryTracker.cs, AbTest.cs, AllocCheck.cs, WorldGenCost.cs, ObjectCosts.cs (most expensive objects), LogWatch.cs (errors per mod, log floods), Alarms.cs (events, leak and pathfinding warnings), UnityCensus.cs (Unity objects in the memory census), RelogTest.cs, FirstOpens.cs (first-open stutter), BridgeScript.cs (runs a ValAgentBridge script for the A/B test), SessionCheck.cs (crash report at launch), BridgeCommands.cs (the perf/* commands) |
preloader/ |
The startup-timing helper and the past-session logs and crash check (a BepInEx preloader patcher) |
License
MIT, see LICENSE.
CHANGELOG
Changelog
1.6.1
- Server: the log says how the network message name lookup went (and why, when it fails).
- Server: messages the server only sends get the mod they belong to.
- Server: server_network_summary.csv is rewritten every minute.
- Server: slow frames when a player joins or leaves are marked (server_hitches.csv and the summaries).
- Server: the last summary before a shutdown gives seconds instead of "0 min".
- Full play recording: no longer stuck on "waiting for the world" when started in the first seconds in a world.
- Full play recording: shorter blocks by default (Timing 5 min, Allocations 2, Trace 2), so each activity gets all three.
1.6.0
- Full play recording (F11 -> Run, off by default): Timing, Allocations and Trace take turns while you play, with memory censuses and one combined summary (play_summary.txt).
- Server: startup freezes no longer blamed on garbage collection.
- Server: the hourly asset clean-up counts as the cause when its freeze lands on the frame that asked for it.
- Server: worst ping leaves out players still joining (in server.csv too); server_players.csv marks them.
- Server: always-empty Steam columns removed from server_players.csv.
- Network messages that only had a number get their name from the mods' code.
- Server: installs or updates its patcher in BepInEx/patchers by itself (works from the next restart).
- Each session's log is also saved in that session's own folder (server_<date> or the game's session folder), next to its CSVs.
1.5.1
- Server: frame times come from the monitor's own clock. Unity's put the slow startup frames on the wrong row.
1.5.0
- The game's asset clean-up (after world load, every 20-60 min, hourly on servers) is timed and named on slow frames, in the game and on servers.
- Materials only count as a possible leak if they are still there after an asset clean-up.
- Harmony's copies of methods are freed once the monitor's probes come off, so later tests have no extra garbage collection cost.
- Memory census names the mod or menu new UI objects belong to.
- Server: slow frames say whether a garbage collection or the asset clean-up ran in them; summaries count slow frames by cause.
- Server: optional memory census once per run while nobody is on (off by default).
- The "Hooking took" line is no longer skipped when the last probe used up a frame's time.
1.4.1
- Server summary's worst ping leaves out each player's first 30 s (joining).
- Server summary's busiest network messages count only that summary's minutes, not the whole session.
- Server startup is reported once on its own and left out of the summaries.
- Server startup split by mod: loading steps and each slow startup frame, with every mod's patch on them (
server_startup_*.csv).
1.4.0
- Dedicated server connection. Ping, how long the server takes to answer, connection quality, traffic, upload backlog and who runs the objects around you, once a second (
connection.csv,perf/connection, overlay line). - What fills your upload. World objects your PC sends to the server, per type of object, with the game or mod code that changes them (
upload.csv). - Server hitches. Every time the server stops answering for over a second, marked server busy or connection trouble (
server_hitches.csv). - Join timing. Each step of joining a server and what the server sent meanwhile, config sync per mod (
join_times.csv). - Server mode. Installed on a dedicated server, it only logs frame rate, slow frames, world saves, players' connections and network messages per mod; no mod timing.
- Received network messages show their names (ConfigSync, ZDOData...) instead of numbers where known.
- No more HarmonyX warnings every 15 s in profiles without ValAgentBridge.
1.3.3
- Season marker sees every season change. With Seasonality 3.8.5 or later it reads the season in play, so changes from the
seasonality setcommand or a server sync are marked too, not only changes to the config setting. - No false "Possible leak" when exploring. The leak check now only compares moments at the same spot (within 100 m), leaves out game objects in locations and dungeon rooms (which exploring loads), and gives materials that grow while exploring a note instead of a warning.
1.3.2
- Log flood warning counts only lines that reach the log file. Levels BepInEx drops (Debug in the default
LogLevelsofBepInEx.cfg) no longer trigger it, and the warning names the real level (info, message, warning, error).
1.3.1
- Fix: possible hitch when working out which mod an error belongs to. Every line under an error or warning was looked up as if it were a line of a stack trace, including plain text (such as the monitor's own report on the last session). Each such line made the lookup search every type of every loaded mod, 0.1-0.3 s on the main thread the first time it was seen, and logged "AccessTools.TypeByName: Could not find type named ...". Now only real stack-trace lines are looked up, with a quick lookup in each loaded assembly, and the monitor's own messages are filed under the monitor.
1.3.0
- Log flood warning: when one message is written more than 20 times in one second (setting "Log flood: lines per second"), a warning appears in the log (and on screen once you are in a world), naming the mod and the message. Before, a mod repeating an error every frame (for example 55,000 "Failed to allocate NavMeshLink" errors in one session) was only noticed afterwards in an 11 MB log. Floods are listed in
perf/log_errors. - Time spent writing the log: the main-thread time BepInEx spends writing log lines is measured: per 10 s in
frames.csv(log_lines,log_write_ms), per capture in the log, and per minute inperf/log_errors. Unity's own cost of building an error comes on top, so this is a lower bound. - Leak warning, also during normal play: game objects, materials and mod/game memory (its lowest point after a garbage collection, also in
memory.csvaslive_after_gc_mb) are compared with an earlier moment that had about as many loaded world objects; when they grew a lot while the world around you did not, a warning appears ("Possible leak"). The memory check now also runs every 5 minutes while nothing is recording (setting "Leak check every (seconds) when not recording"; 0 = only while recording), with a short stutter each time. - Memory census lists Unity's own objects: game objects by scene and by root object, and materials by name (with how many are runtime copies), compared with the previous census. That shows which objects or materials pile up.
- Pathfinding links: every 10 s in a world, the navigation tiles, the links between them and the links waiting to be removed go to
pathfinding.csv. A warning appears at 75% of Unity's limit of about 65,535 links; at the limit the game logs "Failed to allocate NavMeshLink" every frame. - Events: teleports and portals, entering and leaving a world, season changes (Seasonality) and ValAgentBridge commands are kept in
events.csv, listed per 10 s inframes.csv, and named on slow frames up to 3 s after them ("after season change to Winter (0.4 s)"). - Test tooling left out: frames in which ValAgentBridge ran a command are left out of captures, like the monitor's own work (read from the bridge's own record with ValAgentBridge 1.8.0 and later). Each capture's log summary lists them per command with the slowest frame.
- Labelled recording in one call:
perf/start_capture(label,mode) andperf/stop_capture, which returns the result (FPS, 1% low, heaviest mod, report). perf/alarms(new): pathfinding counts, the latest memory sample, log floods and recent events.- Relog test (
perf/relog_test/perf/relog_result, scriptexamples/relog-test.json): log out and back in a few times (relogs, default 3; then it stops, so later logins are left alone); after each login the monitor records a cycle (error counts, game objects, materials and a memory census). The result lists errors that first appeared after a relog, with how many in each cycle, and static fields, component types and Unity objects that grew in every cycle: the signs of a mod keeping old data across logouts. - A/B test while doing something:
perf/ab_testtakesscript, a ValAgentBridge test script run over and over in every turn (open the inventory and the map, place pieces...), instead of only standing still. Frames of that script are counted;examples/ab-actions-example.jsonis a start. - Messages the game stamps with the time are counted as one message, so repeats group together in
log_errors.csvand the flood warning. - First-open stutter (
perf/first_opens,first_opens.csv): every opening of the inventory, the map, the build menu, a crafting station or a container is timed (the frame it opened in and the two after, and the opening code itself), so the first open of the session can be compared with later ones, and with the first open after a relog. A slow first open (50 ms or more) is noted in the log.
1.2.0
- Memory census (new in the F11 Run tab, and
perf/memory_census/perf/memory_census_result): counts every object kept in memory (reachable from static fields and from all components and ScriptableObjects) and groups them by mod, by root (one static field, or all components of one type) and by object type, with approximate MB. Then it times a full garbage collection, whose length grows with the number of live objects. Results go to the log in plain English and tomemory_census_<time>.txt, which has column headers and a summary on top. Static fields are read straight from memory, so no mod's setup code is ever run early (a self-test checks this on every run). The work is spread over a few seconds of frames, which are left out of captures; a running recording is ended (and saved) first. The monitor's own memory is shown on a separate line.- What grew (leak finder): each census is compared with the previous one this session, by owner and by root, so a mod whose objects keep growing while you stand in one place stands out.
- Harmony's share by mod: Harmony's copies of patched methods (the biggest single holder in a modded game) are split over the mods patching each method, with the monitor's own probes on their own line.
- Cheaper when idle: when a recording or test ends, the probes and the network counting stop doing any work. Before, they kept costing about 0.3–1 ms per frame until the game closed. The once-a-minute object scan, memory check (
memory.csv) and 10-second world sample now only run while something is recording, so idle play no longer stutters once a minute. - Tests stay clean:
perf/alloc_checkandperf/worldgen_costrefuse to run during a suite, timed test, A/B test, object costs or memory census. The frames of those tools and ofperf/objects,perf/slow_objectsandperf/session_logsare never counted.- Splitting slow spawns into parts no longer adds timers in the middle of a test or a Baseline capture.
- A test stops (keeping what it recorded) when you log out or die, instead of carrying on at the main menu.
- The full suite (Shift+F11 or the panel) needs a loaded world, like the other tests; before, it could start its Baseline step at the main menu.
perf/worldgen_costis fairer: the monitor's own probes on the mod's world-generator patches come off while it times them. Before, they made the mod look slower than it is.- A/B test turns stay out of the run history: each turn still gets its own folder and report, but no longer adds rows to
runs.csvandcompare.html; the result is inab_tests.csvas before. perf/start_suiteno longer saves its label and seconds as your "Run label" and "Seconds per mode" settings; they apply to that suite only.- Removed the unused "Compact" setting under "5 - Overlay" (the live overlay's own Compact mode is unchanged).
1.1.0
- Most expensive objects (new in the F11 Run tab, and
perf/object_costs/perf/object_costs_result): for 20 s, each object's per-frame code (the game's update loops with the mod patches on them, and mods' own Update) is timed per object, then objects and prefabs are ranked (all loaded torches together, the single worst creature...). Results in the log andobject_costs.csv, with an instanceId for ValAgentBridge's debug tools. The normal recording pauses while it runs (and is not saved as a run, since the timers add a little to every call); its timers come off afterwards. - A/B test for objects:
perf/ab_testcan now switch an object off and on in turns (objectId,objectPath) or every loaded copy of a prefab (prefab), to see whether it really costs frame time. Refuses the player, the game's core objects and anything holding mods' plugins. - Errors per mod: errors and warnings in the BepInEx log are counted per mod (a mod's own log name, or for game and Unity errors the first mod in the stack trace). Each capture's report and
compare.htmlshow errors per minute and the mod with the most;log_errors.csvlists every distinct message;perf/log_errorsfor the bridge. A mod throwing an error every frame costs CPU. - Crash report at launch: if the last session didn't close normally, the next launch says so in the log and on screen at the main menu, with Unity's crash report folder when it crashed, the code that was running (and the first mod in it; only a real crash names a mod), and the last errors. Saved in
last_crash.txt; the saved session log is named_crashedor_not-closed.perf/session_logslists past sessions (how each ended, errors per log name), searches them and reads their last lines.
1.0.5
- Past session logs are kept (new setting "Keep past session logs" under "3 - Logging", on by default): BepInEx empties
LogOutput.logevery time the game starts, so only the last session's log is ever there. Now each launch first saves the previous session's log inBepInEx/ValPerfMonitor/session_logs, named by when that session ended, including sessions that ended in a crash. Copies older than "Days to keep past session logs" (14) are deleted. Done by the preloader, before any mod loads; nothing runs while you play. Switch it off to go back to BepInEx's single log.
1.0.4
- Minor README fixes: the companion bridge's current name and link (ValAgentBridge), the hooking and startup split settings, and the full source file list. No changes to how the mod works.
1.0.3
- Faster hooking on entering a world: attaching all probes at once (a few seconds after you load in) now uses the fast budget too, so it takes about 9-10 s instead of 14-17 s. The game stutters briefly while it runs; small top-ups while playing still use the gentle 8 ms per frame. The setting is now called "Fast hooking budget ms per frame".
1.0.2
- Startup split for slow mods: name mods in the config (or pick "the N slowest from the last launch") and on the next launch their startup is broken down into their own methods, so you can see what inside a mod makes it slow to load. Results in the log,
startup_methods.csvandperf/load_times;perf/split_startupsets it from ValBridge. Off by default. - Faster hooking in tests: while a test suite or A/B test runs, probes are attached with up to 40 ms per frame instead of 8 (new setting "Hooking budget ms per frame during tests"). The game stutters until hooking is done; those frames were never counted anyway.
- No repeated rebuilds: game methods rebuilt the first time probes went on are not rebuilt again when the probes come back after a Baseline step. Later suites hook noticeably faster.
- The log now says how long hooking took and where the time went.
- Build files default to Gale's
Defaultprofile.
1.0.1
- No changes to the mod. Full changelog and source: https://github.com/skatefast29/ValPerfMonitor/blob/main/CHANGELOG.md
1.0.0
First public release.
- Per-mod cost while playing: CPU time and garbage (memory allocations) per mod and per game system, in four capture modes (Baseline, Timing, Allocations, Trace), with an F11 test panel, a live overlay, CSV logs, an HTML report per capture and a page comparing runs.
- Slow frames explained: each slow frame is tagged with its likely cause (garbage collection, world save, teleport, objects loading, one mod or system, engine/rendering); Trace mode records flame graphs.
- Loading: startup, world loads and logouts timed and split by the game's own steps, and each step split again into every mod's patches on it and the game's own code. Each mod's own loading time at startup (with the preloader).
- Objects: which objects are slow to spawn (and which script or mod patch on them), and what is loaded right now by prefab, including copies stacked on top of each other.
- Network and memory: every network message by name with the mod that handles it; object counts and memory once a minute to spot leaks.
- Remote control with ValBridge (optional): status, full suites, results, load times, which mods patch a method, an exact garbage check for one method, an A/B test that takes a mod's patches off and puts them back without a restart (rounds where conditions changed are left out, extra rounds added when unsure), and the cost a world-generation mod adds to world loading.
- Every run is stamped with the game version and a fingerprint of the mod list.
- Crash-safe hooking: a method that crashes the game while being hooked is remembered and skipped from then on.