chore(inputs.csgo): Migrate plugin to new maintained version of rcon (#14756)
This commit is contained in:
parent
bb27c696b3
commit
ea26973a34
|
|
@ -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
2
go.mod
|
|
@ -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
4
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue