Skip to content

Commit

Permalink
New argument --exclude-player-metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
armsnyder committed Dec 11, 2022
1 parent 9b46b89 commit 441797c
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 15 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
31 changes: 20 additions & 11 deletions internal/collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
}
}

Expand All @@ -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() {
Expand All @@ -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}
Expand All @@ -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.
Expand All @@ -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
Expand Down
66 changes: 64 additions & 2 deletions internal/collector/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down Expand Up @@ -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{})
Expand Down

0 comments on commit 441797c

Please sign in to comment.