3.9 KiB
Metrics
What you get
A MetricsService with three metric types and a Prometheus-compatible HTTP endpoint that scrapers (Prometheus, Grafana Agent, Vector) can read every N seconds.
Metric state lives in JVM memory only. It resets when the server restarts. Persistence is the scraper's job - see 09_Operations_And_Security.md for the rationale.
API
public interface MetricsService {
Counter counter(String name, Map<String, String> labels);
Gauge gauge(String name, Map<String, String> labels);
Timer timer(String name, Map<String, String> labels);
}
All three are interned by (name, labels). Calling counter("matches_started", Map.of("game", "bridge_duel")) twice returns the same instance, so it's safe to call inside a hot path.
Counter
Monotonically increasing.
Counter started = metrics.counter("matches_started", Map.of("game", "bridge_duel"));
started.inc();
started.inc(5);
double v = started.value();
Gauge
Point-in-time value. Set explicitly or bind to a supplier that's called whenever Prometheus scrapes.
Gauge queueLen = metrics.gauge("queue_length", Map.of("game", "bridge_duel"));
queueLen.set(42);
Gauge memUsed = metrics.gauge("custom_memory_bytes", Map.of("pool", "off_heap"));
memUsed.bind(() -> myAllocator.currentBytes());
bind is preferred when the value is computed from somewhere else - it dodges write-paths and gets the current value at scrape time.
Timer
Records durations. Tracks count and total seconds.
Timer dbQuery = metrics.timer("db_query_seconds", Map.of("query", "find_player"));
long start = System.nanoTime();
runQuery();
dbQuery.recordNanos(System.nanoTime() - start);
The Prometheus output for a timer is two series: <name>_count and <name>_sum_seconds. To get the average in PromQL: rate(db_query_seconds_sum_seconds[5m]) / rate(db_query_seconds_count[5m]).
Naming
Metric names get sanitized to [a-zA-Z0-9_] (anything else becomes _). Stick to lowercase snake_case to avoid surprises. Don't include label values in the name - use labels for that. Example:
Good: matches_started{game="bridge_duel"}
Bad: matches_started_bridge_duel
Labels with high cardinality (player UUIDs, match IDs, timestamps) will blow up Prometheus storage. Stick to bounded values: game id, world name, role.
Built-in metrics
NetworkCore registers these on its own. You'll see them in the output without doing anything.
| Name | Type | Labels | Source |
|---|---|---|---|
players_online |
gauge | none | Universe.get().getPlayerCount() |
jvm_memory_used_bytes |
gauge | pool=<pool name> |
JVM MemoryPoolMXBean |
messagebus_published_total |
counter | none | every MessageBus.publish call |
messagebus_received_total |
counter | none | every accepted incoming message |
messagebus_rejected_total |
counter | none | every message rejected due to bad auth token |
rpc_request_total |
counter | service, method, result |
every RPC completion (result is ok, error, timeout, or decode_error) |
rpc_request_duration_seconds |
timer | service, method |
every RPC completion |
The endpoint
GET http://<bind>:<port><path>
Defaults: http://127.0.0.1:9100/metrics. Response is Prometheus text format 0.0.4. Bind defaults to loopback - you have to opt into exposing it externally (see 09_Operations_And_Security.md).
Prometheus scrape config
scrape_configs:
- job_name: 'kweebec_servers'
static_configs:
- targets:
- '10.0.0.10:9100'
- '10.0.0.11:9100'
- '10.0.0.12:9100'
When not to use this
Don't store anything you want to query later (like "what was Bob's match count yesterday"). That's a database, not a metrics system. Use MetricsService for things you want to graph or alert on - rates, latencies, queue depths, error counts.