Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SPM

A Paper plugin that watches tick health, world load, memory and CPU, works out which plugin is actually burning the tick, and pushes alerts to Discord or any webhook when things go bad.

Built for Minecraft 26.2, Java 25.

What it does

  • Tick monitoring: TPS across 5s/1m/5m/15m windows, MSPT average, p95 and peak, plus a distribution histogram and a log of individual stalls with timestamps.
  • Per-world stats: entities, living entities, loaded chunks, players and scan cost, per world.
  • Entity and chunk hotspots: counts by type, and the chunks with the worst entity pile-ups (click a hotspot to get a teleport command).
  • Plugin attribution: a background stack sampler tells you what share of the main thread each plugin owns, down to individual methods. Optional event-handler timing on top of that.
  • Memory and CPU: heap, committed, native, per-pool usage, GC load as a percentage of wall clock, collection rate, process/system CPU, threads and JVM flags.
  • Automatic lag detection: a small rule engine with per-rule streak counts and cooldowns, so a single bad tick does not page you, and a long outage does not spam you.
  • Alerts: console, in-game, Discord embeds and generic JSON webhooks, with retry and a per-minute budget. Recovery notices when a rule clears.
  • Reports: a full text snapshot written to disk, automatically on any critical alert.

Requirements

  • Paper 26.2 or newer
  • Java 25

Build

mvn clean package

The jar lands in target/SPM-26.2.jar. Drop it in plugins/ and restart.

Commands

Root command is /spm, aliased to /perf and /lagcheck. Running it bare gives the overview.

Command What it shows
/spm everything on one screen
/spm tps tick rate windows and recent stalls
/spm mspt tick time breakdown and distribution
/spm world [name] per world load, top types, busiest chunks
/spm entities [world] entity counts by type
/spm chunks [world] loaded chunks, block entities, hotspots
/spm mem heap, pools, garbage collection
/spm cpu cpu, threads, jvm flags
/spm plugins main thread share per plugin
/spm timings <plugin> hot frames and event costs for one plugin
/spm profiler <on|off|reset|status> control the sampler
/spm profiler events <on|off> control event handler timing
/spm alerts [mute|unmute|test] alert history and routing
/spm report [print] write or print a full snapshot
/spm reload reload config and restart the timers

Permissions

Four roles, each granted separately. spm.admin inherits the other three.

Node Grants
spm.view every read-only subcommand
spm.profile starting/stopping the profiler
spm.alerts receiving lag alerts in chat
spm.admin reload, mute, reports, and everything above

How the plugin attribution works

There are two independent mechanisms, because they answer different questions.

Stack sampler. A low-priority daemon thread wakes every 25ms and takes a stack trace of the server thread, but only while it is actually RUNNABLE so idle time is not counted. It walks the trace from the top down and credits the first frame it can map to a plugin. The map is built from each plugin's main class package, plus a three-segment root so sibling packages (me.you.plugin.listeners) still resolve. It is statistical, costs almost nothing, and is on by default. This is what answers "who is eating my tick".

Event timing. Opt-in, and more invasive. It walks every HandlerList, finds each RegisteredListener, and swaps its EventExecutor for a wrapper that records call count, total time and worst case per plugin/event pair. The original executors are kept and restored on /spm profiler events off and on disable. Newly registered handlers are picked up by a periodic rescan. This costs one System.nanoTime() pair per handler call, so leave it off unless you are chasing something specific. This is what answers "which listener is slow".

If the server build does not expose an executor field the plugin can reach, event timing reports itself as unsupported and the sampler carries on alone.

How lag detection works

Each rule produces a severity (WARN, CRITICAL) or nothing. A rule has to come back dirty on thresholds.streak consecutive passes before it fires, which filters out single bad ticks like a world save. Once firing, it stays quiet for alerts.cooldown-seconds unless the severity gets worse, in which case it re-fires immediately. When it goes clean it emits a recovery notice.

Rules cover tick rate, tick time, heap, CPU, GC load, per-world entity counts and per-chunk entity pile-ups.

Configuration

plugins/SPM/config.yml. Every value is clamped to a sane range on load, so a typo will not take the server down.

intervals:
  scan-ticks: 100          # world/entity scan, main thread
  resource-ticks: 40       # memory + cpu poll, async
  watch-ticks: 60          # rule evaluation, async
  history-seconds: 300     # ring buffer depth

thresholds:
  tps-warn: 18.5
  tps-critical: 15.0
  mspt-warn: 40.0
  mspt-critical: 50.0
  heap-warn: 80.0
  heap-critical: 92.0
  entities-per-chunk: 150
  streak: 3                # passes before a rule fires

alerts:
  cooldown-seconds: 180
  max-per-minute: 6
  discord:
    enabled: false
    url: ""
    mention: ""            # e.g. "<@&ROLE_ID>"
  webhook:
    enabled: false
    url: ""
    headers: {}            # e.g. Authorization: "Bearer ..."

The generic webhook posts a flat JSON document with a metrics object, so it drops straight into Grafana, a Discord relay, or your own endpoint.

Notes

  • The world scan runs on the main thread because World#getEntities() has to. It is one pass with a reused Location, and it reports its own cost in /spm world so you can see what it costs you. Raise intervals.scan-ticks on very large servers.
  • Block entity counts are only computed when you ask for them (/spm chunks <world>), since that walk is expensive. It is also capped at 20ms, so on a very large world the count comes back marked + and partial rather than holding the tick.
  • MSPT comes from the server's own tick timings where available, and falls back to a measured tick interval otherwise.
  • Sampler attribution is by package. A plugin that shades another library under its own package will absorb that library's time, which is usually what you want anyway.

License

MIT. See LICENSE.

Written by Am4er.

About

Server performance monitor for Paper. TPS/MSPT, per-world entity and chunk stats, memory and CPU, plugin profiling and automatic lag alerts to Discord or a webhook.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages