Fixed Connection Time

This commit is contained in:
2026-04-29 10:23:41 +02:00
parent 8c9fe6b82d
commit 0859a0476c
+72 -69
View File
@@ -21,44 +21,22 @@ from .metrics_parser import XonoticMetrics
# ── Metric definitions (names, help strings, labels) ───────────────────────── # ── Metric definitions (names, help strings, labels) ─────────────────────────
_METRIC_DEFS: list[tuple[str, str]] = [ _METRIC_DEFS: list[tuple[str, str]] = [
("xonotic_up", ("xonotic_up", "1 if the server was reachable, 0 otherwise"),
"1 if the server was reachable, 0 otherwise"), ("xonotic_sv_public", "Value of sv_public cvar (1 = listed on master server)"),
("xonotic_ping_seconds", "Round-trip time to the server in seconds"),
("xonotic_sv_public", ("xonotic_timing_cpu_percent", "Server CPU usage percentage reported by status"),
"Value of sv_public cvar (1 = listed on master server)"), ("xonotic_timing_lost_percent", "Percentage of packets lost reported by status"),
("xonotic_timing_offset_avg_ms", "Average timing offset in milliseconds"),
("xonotic_ping_seconds", ("xonotic_timing_offset_max_ms", "Maximum timing offset in milliseconds"),
"Round-trip time to the server in seconds"), (
"xonotic_timing_offset_sdev_ms",
("xonotic_timing_cpu_percent", "Standard deviation of timing offset in milliseconds",
"Server CPU usage percentage reported by status"), ),
("xonotic_players_active", "Number of active (scoring) players"),
("xonotic_timing_lost_percent", ("xonotic_players_spectators", "Number of spectators"),
"Percentage of packets lost reported by status"), ("xonotic_players_bots", "Number of bots"),
("xonotic_players_total", "Total players connected (active + spectators + bots)"),
("xonotic_timing_offset_avg_ms", ("xonotic_players_max", "Maximum player slots on the server"),
"Average timing offset in milliseconds"),
("xonotic_timing_offset_max_ms",
"Maximum timing offset in milliseconds"),
("xonotic_timing_offset_sdev_ms",
"Standard deviation of timing offset in milliseconds"),
("xonotic_players_active",
"Number of active (scoring) players"),
("xonotic_players_spectators",
"Number of spectators"),
("xonotic_players_bots",
"Number of bots"),
("xonotic_players_total",
"Total players connected (active + spectators + bots)"),
("xonotic_players_max",
"Maximum player slots on the server"),
] ]
@@ -86,26 +64,42 @@ def build_registry(
Additional constant labels to attach to every metric (optional). Additional constant labels to attach to every metric (optional).
""" """
registry = CollectorRegistry(auto_describe=False) registry = CollectorRegistry(auto_describe=False)
labels = {"instance": server_name, **(extra_labels or {})} labels = {"instance": server_name, **(extra_labels or {})}
def _gauge(name: str, helpstr: str, value: float) -> None: def _gauge(name: str, helpstr: str, value: float) -> None:
g = Gauge(name, helpstr, labelnames=list(labels), registry=registry) g = Gauge(name, helpstr, labelnames=list(labels), registry=registry)
g.labels(**labels).set(value) g.labels(**labels).set(value)
# always emit "up" # always emit "up"
_gauge("xonotic_up", "1 if the server was reachable, 0 otherwise", 1.0 if up else 0.0) _gauge(
"xonotic_up", "1 if the server was reachable, 0 otherwise", 1.0 if up else 0.0
)
if metrics is not None and up: if metrics is not None and up:
_gauge("xonotic_sv_public", "Value of sv_public cvar", metrics.sv_public) _gauge("xonotic_sv_public", "Value of sv_public cvar", metrics.sv_public)
_gauge("xonotic_ping_seconds", "Round-trip time in seconds", metrics.ping) _gauge("xonotic_ping_seconds", "Round-trip time in seconds", metrics.ping)
_gauge("xonotic_timing_cpu_percent", "Server CPU usage %", metrics.timing_cpu) _gauge("xonotic_timing_cpu_percent", "Server CPU usage %", metrics.timing_cpu)
_gauge("xonotic_timing_lost_percent", "Packet loss %", metrics.timing_lost) _gauge("xonotic_timing_lost_percent", "Packet loss %", metrics.timing_lost)
_gauge("xonotic_timing_offset_avg_ms", "Avg timing offset ms", metrics.timing_offset_avg) _gauge(
_gauge("xonotic_timing_offset_max_ms", "Max timing offset ms", metrics.timing_offset_max) "xonotic_timing_offset_avg_ms",
_gauge("xonotic_timing_offset_sdev_ms","Timing offset sdev ms", metrics.timing_offset_sdev) "Avg timing offset ms",
_gauge("xonotic_players_active", "Active (scoring) players", metrics.players_active) metrics.timing_offset_avg,
_gauge("xonotic_players_spectators", "Spectators", metrics.players_spectators) )
_gauge("xonotic_players_bots", "Bots", metrics.players_bots) _gauge(
"xonotic_timing_offset_max_ms",
"Max timing offset ms",
metrics.timing_offset_max,
)
_gauge(
"xonotic_timing_offset_sdev_ms",
"Timing offset sdev ms",
metrics.timing_offset_sdev,
)
_gauge(
"xonotic_players_active", "Active (scoring) players", metrics.players_active
)
_gauge("xonotic_players_spectators", "Spectators", metrics.players_spectators)
_gauge("xonotic_players_bots", "Bots", metrics.players_bots)
_gauge( _gauge(
"xonotic_players_total", "xonotic_players_total",
"Total connected (active + spectators + bots)", "Total connected (active + spectators + bots)",
@@ -119,6 +113,7 @@ def build_registry(
CONTENT_TYPE = CONTENT_TYPE_LATEST CONTENT_TYPE = CONTENT_TYPE_LATEST
def _safe_label(value: str) -> str: def _safe_label(value: str) -> str:
"""Keep only printable ASCII characters (32126).""" """Keep only printable ASCII characters (32126)."""
r = "".join(c for c in value if 32 <= ord(c) <= 126).strip() r = "".join(c for c in value if 32 <= ord(c) <= 126).strip()
@@ -126,10 +121,11 @@ def _safe_label(value: str) -> str:
return r return r
return "Anonymous" return "Anonymous"
def build_player_geo_registry( def build_player_geo_registry(
server_name: str, server_name: str,
players: list, # list[PlayerRow] players: list, # list[PlayerRow]
geo_results: dict, # dict[ip, GeoResult] geo_results: dict, # dict[ip, GeoResult]
) -> bytes: ) -> bytes:
""" """
Emits xonotic_player_info and xonotic_player_geo metrics. Emits xonotic_player_info and xonotic_player_geo metrics.
@@ -171,9 +167,9 @@ def build_player_geo_registry(
continue # private IP — excluded continue # private IP — excluded
country = geo.country country = geo.country
city = geo.city city = geo.city
lat = str(round(geo.lat, 4)) lat = str(round(geo.lat, 4))
lon = str(round(geo.lon, 4)) lon = str(round(geo.lon, 4))
info_gauge.labels( info_gauge.labels(
instance=server_name, instance=server_name,
@@ -182,7 +178,7 @@ def build_player_geo_registry(
slot=player.slot, slot=player.slot,
name=_safe_label(player.name), name=_safe_label(player.name),
country=country, country=country,
city=city city=city,
).set(1) ).set(1)
geo_gauge.labels( geo_gauge.labels(
@@ -193,7 +189,7 @@ def build_player_geo_registry(
city=city, city=city,
lat=lat, lat=lat,
lon=lon, lon=lon,
ping=str(player.ping) ping=str(player.ping),
).set(1) ).set(1)
ping_gauge.labels( ping_gauge.labels(
@@ -210,6 +206,7 @@ def build_player_geo_registry(
return generate_latest(registry) return generate_latest(registry)
def build_match_registry( def build_match_registry(
server_name: str, server_name: str,
metrics: XonoticMetrics, metrics: XonoticMetrics,
@@ -217,24 +214,30 @@ def build_match_registry(
) -> bytes: ) -> bytes:
""" """
Emits match-level and per-player metrics for the match dashboard. Emits match-level and per-player metrics for the match dashboard.
This works but its not great...
""" """
registry = CollectorRegistry(auto_describe=False) registry = CollectorRegistry(auto_describe=False)
labels = {"instance": server_name} labels = {"instance": server_name}
def _gauge(name, helpstr, value): def _gauge(name, helpstr, value):
g = Gauge(name, helpstr, labelnames=list(labels), registry=registry) g = Gauge(name, helpstr, labelnames=list(labels), registry=registry)
g.labels(**labels).set(value) g.labels(**labels).set(value)
# match metadata # match metadata
_gauge("xonotic_match_timelimit_seconds", _gauge(
"Match time limit in seconds", "xonotic_match_timelimit_seconds",
match_meta.get("timelimit", 0) * 60) "Match time limit in seconds",
_gauge("xonotic_match_fraglimit", match_meta.get("timelimit", 0) * 60,
"Frag limit for current match", )
match_meta.get("fraglimit", 0)) _gauge(
_gauge("xonotic_match_teamplay", "xonotic_match_fraglimit",
"Teamplay mode (0=FFA)", "Frag limit for current match",
match_meta.get("teamplay", 0)) match_meta.get("fraglimit", 0),
)
_gauge(
"xonotic_match_teamplay", "Teamplay mode (0=FFA)", match_meta.get("teamplay", 0)
)
# map name as a label on a info metric # map name as a label on a info metric
map_gauge = Gauge( map_gauge = Gauge(
@@ -263,7 +266,7 @@ def build_match_registry(
"Player connection stats", "Player connection stats",
labelnames=["instance", "name", "slot", "ping", "packetloss", "time_seconds"], labelnames=["instance", "name", "slot", "ping", "packetloss", "time_seconds"],
registry=registry, registry=registry,
) )
for player in metrics.players: for player in metrics.players:
if player.is_bot: if player.is_bot:
@@ -282,6 +285,6 @@ def build_match_registry(
ping=str(player.ping), ping=str(player.ping),
packetloss=str(player.packetloss), packetloss=str(player.packetloss),
time_seconds=str(player.time_seconds), time_seconds=str(player.time_seconds),
).set(1) ).set(player.time_seconds)
return generate_latest(registry) return generate_latest(registry)