Find what lags your server, with Claude
/bench profile samples your tick threads with Java Flight Recorder. Upload a profile, and Claude reads it with you: who runs, which methods, and how far two profiles can be compared. A walkthrough on two real profiles, and how to catch a lag spike after it happened.
What runs on your tick thread? The plugin samples it, Claude reads it with you
When TPS drops, the question is where the tick time goes: a plugin, the entities, chunk loading, the server itself? VoxelBench's /bench profile samples the tick threads with Java Flight Recorder, built into Java, and attributes every sample to a plugin, the server or Java. Once a profile is on your voxelbench.com account, Claude can read it with you, compare it with another one, and tell you how far the numbers can be trusted.
This walkthrough uses two real profiles of a faction server running 49 plugins on Canvas, a Folia fork, with Minecraft 26.1.2 and Java 25. Its private plugins are renamed here.
Before you start
- VoxelBench 2.0.0 or newer, on a server linked to your account (
/bench link). - Claude connected to VoxelBench with the
monitoring:readpermission: see Connect Claude to your Minecraft servers. - The permissions
voxelbench.profile, andvoxelbench.profile.uploadto send a profile. Operators have both by default. - Java Flight Recorder, which every HotSpot runtime from Java 16 to 25 includes.
/bench profile statussays when it is missing.
Step 1: capture a profile
A timed capture, while the server does what you want to look at:
/bench profile 60 upload
It samples the tick threads for 60 seconds (5 to 300), prints a summary in chat, saves the profile and sends it to your account. Start it before the load you want to see.
Aim for at least 300 samples on the tick threads: below that, the profile warns that its percentages are rough. Java Flight Recorder only samples a thread while it runs Java code, so a quiet server yields few samples, whatever the duration.
The lag you did not see coming. Turn on the ring buffer:
/bench profile ring on
It keeps the last minutes of samples. When the server lags (by default, 40 ticks in a row of 100 ms or more, or a single tick of a second), VoxelBench dumps and analyses the window from 10 seconds before the lag to 5 seconds after it, at most once every 5 minutes. The profile is saved with the trigger lag, and the server log names it. An automatic dump is never sent: upload it yourself with /bench profile list, then /bench profile upload <number>. To start the ring buffer with the server, set profiling.ring.enabled-on-start: true.
Scored runs are never profiled: during a benchmark, a stress limit or an auto-bench run, captures are refused and the ring buffer stops, then everything resumes 30 seconds after the run.
Step 2: ask Claude
What runs on my server's tick threads? Read my profile from 18:19 UTC.
Claude calls list_profiles, which lists the profiles of all your servers with their trigger and top plugin, then get_profile. Our first profile is a 300-second capture with 2,528 samples on the tick threads, and no warning.
| Owner | Self | Anywhere in the stack |
|---|---|---|
| server | 98.69 % | 100 % |
| VoxelBench | 0.32 % | 0.32 % |
| other (Canvas's own code) | 0.32 % | 0.36 % |
| TabPlugin (private) | 0.2 % | 0.2 % |
Self counts each sample for the innermost plugin on the stack, so the server code a plugin calls counts for that plugin. Anywhere in the stack counts it for every owner present. Here, no plugin reaches half a percent: the tick goes to the server itself. The busiest methods say where:
| Method | Anywhere in the stack |
|---|---|
RegionizedWorldData.forEachTickingEntity | 79.75 % |
LivingEntity.tick | 61.43 % |
LivingEntity.aiStep | 43.55 % |
Mob.serverAiStep | 13.01 % |
GoalSelector.tick | 12.42 % |
Entity.move | 10.25 % |
79.75 % of the tick samples are entities ticking, mostly mob AI and movement. The heaviest stacks end in zombies and creepers moving, glow squids, bats, and mobs checking whether to despawn. There is no plugin to remove: what would change this profile is how many mobs tick, and how often, which your server's spawn and entity settings decide.
Every figure is a share of tick samples, never a duration: the tools tell Claude not to turn them into milliseconds.
A second profile, and when not to conclude
At 20:13 UTC, the same server gave a second 300-second profile, very different:
| Owner | Self |
|---|---|
| server | 91.91 % |
| TabPlugin (private) | 5.15 % |
Where do TabPlugin's samples go?
Claude calls get_profile again, narrowed to that plugin. All of its samples follow the same path: the tab plugin's repeating task calls TabService.readTps, which calls CraftServer.getTPS. On Folia, the server answers by building its 15-minute tick report (getTickReport15m, then TickData.generateTickReport), which sorts its tick data with Arrays.sort. That work is the server's and Java's, but self counts it for the innermost plugin on the stack: the tab plugin that asked.
A real lead, then. But look at what Claude sees when asked:
Compare these two profiles.
compare_profiles starts with comparability, and here it says caution:
- few samples: 2,528 on the tick threads in the first profile, 136 in the second. The second capture ran while the server was nearly idle, and 5.15 % of 136 samples is seven samples;
- a different VoxelBench version, for information only.
TabPlugin's share rose by 5 points, against an estimated sampling noise of 3.8 points: beyond the noise, but the tool warns that this estimate underestimates the real noise, and that a share that rises is not a cause. Meanwhile the share of entities fell from 77.5 % to 31.6 %: less was happening on the server, so what remained stood out.
The honest answer is a lead, not a verdict. The path is certain: on this Folia server, each TPS read of the tab plugin sorts the server's tick data. How much it weighs on a busy server is not: capture a longer profile under load and see whether those reads still show. If they do, reading the TPS less often is a change for the plugin's author, not for your server's settings.
After a lag spike
With the ring buffer on, a lag leaves a lag profile behind. Upload it, then ask:
What was running during the lag at 21:04?
Claude looks for profiles with the trigger lag and reads the one that matches. A lag profile carries the only real duration of a profile: its longest tick, next to how many slow ticks were detected. If it holds no tick sample at all, the tick thread was not running Java code: waiting, sleeping, or starved of CPU by the host. The cause is then outside the JVM.
With monitoring on, the investigate_incident prompt goes further: given a server and a time, it correlates the metrics, events and alerts around that moment, without guessing.
Before and after a change
To check what a plugin update or a setting changed, capture two profiles in the same conditions (same load, same duration, enough samples), upload both, and ask Claude to compare them. Read the comparability first: not-comparable or caution limits what the numbers mean, and Claude has to say so.
What it costs
Profiles upload on every plan. Free keeps 5 profiles for 30 days, Pro 50 for 180 days, Enterprise and hosting provider accounts 200 for a year, and a short summary of each profile is kept a year on every plan: that summary is what Claude reads for a server's profile history.
This walkthrough takes five tool calls: one list, three reads and one comparison. Each is one API call from your account's daily quota: 10 on Free, 50 on Pro.
Going further
- Profiling: commands, the ring buffer, the automatic dump on lag, privacy.
- Profiles and Memory on the Website: the same profiles and comparisons in your dashboard, without Claude.
- Find what fills your server's memory, with Claude: the same approach for the heap.