From 441797ca7904ff4197a255d93b573c553f5e6722 Mon Sep 17 00:00:00 2001 From: Adam Snyder Date: Sat, 10 Dec 2022 17:25:56 -0800 Subject: [PATCH] New argument --exclude-player-metrics --- README.md | 1 + internal/collector/collector.go | 31 ++++++++----- internal/collector/collector_test.go | 66 +++++++++++++++++++++++++++- main.go | 5 ++- 4 files changed, 88 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 8b556ec..7c5a91e 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Flag | Variable | Default | Help --port | A2S_EXPORTER_PORT | 9841 | Port for the metrics exporter. --path | A2S_EXPORTER_PATH | /metrics | Path for the metrics exporter. --namespace | A2S_EXPORTER_NAMESPACE | a2s | Namespace prefix for all exported a2s metrics. +--exclude-player-metrics | A2S_EXPORTER_EXCLUDE_PLAYER_METRICS | false | If true, exclude all `player_*` metrics. This option may be necessary for some servers. --a2s-only-metrics | A2S_EXPORTER_A2S_ONLY_METRICS | false | If true, excludes Go runtime and promhttp metrics. --max-packet-size | A2S_EXPORTER_MAX_PACKET_SIZE | 1400 | Advanced option to set a non-standard max packet size of the A2S query server. diff --git a/internal/collector/collector.go b/internal/collector/collector.go index 6df929d..2ebd0cf 100644 --- a/internal/collector/collector.go +++ b/internal/collector/collector.go @@ -9,15 +9,16 @@ import ( ) type Collector struct { - addr string - clientOptions []func(*a2s.Client) error - client *a2s.Client - descs map[string]*prometheus.Desc + addr string + clientOptions []func(*a2s.Client) error + excludePlayerMetrics bool + client *a2s.Client + descs map[string]*prometheus.Desc } type adder func(name string, value float64, labelValues ...string) -func New(namespace, addr string, clientOptions ...func(*a2s.Client) error) *Collector { +func New(namespace, addr string, excludePlayerMetrics bool, clientOptions ...func(*a2s.Client) error) *Collector { descs := make(map[string]*prometheus.Desc) fullDesc := func(name, help string, labels []string) { @@ -54,9 +55,10 @@ func New(namespace, addr string, clientOptions ...func(*a2s.Client) error) *Coll playerDesc("player_the_ship_money", "Player's money in a The Ship server.") return &Collector{ - addr: addr, - clientOptions: clientOptions, - descs: descs, + addr: addr, + clientOptions: clientOptions, + excludePlayerMetrics: excludePlayerMetrics, + descs: descs, } } @@ -67,7 +69,7 @@ func (c *Collector) Describe(descs chan<- *prometheus.Desc) { } func (c *Collector) Collect(metrics chan<- prometheus.Metric) { - serverInfo, playerInfo := c.queryInfo() + serverInfo, playerInfo := c.queryInfo(c.excludePlayerMetrics) truthyFloat := func(v interface{}) float64 { if reflect.ValueOf(v).IsNil() { @@ -81,7 +83,10 @@ func (c *Collector) Collect(metrics chan<- prometheus.Metric) { } add("server_up", truthyFloat(serverInfo)) - add("player_up", truthyFloat(playerInfo)) + + if !c.excludePlayerMetrics { + add("player_up", truthyFloat(playerInfo)) + } addPreLabelled := func(name string, value float64, labelValues ...string) { labelValues2 := []string{serverInfo.Name} @@ -94,7 +99,7 @@ func (c *Collector) Collect(metrics chan<- prometheus.Metric) { } // queryInfo queries the A2S server over UDP. Failure will result in one or both of the return values being nil. -func (c *Collector) queryInfo() (serverInfo *a2s.ServerInfo, playerInfo *a2s.PlayerInfo) { +func (c *Collector) queryInfo(excludePlayerMetrics bool) (serverInfo *a2s.ServerInfo, playerInfo *a2s.PlayerInfo) { var err error // Lazy initialization of UDP client. @@ -113,6 +118,10 @@ func (c *Collector) queryInfo() (serverInfo *a2s.ServerInfo, playerInfo *a2s.Pla return } + if excludePlayerMetrics { + return + } + // A quirk of the a2s-go client is that in order for The Ship player queries to succeed, the client must be // constructed with The Ship App ID. playerClient := c.client diff --git a/internal/collector/collector_test.go b/internal/collector/collector_test.go index 4828a4d..c6b7c30 100644 --- a/internal/collector/collector_test.go +++ b/internal/collector/collector_test.go @@ -19,7 +19,7 @@ import ( // TestCollector_Describe_PrintTable tests the Describe function. // It also prints a Markdown-formatted table of all registered metrics, which can be copied to the README. func TestCollector_Describe_PrintTable(t *testing.T) { - c := collector.New("", "") + c := collector.New("", "", false) descs := testDescribe(c) if len(descs) == 0 { t.Error("expected Descs but got none") @@ -97,7 +97,7 @@ func TestCollector(t *testing.T) { // Set up the registry and gather metrics from the test A2S server. registry := prometheus.NewPedanticRegistry() - registry.MustRegister(collector.New("", conn.LocalAddr().String())) + registry.MustRegister(collector.New("", conn.LocalAddr().String(), false)) metrics, err := registry.Gather() if err != nil { t.Fatal(err) @@ -119,6 +119,68 @@ func TestCollector(t *testing.T) { ) } +func TestCollector_ExcludePlayerMetrics(t *testing.T) { + // Run a test A2S server. + conn, err := net.ListenUDP("udp", nil) + if err != nil { + t.Fatal(err) + } + srv := &testserver.TestServer{ + ServerInfo: &a2s.ServerInfo{ + Name: "foo", + Players: 3, + MaxPlayers: 6, + }, + PlayerInfo: &a2s.PlayerInfo{ + Count: 3, + Players: []*a2s.Player{ + { + Index: 0, + Name: "jon", + Duration: 32, + }, + { + Index: 0, + Name: "alice", + Duration: 64, + }, + // Duplicate players should be de-duplicated to avoid causing registry errors. + { + Index: 0, + Name: "alice", + Duration: 99, + }, + }, + }, + } + go func() { + t.Error(srv.Serve(conn)) + }() + + // Set up the registry and gather metrics from the test A2S server. + registry := prometheus.NewPedanticRegistry() + registry.MustRegister(collector.New("", conn.LocalAddr().String(), true)) + metrics, err := registry.Gather() + if err != nil { + t.Fatal(err) + } + + // Spot check the gathered metrics. + + testAssertGauge(t, metrics, "server_players", + expectGauge{value: 3, labels: map[string]string{"server_name": "foo"}}, + ) + testAssertGauge(t, metrics, "server_max_players", + expectGauge{value: 6, labels: map[string]string{"server_name": "foo"}}, + ) + + for _, family := range metrics { + if strings.HasPrefix(*family.Name, "player_") { + t.Errorf("metric name %s should have been excluded", *family.Name) + } + } +} + type expectGauge struct { value float64 labels map[string]string diff --git a/main.go b/main.go index 29e39e6..b7181a6 100644 --- a/main.go +++ b/main.go @@ -24,7 +24,8 @@ func main() { port := flag.Int("port", envOrDefaultInt("A2S_EXPORTER_PORT", 9841), "Port for the metrics exporter.") path := flag.String("path", envOrDefault("A2S_EXPORTER_PATH", "/metrics"), "Path for the metrics exporter.") namespace := flag.String("namespace", envOrDefault("A2S_EXPORTER_NAMESPACE", "a2s"), "Namespace prefix for all exported a2s metrics.") - a2sOnlyMetrics := flag.Bool("a2s-only-metrics", envOrDefaultBool("A2S_EXPORTER_A2S_ONLY_METRICS", false), "If true, skips exporting Go runtime metrics.") + excludePlayerMetrics := flag.Bool("exclude-player-metrics", envOrDefaultBool("A2S_EXPORTER_EXCLUDE_PLAYER_METRICS", false), "If true, exclude all `player_*` metrics. This option may be necessary for some servers.") + a2sOnlyMetrics := flag.Bool("a2s-only-metrics", envOrDefaultBool("A2S_EXPORTER_A2S_ONLY_METRICS", false), "If true, excludes Go runtime and promhttp metrics.") maxPacketSize := flag.Int("max-packet-size", envOrDefaultInt("A2S_EXPORTER_MAX_PACKET_SIZE", 1400), "Advanced option to set a non-standard max packet size of the A2S query server.") help := flag.Bool("h", false, "Show help.") version := flag.Bool("version", false, "Show build version.") @@ -62,7 +63,7 @@ func main() { clientOptions := []func(*a2s.Client) error{ a2s.SetMaxPacketSize(uint32(*maxPacketSize)), } - registry.MustRegister(collector.New(*namespace, *address, clientOptions...)) + registry.MustRegister(collector.New(*namespace, *address, *excludePlayerMetrics, clientOptions...)) // Set up http handler. handler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})