A lightweight, high-performance server performance monitoring plugin for Minecraft 1.21.1 (Paper / Folia). Collects and reports real-time server metrics — MSPT, entity count, and loaded chunk count — with negligible overhead and a clean architecture.
- Real-time MSPT tracking via a rolling average over the last 20 ticks
- Entity count and loaded chunk count across all worlds
- Per-world breakdown — entities and chunks per dimension
- Metrics history — last 60 snapshots queryable in-game
- Alert system — broadcasts warnings to ops when MSPT or entity count exceed configurable thresholds (60-second cooldown)
- PlaceholderAPI support — expose metrics in scoreboards, tab lists, and chat
- Folia support — auto-detects Folia at startup; uses
GlobalRegionSchedulerfor safe scheduling andRegionSchedulerper-chunk tasks for entity hotspot detection - bStats telemetry — anonymous MSPT and entity count brackets reported (opt-out in config)
- Metrics sampled every 20 ticks (1 second) by default — configurable, no per-tick world scans
- Thread-safe snapshot system using
AtomicReference - Dependency-injected, interface-driven architecture
| Requirement | Version |
|---|---|
| Minecraft | 1.21.1 |
| Server Software | Paper 1.21.1 or Folia 1.21.1 |
| Java | 17 or higher (built with Java 21) |
Spigot is not supported. Paper is required for API compatibility.
- Download
Thread Sight-1.0-SNAPSHOT.jarfrom the releases page or build from source (see below). - Place the jar in your server's
plugins/folder. - Start or restart the server.
- Confirm the plugin loaded:
[Thread Sight] Enabled
[Thread Sight] Running metrics scheduler
On Folia the startup message reads Enabled (Folia mode). If PlaceholderAPI is present, Hooked into PlaceholderAPI will also appear.
No configuration is required. The plugin is ready to use immediately with sensible defaults.
| Command | Description | Permission |
|---|---|---|
/threadsight |
Global metrics — MSPT, entities, chunks | none |
/threadsight worlds |
Per-world entity and chunk breakdown | none |
/threadsight regions |
Top entity hotspots by chunk (Folia only) | none |
/threadsight history [n] |
Last n snapshots (default 10, max 60) | none |
/threadsight reload |
Reload config.yml without restart | threadsight.reload (op) |
All sub-commands support tab completion.
[ThreadSight]
MSPT: 18.4
Entities: 245
Chunks: 812
MSPT is color-coded: green < 35ms · yellow 35–50ms · red > 50ms.
[ThreadSight] Per-world breakdown:
world — Entities: 198, Chunks: 612
world_nether — Entities: 32, Chunks: 120
world_the_end — Entities: 15, Chunks: 80
[ThreadSight] Last 5 snapshots:
14:02:01 | MSPT: 18.4 | Entities: 245 | Chunks: 812
14:02:02 | MSPT: 19.1 | Entities: 247 | Chunks: 812
...
| Placeholder | Returns |
|---|---|
%threadsight_mspt% |
Current MSPT (1 decimal place) |
%threadsight_entities% |
Total entity count |
%threadsight_chunks% |
Total loaded chunk count |
%threadsight_mspt_status% |
Excellent / Good / Degraded / Poor |
Requires PlaceholderAPI to be installed. ThreadSight registers automatically if PAPI is detected at startup.
plugins/Thread Sight/config.yml is created on first run:
# How often (in ticks) to collect a metrics snapshot. Default: 20 (1 second)
sampling-interval-ticks: 20
alert-thresholds:
# Broadcast to ops when MSPT exceeds this value. Set to 0 to disable.
mspt: 50.0
# Broadcast to ops when total entity count exceeds this value. Set to 0 to disable.
entities: 2000
# Send anonymous usage stats to bStats.org
bstats-enabled: trueAlerts broadcast to all online players with the threadsight.alerts permission (default: op). Alerts for each metric have a 60-second cooldown to prevent spam.
The server targets 50ms per tick (20 TPS). ThreadSight measures the wall-clock time between ticks and computes a rolling average over the last 20 samples.
| MSPT | Server Health |
|---|---|
| < 35ms | Excellent — headroom to spare |
| 35–50ms | Good — running smoothly |
| 50–75ms | Degraded — TPS starting to drop |
| > 75ms | Poor — significant lag |
High entity counts are one of the most common causes of server lag.
| Count | Status |
|---|---|
| < 500 | Healthy for most servers |
| 500–2000 | Monitor closely |
| > 2000 | Investigate entity farms or mob spawning |
Use /threadsight regions on Folia to locate which chunk areas are driving the count.
- Java 21 JDK (Eclipse Temurin recommended)
- Git
git clone <your-repo-url>
cd "Thread Sight"
gradlew.bat build # Windows
./gradlew build # Linux/MacThe compiled jar will be at:
build/libs/Thread Sight-1.0-SNAPSHOT.jar
gradlew.bat runServer # Windows
./gradlew runServer # Linux/MacThe server runs in run/ with 2GB RAM. Accept the EULA on first run by setting eula=true in run/eula.txt.
src/main/java/dev/nullstilles/threadSight/
├── Main.java Entry point — wires all components
├── metrics/
│ ├── MetricsSnapshot.java Immutable record (mspt, entities, chunks, timestamp, world/region breakdown)
│ ├── MetricsService.java Collects raw data; owns history and optional Folia collector
│ ├── MetricsScheduler.java Drives per-tick timing and per-second snapshot updates
│ ├── MetricsHistory.java Ring buffer of last 60 snapshots
│ ├── WorldMetrics.java Per-world entity and chunk record
│ ├── RegionMetrics.java Per-chunk-region entity record (Folia only)
│ └── FoliaRegionCollector.java Schedules RegionScheduler tasks; maintains hotspot map
├── scheduler/
│ ├── SchedulerAdapter.java Interface abstracting the scheduler
│ ├── BukkitSchedulerAdapter.java Bukkit implementation
│ └── FoliaSchedulerAdapter.java Folia GlobalRegionScheduler implementation
├── config/
│ └── ThreadSightConfig.java Loads and hot-reloads config.yml
├── alert/
│ └── AlertService.java Threshold checks; broadcasts with 60s cooldown
├── placeholder/
│ └── ThreadSightPlaceholders.java PlaceholderAPI expansion
└── command/
└── ThreadSightCommand.java Handles all /threadsight sub-commands
Folia has a different scheduler API (RegionScheduler, GlobalRegionScheduler). Routing all task scheduling through SchedulerAdapter means adding Folia support required only one new class with no changes to MetricsService or MetricsScheduler. Main.java detects Folia at runtime via Class.forName and selects the appropriate adapter.
On Folia, FoliaRegionCollector samples one anchor chunk per 4×4 chunk tile. For each anchor, RegionScheduler.execute() runs a task on the thread that owns that chunk's region — the only thread allowed to safely read entity data there. Results accumulate in a ConcurrentHashMap; stale entries are pruned each collection cycle. The top 20 hotspots are included in every snapshot.
A per-tick task records System.nanoTime() on each firing. The delta between consecutive calls gives tick duration in nanoseconds, converted to milliseconds. The last 20 values are stored in an ArrayDeque ring buffer.
MetricsService.recordTick()andupdateSnapshot()both run on the main/global-region thread.MetricsSnapshotis an immutable Java record.AtomicReference<MetricsSnapshot>allows safe reads from any thread (command handler, PAPI).MetricsHistorysynchronizes onthisfor buffer access.FoliaRegionCollector.latestis aConcurrentHashMapwritten by many region threads, read by the global thread.
| Permission | Default | Description |
|---|---|---|
threadsight.reload |
op | Reload config without restart |
threadsight.alerts |
op | Receive performance alert broadcasts |
MIT — free to use, modify, and distribute.