chore(inputs.csgo): Migrate plugin to new maintained version of rcon (#14756)

This commit is contained in:
Sven Rebhan 2024-02-12 17:36:54 +01:00 committed by GitHub
parent bb27c696b3
commit ea26973a34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 172 additions and 179 deletions

View File

@ -174,6 +174,7 @@ following works:
- github.com/googleapis/gax-go [BSD 3-Clause "New" or "Revised" License](https://github.com/googleapis/gax-go/blob/master/LICENSE)
- github.com/gopcua/opcua [MIT License](https://github.com/gopcua/opcua/blob/master/LICENSE)
- github.com/gophercloud/gophercloud [Apache License 2.0](https://github.com/gophercloud/gophercloud/blob/master/LICENSE)
- github.com/gorcon/rcon [MIT License](https://github.com/gorcon/rcon/blob/master/LICENSE)
- github.com/gorilla/mux [BSD 3-Clause "New" or "Revised" License](https://github.com/gorilla/mux/blob/master/LICENSE)
- github.com/gorilla/websocket [BSD 3-Clause "New" or "Revised" License](https://github.com/gorilla/websocket/blob/master/LICENSE)
- github.com/gosnmp/gosnmp [BSD 2-Clause "Simplified" License](https://github.com/gosnmp/gosnmp/blob/master/LICENSE)
@ -216,7 +217,6 @@ following works:
- github.com/jackc/pgx [MIT License](https://github.com/jackc/pgx/blob/master/LICENSE)
- github.com/jackc/puddle [MIT License](https://github.com/jackc/puddle/blob/master/LICENSE)
- github.com/jaegertracing/jaeger [Apache License 2.0](https://github.com/jaegertracing/jaeger/blob/master/LICENSE)
- github.com/james4k/rcon [MIT License](https://github.com/james4k/rcon/blob/master/LICENSE)
- github.com/jcmturner/aescts [Apache License 2.0](https://github.com/jcmturner/aescts/blob/master/LICENSE)
- github.com/jcmturner/dnsutils [Apache License 2.0](https://github.com/jcmturner/dnsutils/blob/master/LICENSE)
- github.com/jcmturner/gofork [BSD 3-Clause "New" or "Revised" License](https://github.com/jcmturner/gofork/blob/master/LICENSE)

2
go.mod
View File

@ -96,6 +96,7 @@ require (
github.com/google/uuid v1.5.0
github.com/gopcua/opcua v0.5.3
github.com/gophercloud/gophercloud v1.7.0
github.com/gorcon/rcon v1.3.5
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.1
github.com/gosnmp/gosnmp v1.37.0
@ -118,7 +119,6 @@ require (
github.com/jackc/pgio v1.0.0
github.com/jackc/pgtype v1.14.0
github.com/jackc/pgx/v4 v4.18.1
github.com/james4k/rcon v0.0.0-20120923215419-8fbb8268b60a
github.com/jeremywohl/flatten/v2 v2.0.0-20211013061545-07e4a09fb8e4
github.com/jhump/protoreflect v1.15.4
github.com/jmespath/go-jmespath v0.4.0

4
go.sum
View File

@ -1391,6 +1391,8 @@ github.com/gophercloud/gophercloud v1.7.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgz
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorcon/rcon v1.3.5 h1:YE/Vrw6R99uEP08wp0EjdPAP3Jwz/ys3J8qxI1nYoeU=
github.com/gorcon/rcon v1.3.5/go.mod h1:zR1qfKZttF8vAgH1NsP6CdpachOvLDq8jE64NboTpIM=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
@ -1570,8 +1572,6 @@ github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jaegertracing/jaeger v1.47.0 h1:XXxTMO+GxX930gxKWsg90rFr6RswkCRIW0AgWFnTYsg=
github.com/jaegertracing/jaeger v1.47.0/go.mod h1:mHU/OHFML51CijQql4+rLfgPOcIb9MhxOMn+RKQwrJc=
github.com/james4k/rcon v0.0.0-20120923215419-8fbb8268b60a h1:JxcWget6X/VfBMKxPIc28Jel37LGREut2fpV+ObkwJ0=
github.com/james4k/rcon v0.0.0-20120923215419-8fbb8268b60a/go.mod h1:1qNVsDcmNQDsAXYfUuF/Z0rtK5eT8x9D6Pi7S3PjXAg=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=

View File

@ -3,35 +3,22 @@ package csgo
import (
_ "embed"
"encoding/json"
"errors"
"strconv"
"strings"
"sync"
"time"
"github.com/james4k/rcon"
"github.com/gorcon/rcon"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
type statsData struct {
CPU float64 `json:"cpu"`
NetIn float64 `json:"net_in"`
NetOut float64 `json:"net_out"`
UptimeMinutes float64 `json:"uptime_minutes"`
Maps float64 `json:"maps"`
FPS float64 `json:"fps"`
Players float64 `json:"players"`
Sim float64 `json:"sv_ms"`
Variance float64 `json:"variance_ms"`
Tick float64 `json:"tick_ms"`
}
type CSGO struct {
Servers [][]string `toml:"servers"`
}
@ -40,143 +27,125 @@ func (*CSGO) SampleConfig() string {
return sampleConfig
}
func (s *CSGO) Init() error {
for _, server := range s.Servers {
if len(server) != 2 {
return errors.New("incorrect server config")
}
}
return nil
}
func (s *CSGO) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup
// Loop through each server and collect metrics
for _, server := range s.Servers {
wg.Add(1)
go func(ss []string) {
go func(addr, passwd string) {
defer wg.Done()
acc.AddError(s.gatherServer(acc, ss, requestServer))
}(server)
// Connect and send the request
client, err := rcon.Dial(addr, passwd)
if err != nil {
acc.AddError(err)
return
}
defer client.Close()
t := time.Now()
response, err := client.Execute("stats")
if err != nil {
acc.AddError(err)
return
}
// Generate the metric and add it to the accumulator
m, err := s.parseResponse(addr, response, t)
if err != nil {
acc.AddError(err)
return
}
acc.AddMetric(m)
}(server[0], server[1])
}
wg.Wait()
return nil
}
func (s *CSGO) parseResponse(addr, response string, t time.Time) (telegraf.Metric, error) {
rows := strings.Split(response, "\n")
if len(rows) < 2 {
return nil, errors.New("bad response")
}
// Parse the columns
columns := strings.Fields(rows[1])
if len(columns) != 10 {
return nil, errors.New("not enough columns")
}
cpu, err := strconv.ParseFloat(columns[0], 32)
if err != nil {
return nil, err
}
netIn, err := strconv.ParseFloat(columns[1], 64)
if err != nil {
return nil, err
}
netOut, err := strconv.ParseFloat(columns[2], 64)
if err != nil {
return nil, err
}
uptimeMinutes, err := strconv.ParseFloat(columns[3], 64)
if err != nil {
return nil, err
}
maps, err := strconv.ParseFloat(columns[4], 64)
if err != nil {
return nil, err
}
fps, err := strconv.ParseFloat(columns[5], 64)
if err != nil {
return nil, err
}
players, err := strconv.ParseFloat(columns[6], 64)
if err != nil {
return nil, err
}
svms, err := strconv.ParseFloat(columns[7], 64)
if err != nil {
return nil, err
}
msVar, err := strconv.ParseFloat(columns[8], 64)
if err != nil {
return nil, err
}
tick, err := strconv.ParseFloat(columns[9], 64)
if err != nil {
return nil, err
}
// Construct the metric
tags := map[string]string{"host": addr}
fields := map[string]interface{}{
"cpu": cpu,
"net_in": netIn,
"net_out": netOut,
"uptime_minutes": uptimeMinutes,
"maps": maps,
"fps": fps,
"players": players,
"sv_ms": svms,
"variance_ms": msVar,
"tick_ms": tick,
}
return metric.New("csgo", tags, fields, t, telegraf.Gauge), nil
}
func init() {
inputs.Add("csgo", func() telegraf.Input {
return &CSGO{}
})
}
func (s *CSGO) gatherServer(
acc telegraf.Accumulator,
server []string,
request func(string, string) (string, error),
) error {
if len(server) != 2 {
return errors.New("incorrect server config")
}
url, rconPw := server[0], server[1]
resp, err := request(url, rconPw)
if err != nil {
return err
}
rows := strings.Split(resp, "\n")
if len(rows) < 2 {
return errors.New("bad response")
}
fields := strings.Fields(rows[1])
if len(fields) != 10 {
return errors.New("bad response")
}
cpu, err := strconv.ParseFloat(fields[0], 32)
if err != nil {
return err
}
netIn, err := strconv.ParseFloat(fields[1], 64)
if err != nil {
return err
}
netOut, err := strconv.ParseFloat(fields[2], 64)
if err != nil {
return err
}
uptimeMinutes, err := strconv.ParseFloat(fields[3], 64)
if err != nil {
return err
}
maps, err := strconv.ParseFloat(fields[4], 64)
if err != nil {
return err
}
fps, err := strconv.ParseFloat(fields[5], 64)
if err != nil {
return err
}
players, err := strconv.ParseFloat(fields[6], 64)
if err != nil {
return err
}
svms, err := strconv.ParseFloat(fields[7], 64)
if err != nil {
return err
}
msVar, err := strconv.ParseFloat(fields[8], 64)
if err != nil {
return err
}
tick, err := strconv.ParseFloat(fields[9], 64)
if err != nil {
return err
}
now := time.Now()
stats := statsData{
CPU: cpu,
NetIn: netIn,
NetOut: netOut,
UptimeMinutes: uptimeMinutes,
Maps: maps,
FPS: fps,
Players: players,
Sim: svms,
Variance: msVar,
Tick: tick,
}
tags := map[string]string{
"host": url,
}
var statsMap map[string]interface{}
marshalled, err := json.Marshal(stats)
if err != nil {
return err
}
err = json.Unmarshal(marshalled, &statsMap)
if err != nil {
return err
}
acc.AddGauge("csgo", statsMap, tags, now)
return nil
}
func requestServer(url string, rconPw string) (string, error) {
remoteConsole, err := rcon.Dial(url, rconPw)
if err != nil {
return "", err
}
defer remoteConsole.Close()
reqID, err := remoteConsole.Write("stats")
if err != nil {
return "", err
}
resp, respReqID, err := remoteConsole.Read()
if err != nil {
return "", err
} else if reqID != respReqID {
return "", errors.New("response/request mismatch")
}
return resp, nil
}

View File

@ -2,54 +2,78 @@ package csgo
import (
"testing"
"time"
"github.com/gorcon/rcon"
"github.com/gorcon/rcon/rcontest"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/testutil"
)
const testInput = `CPU NetIn NetOut Uptime Maps FPS Players Svms +-ms ~tick
func TestCPUStats(t *testing.T) {
// Define the input
const input = `CPU NetIn NetOut Uptime Maps FPS Players Svms +-ms ~tick
10.0 1.2 3.4 100 1 120.20 15 5.23 0.01 0.02`
var (
expectedOutput = statsData{
10.0, 1.2, 3.4, 100.0, 1, 120.20, 15, 5.23, 0.01, 0.02,
}
)
// Start the mockup server
server := rcontest.NewUnstartedServer()
server.Settings.Password = "password"
server.SetAuthHandler(func(c *rcontest.Context) {
if c.Request().Body() == c.Server().Settings.Password {
pkg := rcon.NewPacket(rcon.SERVERDATA_AUTH_RESPONSE, c.Request().ID, "")
_, _ = pkg.WriteTo(c.Conn())
} else {
pkg := rcon.NewPacket(rcon.SERVERDATA_AUTH_RESPONSE, -1, string([]byte{0x00}))
_, _ = pkg.WriteTo(c.Conn())
}
})
server.SetCommandHandler(func(c *rcontest.Context) {
pkg := rcon.NewPacket(rcon.SERVERDATA_RESPONSE_VALUE, c.Request().ID, input)
_, _ = pkg.WriteTo(c.Conn())
})
server.Start()
defer server.Close()
func TestCPUStats(t *testing.T) {
c := NewCSGOStats()
var acc testutil.Accumulator
err := c.gatherServer(&acc, c.Servers[0], requestMock)
if err != nil {
t.Error(err)
}
if !acc.HasMeasurement("csgo") {
t.Errorf("acc.HasMeasurement: expected csgo")
}
require.Equal(t, "1.2.3.4:1234", acc.Metrics[0].Tags["host"])
require.Equal(t, expectedOutput.CPU, acc.Metrics[0].Fields["cpu"])
require.Equal(t, expectedOutput.NetIn, acc.Metrics[0].Fields["net_in"])
require.Equal(t, expectedOutput.NetOut, acc.Metrics[0].Fields["net_out"])
require.Equal(t, expectedOutput.UptimeMinutes, acc.Metrics[0].Fields["uptime_minutes"])
require.Equal(t, expectedOutput.Maps, acc.Metrics[0].Fields["maps"])
require.Equal(t, expectedOutput.FPS, acc.Metrics[0].Fields["fps"])
require.Equal(t, expectedOutput.Players, acc.Metrics[0].Fields["players"])
require.Equal(t, expectedOutput.Sim, acc.Metrics[0].Fields["sv_ms"])
require.Equal(t, expectedOutput.Variance, acc.Metrics[0].Fields["variance_ms"])
require.Equal(t, expectedOutput.Tick, acc.Metrics[0].Fields["tick_ms"])
}
func requestMock(_ string, _ string) (string, error) {
return testInput, nil
}
func NewCSGOStats() *CSGO {
return &CSGO{
// Setup the plugin
plugin := &CSGO{
Servers: [][]string{
{"1.2.3.4:1234", "password"},
{server.Addr(), "password"},
},
}
require.NoError(t, plugin.Init())
// Define expected result
expected := []telegraf.Metric{
metric.New(
"csgo",
map[string]string{
"host": server.Addr(),
},
map[string]interface{}{
"cpu": 10.0,
"fps": 120.2,
"maps": 1.0,
"net_in": 1.2,
"net_out": 3.4,
"players": 15.0,
"sv_ms": 5.23,
"tick_ms": 0.02,
"uptime_minutes": 100.0,
"variance_ms": 0.01,
},
time.Unix(0, 0),
telegraf.Gauge,
),
}
// Gather data
var acc testutil.Accumulator
require.NoError(t, acc.GatherError(plugin.Gather))
// Test the result
actual := acc.GetTelegrafMetrics()
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime())
}