forked from Kweebec_Network/core-vitals
106 lines
3.9 KiB
Markdown
106 lines
3.9 KiB
Markdown
# 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](09_Operations_And_Security.md) for the rationale.
|
|
|
|
## API
|
|
|
|
```java
|
|
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.
|
|
|
|
```java
|
|
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.
|
|
|
|
```java
|
|
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.
|
|
|
|
```java
|
|
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](09_Operations_And_Security.md)).
|
|
|
|
## Prometheus scrape config
|
|
|
|
```yaml
|
|
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.
|