aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pkg/api/api0/api.go26
-rw-r--r--pkg/api/api0/client.go12
-rw-r--r--pkg/api/api0/metrics.go20
-rw-r--r--pkg/api/api0/server.go17
-rw-r--r--pkg/api/api0/serverlist.go37
5 files changed, 109 insertions, 3 deletions
diff --git a/pkg/api/api0/api.go b/pkg/api/api0/api.go
index fb557ad..66f9a93 100644
--- a/pkg/api/api0/api.go
+++ b/pkg/api/api0/api.go
@@ -26,6 +26,7 @@ import (
"github.com/klauspost/compress/gzip"
"github.com/pg9182/ip2x"
+ "github.com/r2northstar/atlas/pkg/metricsx"
"github.com/r2northstar/atlas/pkg/origin"
"github.com/rs/zerolog/hlog"
"golang.org/x/mod/semver"
@@ -82,7 +83,8 @@ type Handler struct {
AllowGameServerIPv6 bool
// LookupIP looks up an IP2Location record for an IP. If not provided,
- // server regions are disabled.
+ // server regions and geo metrics are disabled. If it doesn't include latlon
+ // info, geo metrics will be disabled too.
LookupIP func(netip.Addr) (ip2x.Record, error)
// GetRegion gets the region name from an IP2Location record. If not
@@ -212,6 +214,28 @@ func (h *Handler) extractLauncherVersion(r *http.Request) string {
return ""
}
+// geoCounter2 increments a [metricsx.GeoCounter2] for the location of r.
+func (h *Handler) geoCounter2(r *http.Request, ctr *metricsx.GeoCounter2) {
+ a, err := netip.ParseAddrPort(r.RemoteAddr)
+ if err != nil {
+ return
+ }
+
+ c, err := h.LookupIP(a.Addr())
+ if err != nil {
+ return
+ }
+
+ lat, _ := c.GetFloat32(ip2x.Latitude)
+ lon, _ := c.GetFloat32(ip2x.Longitude)
+
+ if lat != 0 && lon != 0 {
+ ctr.Inc(float64(lat), float64(lon))
+ } else {
+ ctr.IncUnknown()
+ }
+}
+
// respFail writes a {success:false,error:ErrorObj} response with the provided
// response status.
func respFail(w http.ResponseWriter, r *http.Request, status int, obj ErrorObj) {
diff --git a/pkg/api/api0/client.go b/pkg/api/api0/client.go
index 87d5345..b5659ed 100644
--- a/pkg/api/api0/client.go
+++ b/pkg/api/api0/client.go
@@ -62,6 +62,7 @@ func (h *Handler) handleMainMenuPromos(w http.ResponseWriter, r *http.Request) {
}
h.m().client_mainmenupromos_requests_total.success(h.extractLauncherVersion(r)).Inc()
+ h.geoCounter2(r, h.m().client_mainmenupromos_requests_map)
var p MainMenuPromos
if h.MainMenuPromos != nil {
@@ -293,6 +294,8 @@ func (h *Handler) handleClientOriginAuth(w http.ResponseWriter, r *http.Request)
}
h.m().client_originauth_requests_total.success.Inc()
+ h.geoCounter2(r, h.m().client_originauth_requests_map)
+
respJSON(w, r, http.StatusOK, map[string]any{
"success": true,
"token": acct.AuthToken,
@@ -610,7 +613,14 @@ func (h *Handler) handleClientServers(w http.ResponseWriter, r *http.Request) {
h.m().client_servers_response_size_bytes.none.Update(float64(len(buf)))
}
- h.m().client_servers_requests_total.success(h.extractLauncherVersion(r)).Inc()
+ lver := h.extractLauncherVersion(r)
+ h.m().client_servers_requests_total.success(lver).Inc()
+ if lver != "" {
+ h.geoCounter2(r, h.m().client_servers_requests_map.northstar)
+ } else {
+ h.geoCounter2(r, h.m().client_servers_requests_map.other)
+ }
+
w.Header().Set("Content-Length", strconv.Itoa(len(buf)))
w.WriteHeader(http.StatusOK)
if r.Method != http.MethodHead {
diff --git a/pkg/api/api0/metrics.go b/pkg/api/api0/metrics.go
index 57e3b0a..8f9a0b5 100644
--- a/pkg/api/api0/metrics.go
+++ b/pkg/api/api0/metrics.go
@@ -6,6 +6,7 @@ import (
"reflect"
"github.com/VictoriaMetrics/metrics"
+ "github.com/r2northstar/atlas/pkg/metricsx"
)
// note: for results, fail_ prefix is for errors which are likely a problem with the backend, and reject_ are for client errors
@@ -55,7 +56,8 @@ type apiMetrics struct {
success func(version string) *metrics.Counter
http_method_not_allowed *metrics.Counter
}
- client_originauth_requests_total struct {
+ client_mainmenupromos_requests_map *metricsx.GeoCounter2
+ client_originauth_requests_total struct {
success *metrics.Counter
reject_bad_request *metrics.Counter
reject_versiongate *metrics.Counter
@@ -68,6 +70,7 @@ type apiMetrics struct {
fail_other_error *metrics.Counter
http_method_not_allowed *metrics.Counter
}
+ client_originauth_requests_map *metricsx.GeoCounter2
client_originauth_stryder_auth_duration_seconds *metrics.Histogram
client_originauth_origin_username_lookup_duration_seconds *metrics.Histogram
client_originauth_origin_username_lookup_calls_total struct {
@@ -106,6 +109,10 @@ type apiMetrics struct {
success func(version string) *metrics.Counter
http_method_not_allowed *metrics.Counter
}
+ client_servers_requests_map struct {
+ northstar *metricsx.GeoCounter2
+ other *metricsx.GeoCounter2
+ }
client_servers_response_size_bytes struct {
gzip *metrics.Histogram
none *metrics.Histogram
@@ -163,6 +170,13 @@ func (h *Handler) WritePrometheus(w io.Writer) {
h.m().set.WritePrometheus(w)
}
+func (h *Handler) WritePrometheusGeo(w io.Writer) {
+ h.m().client_mainmenupromos_requests_map.WritePrometheus(w)
+ h.m().client_originauth_requests_map.WritePrometheus(w)
+ h.m().client_servers_requests_map.northstar.WritePrometheus(w)
+ h.m().client_servers_requests_map.other.WritePrometheus(w)
+}
+
// m gets metrics objects for h.
//
// We use it instead of using a *metrics.Set directly because:
@@ -213,6 +227,7 @@ func (h *Handler) m() *apiMetrics {
}
mo.client_mainmenupromos_requests_total.success("unknown")
mo.client_mainmenupromos_requests_total.http_method_not_allowed = mo.set.NewCounter(`atlas_api0_client_servers_response_size_bytes{result="http_method_not_allowed"}`)
+ mo.client_mainmenupromos_requests_map = metricsx.NewGeoCounter2(`atlas_api0_client_mainmenupromos_requests_map`)
mo.client_originauth_requests_total.success = mo.set.NewCounter(`atlas_api0_client_originauth_requests_total{result="success"}`)
mo.client_originauth_requests_total.reject_bad_request = mo.set.NewCounter(`atlas_api0_client_originauth_requests_total{result="reject_bad_request"}`)
mo.client_originauth_requests_total.reject_versiongate = mo.set.NewCounter(`atlas_api0_client_originauth_requests_total{result="reject_versiongate"}`)
@@ -224,6 +239,7 @@ func (h *Handler) m() *apiMetrics {
mo.client_originauth_requests_total.fail_stryder_error = mo.set.NewCounter(`atlas_api0_client_originauth_requests_total{result="fail_stryder_error"}`)
mo.client_originauth_requests_total.fail_other_error = mo.set.NewCounter(`atlas_api0_client_originauth_requests_total{result="fail_other_error"}`)
mo.client_originauth_requests_total.http_method_not_allowed = mo.set.NewCounter(`atlas_api0_client_originauth_requests_total{result="http_method_not_allowed"}`)
+ mo.client_originauth_requests_map = metricsx.NewGeoCounter2(`atlas_api0_client_originauth_requests_map`)
mo.client_originauth_stryder_auth_duration_seconds = mo.set.NewHistogram(`atlas_api0_client_originauth_stryder_auth_duration_seconds`)
mo.client_originauth_origin_username_lookup_duration_seconds = mo.set.NewHistogram(`atlas_api0_client_originauth_origin_username_lookup_duration_seconds`)
mo.client_originauth_origin_username_lookup_calls_total.success = mo.set.NewCounter(`atlas_api0_client_originauth_origin_username_lookup_calls_total{result="success"}`)
@@ -260,6 +276,8 @@ func (h *Handler) m() *apiMetrics {
}
mo.client_servers_requests_total.success("unknown")
mo.client_servers_requests_total.http_method_not_allowed = mo.set.NewCounter(`atlas_api0_client_servers_requests_total{result="http_method_not_allowed"}`)
+ mo.client_servers_requests_map.northstar = metricsx.NewGeoCounter2(`atlas_api0_client_servers_requests_map{user_agent="northstar"}`)
+ mo.client_servers_requests_map.other = metricsx.NewGeoCounter2(`atlas_api0_client_servers_requests_map{user_agent="other"}`)
mo.client_servers_response_size_bytes.gzip = mo.set.NewHistogram(`atlas_api0_client_servers_response_size_bytes{compression="gzip"}`)
mo.client_servers_response_size_bytes.none = mo.set.NewHistogram(`atlas_api0_client_servers_response_size_bytes{compression="none"}`)
mo.server_upsert_requests_total.success_updated = func(action string) *metrics.Counter {
diff --git a/pkg/api/api0/server.go b/pkg/api/api0/server.go
index ba04bc0..ad7133c 100644
--- a/pkg/api/api0/server.go
+++ b/pkg/api/api0/server.go
@@ -11,6 +11,7 @@ import (
"strings"
"time"
+ "github.com/pg9182/ip2x"
"github.com/r2northstar/atlas/pkg/a2s"
"github.com/r2northstar/atlas/pkg/api/api0/api0gameserver"
"github.com/rs/zerolog/hlog"
@@ -161,6 +162,22 @@ func (h *Handler) handleServerUpsert(w http.ResponseWriter, r *http.Request) {
if canCreate || canUpdate {
if h.LookupIP != nil && h.GetRegion != nil {
if rec, err := h.LookupIP(raddr.Addr()); err == nil {
+ var lat, lon float64
+ if v, _ := rec.GetFloat32(ip2x.Latitude); v != 0 {
+ lat = float64(v)
+ }
+ if v, _ := rec.GetFloat32(ip2x.Longitude); v != 0 {
+ lon = float64(v)
+ }
+ if canCreate {
+ s.Latitude = lat
+ s.Longitude = lon
+ }
+ if canUpdate {
+ u.Latitude = &lat
+ u.Longitude = &lon
+ }
+
region, err := h.GetRegion(raddr.Addr(), rec)
if err == nil || region != "" { // if an error occurs, we may still have a best-effort region
if canCreate {
diff --git a/pkg/api/api0/serverlist.go b/pkg/api/api0/serverlist.go
index 9725bf9..3a11c21 100644
--- a/pkg/api/api0/serverlist.go
+++ b/pkg/api/api0/serverlist.go
@@ -17,6 +17,7 @@ import (
"unicode/utf8"
"github.com/klauspost/compress/gzip"
+ "github.com/r2northstar/atlas/pkg/metricsx"
"github.com/r2northstar/atlas/pkg/nstypes"
)
@@ -84,6 +85,9 @@ type Server struct {
Description string
Password string // blank for none
+ Latitude float64
+ Longitude float64
+
VerificationDeadline time.Time // zero once verified
LastHeartbeat time.Time
@@ -124,6 +128,8 @@ type ServerUpdate struct {
Name *string
Region *string
Description *string
+ Latitude *float64
+ Longitude *float64
PlayerCount *int
MaxPlayers *int
Map *string
@@ -681,6 +687,31 @@ func (s *ServerList) WritePrometheus(w io.Writer) {
w.Write(s.GetMetrics())
}
+// WritePrometheusGeo writes location metrics for s to w.
+func (s *ServerList) WritePrometheusGeo(w io.Writer) {
+ t := s.now()
+
+ // take a read lock on the server list
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ if s.servers1 == nil {
+ return
+ }
+
+ ctr := metricsx.NewGeoCounter2(`atlas_api0sl_map`)
+ for _, srv := range s.servers1 {
+ if s.serverState(srv, t) == serverListStateAlive {
+ if srv.Latitude != 0 && srv.Longitude != 0 {
+ ctr.Inc(srv.Latitude, srv.Longitude)
+ } else {
+ ctr.IncUnknown()
+ }
+ }
+ }
+ ctr.WritePrometheus(w)
+}
+
// GetLiveServers loops over live (i.e., not dead/ghost) servers until fn
// returns false. The order of the servers is non-deterministic.
func (s *ServerList) GetLiveServers(fn func(*Server) bool) {
@@ -820,6 +851,12 @@ func (s *ServerList) ServerHybridUpdatePut(u *ServerUpdate, c *Server, l ServerL
if u.Description != nil {
esrv.Description, changed = *u.Description, true
}
+ if u.Latitude != nil {
+ esrv.Latitude, changed = *u.Latitude, true
+ }
+ if u.Longitude != nil {
+ esrv.Longitude, changed = *u.Longitude, true
+ }
if u.Map != nil {
esrv.Map, changed = *u.Map, true
}