Files
noitu/server/internal/wsapi/metrics.go
T
tiennm99 6a7cb34897 Merge branch 'worktree-agent-a582df720cc6e3dea' into dev
# Conflicts:
#	server/internal/wsapi/wsapi_test.go
#	web/src/lib/ws/messages.js
#	web/src/routes/online/+page.svelte
2026-09-21 01:58:02 +07:00

105 lines
4.3 KiB
Go

package wsapi
import "expvar"
// metricSet is the process's answer to "how is the game actually being
// played" — the question plans/reports/brainstormer-260921-0016 names as
// unanswerable with nothing but a dozen scattered slog calls. It is exposed
// at GET /debug/vars, but only when the operator opts in by setting
// NOITU_DEBUG_ADDR to a separate listen address: see cmd/noitu-server/main.go.
// Nothing here is read anywhere in the game logic — a counter that fed a
// decision back into play would need its own tests for correctness, not just
// for visibility.
//
// expvar's registry is a process-wide singleton by design (see the stdlib
// package doc), so these are package-level rather than server-instance
// fields: a second *Server in the same process — every wsapi test — shares
// one set of counters rather than fighting to register a second one under the
// same name.
type metricSet struct {
connectionsOpen *expvar.Int
connectionsTotal *expvar.Int
// roomsLive and roomsTotal are keyed "bot" or "pvp".
roomsLive *expvar.Map
roomsTotal *expvar.Map
// gamesStarted and gamesFinished are keyed the same way.
gamesStarted *expvar.Map
gamesFinished *expvar.Map
wordsSubmitted *expvar.Int
wordsAccepted *expvar.Int
// wordsRejected is keyed by game.RejectReason.String(), which is also
// what the word_rejected log line's reason field carries — one reason
// vocabulary rather than two.
wordsRejected *expvar.Map
// eliminations is keyed by the wire GameEndReason string, since that is
// the one place "the reconnect window ran out" is told apart from "they
// resigned" — both are game.EndResigned to the engine underneath.
eliminations *expvar.Map
chatLines *expvar.Int
// deadEndClaims is keyed "true" or "false": whether the claimed position
// actually had no legal move.
deadEndClaims *expvar.Map
// wordsReported counts accepted ReportWord messages — never the refused
// ones, which cost the sender an error and nothing else.
wordsReported *expvar.Int
// joinsRefused is keyed by the same UI error code the client is sent —
// "too_many_attempts", "room_not_found" or "room_full" — so a reader
// checking this counter against the frontend copy is checking against the
// same string, not a second name for it.
joinsRefused *expvar.Map
// botMoves is keyed by bot.Difficulty.String().
botMoves *expvar.Map
resumesAttempted *expvar.Int
resumesSucceeded *expvar.Int
// quickMatchQueued, quickMatchCancelled and quickMatchMatched count the
// three things that can happen to a QuickMatch: it waits, it is withdrawn
// (by CancelQuickMatch, a disconnect, or entering a room another way), or
// it is paired. Matched counts pairings, not players, so it rises by one
// per room a quick match opened.
quickMatchQueued *expvar.Int
quickMatchCancelled *expvar.Int
quickMatchMatched *expvar.Int
}
// metrics is the one instance every call site writes through. Built at
// package init rather than lazily, so every counter exists — reading at zero
// — from the moment the process starts, whether or not NOITU_DEBUG_ADDR is
// ever set.
var metrics = newMetricSet()
func newMetricSet() *metricSet {
return &metricSet{
connectionsOpen: expvar.NewInt("noitu_connections_open"),
connectionsTotal: expvar.NewInt("noitu_connections_total"),
roomsLive: expvar.NewMap("noitu_rooms_live"),
roomsTotal: expvar.NewMap("noitu_rooms_total"),
gamesStarted: expvar.NewMap("noitu_games_started"),
gamesFinished: expvar.NewMap("noitu_games_finished"),
wordsSubmitted: expvar.NewInt("noitu_words_submitted"),
wordsAccepted: expvar.NewInt("noitu_words_accepted"),
wordsRejected: expvar.NewMap("noitu_words_rejected"),
eliminations: expvar.NewMap("noitu_eliminations"),
chatLines: expvar.NewInt("noitu_chat_lines"),
deadEndClaims: expvar.NewMap("noitu_dead_end_claims"),
wordsReported: expvar.NewInt("noitu_words_reported"),
joinsRefused: expvar.NewMap("noitu_joins_refused"),
botMoves: expvar.NewMap("noitu_bot_moves"),
resumesAttempted: expvar.NewInt("noitu_resumes_attempted"),
resumesSucceeded: expvar.NewInt("noitu_resumes_succeeded"),
quickMatchQueued: expvar.NewInt("noitu_quick_match_queued"),
quickMatchCancelled: expvar.NewInt("noitu_quick_match_cancelled"),
quickMatchMatched: expvar.NewInt("noitu_quick_match_matched"),
}
}