Performance¶
The Performance module is monitoring and diagnostics only - it is
deliberately not an aggressive "lag cleaner" that acts on the server on
its own. Everything it shows comes from standard, reliable Paper/JVM APIs
(Server#getTPS()/getAverageTickTime(), World#getChunkCount()/
getEntities(), Runtime), sampled on a timer and cached - never
recomputed on every GUI render. The one mutating operation it offers,
Entity Clear, is narrow, filtered, previewed, and confirmed - see below.
Open via /admin → Performance (needs universaladmin.performance.view).
Sampling and caching¶
PerformanceSamplingService takes one sample every
core.performance.refresh-interval (default 5s, 1s-10m,
requires a restart to change) via a single repeating Bukkit task -
never on GUI render, never on the main thread outside that scheduled tick
(reading World#getEntities() across every loaded world is not free; doing
it on every menu open would be exactly the "teure Berechnungen bei jedem
GUI Render" this module is built to avoid). The very first sample is taken
synchronously during onEnable, so the dashboard has real numbers
immediately rather than showing zeroes until the first scheduled tick.
Every GUI page reads the cached snapshot; PerformanceSamplingService is
also registered in the platform's ServiceRegistry for any other module
that wants a read-only look at current server load.
Dashboard¶
Read-only, one tile each:
- TPS - 1/5/15 minute averages (
Server#getTPS()) - MSPT - average milliseconds per tick (
Server#getAverageTickTime()) - Used / Max Memory -
Runtime#totalMemory() - freeMemory()/maxMemory(), plus percentage - Online Players
- Loaded Chunks - summed across every loaded world
- Entities - summed across every loaded world, players excluded (see "Players are not entities" below)
- World Count
- Uptime
The TPS tile also shows a short in-memory trend average (see "History" below) when at least one sample exists.
World Performance¶
One tile per currently loaded world: players, loaded chunks, entities. Same small, already-in-memory list as the Worlds module's World Browser - no pagination, no async load.
Entity Overview¶
Entities (never players - see below) grouped two ways:
- By Type - every currently loaded entity type, sorted by count,
paginated. Each tile shows a live count and, for a viewer holding
universaladmin.performance.entity-clear, doubles as the entry point to clear just that type (see "Entity Clear" below). - By World - reuses the same World Performance page above; the numbers are identical, so there is no separate implementation to keep in sync.
Chunk-level drilldown (which chunk a given entity cluster lives in) is not implemented in this version - noted here as a known limitation, not forgotten; the type/world breakdown above is usually enough to point staff at the right world before they go looking in-game.
Players are not entities¶
World#getEntities() technically includes players, but every count in
this module (dashboard, per-world, by-type, by-world) explicitly excludes
them - they already have their own "Online Players" tile and permission
model, and folding them into "Entities" would make every one of these
numbers misleading about what's actually contributing to entity-related
lag.
History¶
A short, bounded, in-memory-only window (PerformanceHistory, 120 samples
- about 10 minutes at the default 5s interval) of TPS/MSPT/memory samples,
used today for the dashboard's "5m avg TPS" line. Not persisted - see
ROADMAP.md: long metric history and real charts are a future web-app
feature, not part of this core version.
Alert Hooks¶
PerformanceSamplingService checks three thresholds after every sample and,
on a breach, notifies every online player holding
universaladmin.performance.view via NotificationService#notifyStaff -
the same interface a future Discord/web-push channel would implement,
rather than a bespoke transport for this module:
| Alert | Setting | Default |
|---|---|---|
| TPS below threshold | performance.alerts.tps-threshold |
18.0 |
| MSPT above threshold | performance.alerts.mspt-threshold-ms |
50.0 |
| Memory above threshold | performance.alerts.memory-threshold-percent |
90.0 |
An alert fires at most once per performance.alerts.cooldown (default
5m) while the same threshold stays breached, so a persisting problem
doesn't spam staff every refresh interval - this is deliberately simple
hysteresis, not a full alert engine (no escalation levels, no
acknowledgement, no per-recipient preferences). Alert messages are plain
text (not MiniMessage) because NotificationService's in-game
implementation renders Notification#message() literally - matching the
existing precedent (see e.g. moderation.enforcement.frozen-disconnect).
Entity Clear¶
Deliberately narrow, per its design constraints - never all entities indiscriminately, never players, dangerous/valuable types excluded by default, filter + live preview + confirmation + audit every time:
- Never players. Hard-coded in
EntityClearFilter, not just a GUI convention -ClearEntitiesActionitself refuses to touch aPlayerregardless of what a caller requests. - Protected types excluded by default.
performance.entity-clear.protected-types(default: villagers, wandering traders, armor stands, item frames, glow item frames, paintings, the ender dragon, the wither, allays) are always stripped from the requested type set before anything runs - a caller cannot opt back into removing a protected type by asking for it directly. - Named/tamed/leashed entities are always skipped, regardless of type - a config typo should not be the only thing standing between a lag-cleanup click and someone's named pet.
- Filter. Clear a single type (Entity Overview → By Type → click a
non-protected tile) or "Clear All Non-Protected" (Entity Overview) across
every currently loaded world - this version does not offer a per-world
scope from the GUI (
ClearEntitiesAction's input supports it for a future command/API caller, just not exposed here yet). - Preview count. Shown in the confirmation dialog, computed by the exact
same
EntityClearFilterpredicate the action itself uses (viaPerformanceSamplingService#previewClearCount) - what a player confirms is what actually happens, never an approximation. - Confirmation. A dangerous
ConfirmationDialog, same as shutdown/restart/ban. - Audit. Every clear runs through
ActionExecutorlike any other action - actor, the effective type set, world scope, and removed count are all on the resulting audit entry, never a bespoke log line.
Permission: universaladmin.performance.entity-clear.
Permissions¶
| Node | Default | Meaning |
|---|---|---|
universaladmin.performance.view |
op | View performance diagnostics (dashboard, world/entity breakdown); also who receives alert notifications |
universaladmin.performance.entity-clear |
op | Clear non-player entities matching a filter, with preview and confirmation |
See docs/user/permissions.md for how these fit into the platform-wide list.
Settings¶
| Setting | Default | Meaning |
|---|---|---|
core.performance.refresh-interval |
5s |
How often the dashboard/world/entity caches refresh (1s-10m). Requires a restart. |
performance.alerts.tps-threshold |
18.0 |
TPS (1-minute average) below which a staff alert fires |
performance.alerts.mspt-threshold-ms |
50.0 |
Average MSPT above which a staff alert fires |
performance.alerts.memory-threshold-percent |
90.0 |
Used/max heap memory percentage above which a staff alert fires |
performance.alerts.cooldown |
5m |
Minimum time between two alerts of the same kind |
performance.entity-clear.protected-types |
see above | Entity types Entity Clear always refuses to remove |
core.performance.refresh-interval predates this module's real
implementation and stays a core-namespaced setting; everything else above
is registered under the module's own performance settings namespace (see
docs/development/settings.md). None of the
new settings require a restart.
Limitations¶
- No long-term metric history or charts - see "History" above; that is intentionally a future web-app feature, not this core version.
- No chunk-level drilldown in the Entity Overview - see that section above.
- Entity Clear has no per-world scope in the GUI yet (all loaded worlds only) - the underlying action already accepts one for a future command/API frontend.
- No dedicated
/admin performance ...console command yet - console can only be reached through whatever a future command frontend adds; this version is fully manageable in-game via the GUI, same situation as the Server module's maintenance mode.
Commands¶
/admin perf (/lag) and /admin entities [clear <type|all> [world] confirm] -
see commands.md.