Add CSGO SRCDS input plugin (#8525)

This commit is contained in:
oofdog 2021-03-03 14:05:14 -05:00 committed by GitHub
parent 786dca2d5e
commit ee09a39de5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 288 additions and 0 deletions

View File

@ -99,6 +99,7 @@ following works:
- github.com/influxdata/wlog [MIT License](https://github.com/influxdata/wlog/blob/master/LICENSE) - github.com/influxdata/wlog [MIT License](https://github.com/influxdata/wlog/blob/master/LICENSE)
- github.com/jackc/pgx [MIT License](https://github.com/jackc/pgx/blob/master/LICENSE) - github.com/jackc/pgx [MIT License](https://github.com/jackc/pgx/blob/master/LICENSE)
- github.com/jaegertracing/jaeger [Apache License 2.0](https://github.com/jaegertracing/jaeger/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/gofork [BSD 3-Clause "New" or "Revised" License](https://github.com/jcmturner/gofork/blob/master/LICENSE) - github.com/jcmturner/gofork [BSD 3-Clause "New" or "Revised" License](https://github.com/jcmturner/gofork/blob/master/LICENSE)
- github.com/jmespath/go-jmespath [Apache License 2.0](https://github.com/jmespath/go-jmespath/blob/master/LICENSE) - github.com/jmespath/go-jmespath [Apache License 2.0](https://github.com/jmespath/go-jmespath/blob/master/LICENSE)
- github.com/jpillora/backoff [MIT License](https://github.com/jpillora/backoff/blob/master/LICENSE) - github.com/jpillora/backoff [MIT License](https://github.com/jpillora/backoff/blob/master/LICENSE)

1
go.mod
View File

@ -85,6 +85,7 @@ require (
github.com/influxdata/wlog v0.0.0-20160411224016-7c63b0a71ef8 github.com/influxdata/wlog v0.0.0-20160411224016-7c63b0a71ef8
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect
github.com/jackc/pgx v3.6.0+incompatible github.com/jackc/pgx v3.6.0+incompatible
github.com/james4k/rcon v0.0.0-20120923215419-8fbb8268b60a
github.com/kardianos/service v1.0.0 github.com/kardianos/service v1.0.0
github.com/karrick/godirwalk v1.16.1 github.com/karrick/godirwalk v1.16.1
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51

2
go.sum
View File

@ -406,6 +406,8 @@ github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGU
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
github.com/jackc/pgx v3.6.0+incompatible h1:bJeo4JdVbDAW8KB2m8XkFeo8CPipREoG37BwEoKGz+Q= github.com/jackc/pgx v3.6.0+incompatible h1:bJeo4JdVbDAW8KB2m8XkFeo8CPipREoG37BwEoKGz+Q=
github.com/jackc/pgx v3.6.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jackc/pgx v3.6.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
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/jaegertracing/jaeger v1.15.1 h1:7QzNAXq+4ko9GtCjozDNAp2uonoABu+B2Rk94hjQcp4= github.com/jaegertracing/jaeger v1.15.1 h1:7QzNAXq+4ko9GtCjozDNAp2uonoABu+B2Rk94hjQcp4=
github.com/jaegertracing/jaeger v1.15.1/go.mod h1:LUWPSnzNPGRubM8pk0inANGitpiMOOxihXx0+53llXI= github.com/jaegertracing/jaeger v1.15.1/go.mod h1:LUWPSnzNPGRubM8pk0inANGitpiMOOxihXx0+53llXI=
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=

View File

@ -29,6 +29,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/couchbase" _ "github.com/influxdata/telegraf/plugins/inputs/couchbase"
_ "github.com/influxdata/telegraf/plugins/inputs/couchdb" _ "github.com/influxdata/telegraf/plugins/inputs/couchdb"
_ "github.com/influxdata/telegraf/plugins/inputs/cpu" _ "github.com/influxdata/telegraf/plugins/inputs/cpu"
_ "github.com/influxdata/telegraf/plugins/inputs/csgo"
_ "github.com/influxdata/telegraf/plugins/inputs/dcos" _ "github.com/influxdata/telegraf/plugins/inputs/dcos"
_ "github.com/influxdata/telegraf/plugins/inputs/directory_monitor" _ "github.com/influxdata/telegraf/plugins/inputs/directory_monitor"
_ "github.com/influxdata/telegraf/plugins/inputs/disk" _ "github.com/influxdata/telegraf/plugins/inputs/disk"

View File

@ -0,0 +1,37 @@
# CSGO Input Plugin
The `csgo` plugin gather metrics from CSGO servers.
#### Configuration
```toml
[[inputs.csgo]]
## Specify servers using the following format:
## servers = [
## ["ip1:port1", "rcon_password1"],
## ["ip2:port2", "rcon_password2"],
## ]
#
## If no servers are specified, no data will be collected
servers = []
```
### Metrics
The plugin retrieves the output of the `stats` command that is executed via rcon.
If no servers are specified, no data will be collected
- csgo
- tags:
- host
- fields:
- cpu (float)
- net_in (float)
- net_out (float)
- uptime_minutes (float)
- maps (float)
- fps (float)
- players (float)
- sv_ms (float)
- variance_ms (float)
- tick_ms (float)

192
plugins/inputs/csgo/csgo.go Normal file
View File

@ -0,0 +1,192 @@
package csgo
import (
"encoding/json"
"errors"
"strconv"
"strings"
"sync"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
"github.com/james4k/rcon"
)
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"`
}
func (_ *CSGO) Description() string {
return "Fetch metrics from a CSGO SRCDS"
}
var sampleConfig = `
## Specify servers using the following format:
## servers = [
## ["ip1:port1", "rcon_password1"],
## ["ip2:port2", "rcon_password2"],
## ]
#
## If no servers are specified, no data will be collected
servers = []
`
func (_ *CSGO) SampleConfig() string {
return sampleConfig
}
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) {
defer wg.Done()
acc.AddError(s.gatherServer(ss, requestServer, acc))
}(server)
}
wg.Wait()
return nil
}
func init() {
inputs.Add("csgo", func() telegraf.Input {
return &CSGO{}
})
}
func (s *CSGO) gatherServer(
server []string,
request func(string, string) (string, error),
acc telegraf.Accumulator) 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")
} else {
return resp, nil
}
}

View File

@ -0,0 +1,54 @@
package csgo
import (
"github.com/influxdata/telegraf/testutil"
"testing"
"github.com/stretchr/testify/assert"
)
const testInput = `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,
}
)
func TestCPUStats(t *testing.T) {
c := NewCSGOStats()
var acc testutil.Accumulator
err := c.gatherServer(c.Servers[0], requestMock, &acc)
if err != nil {
t.Error(err)
}
if !acc.HasMeasurement("csgo") {
t.Errorf("acc.HasMeasurement: expected csgo")
}
assert.Equal(t, "1.2.3.4:1234", acc.Metrics[0].Tags["host"])
assert.Equal(t, expectedOutput.CPU, acc.Metrics[0].Fields["cpu"])
assert.Equal(t, expectedOutput.NetIn, acc.Metrics[0].Fields["net_in"])
assert.Equal(t, expectedOutput.NetOut, acc.Metrics[0].Fields["net_out"])
assert.Equal(t, expectedOutput.UptimeMinutes, acc.Metrics[0].Fields["uptime_minutes"])
assert.Equal(t, expectedOutput.Maps, acc.Metrics[0].Fields["maps"])
assert.Equal(t, expectedOutput.FPS, acc.Metrics[0].Fields["fps"])
assert.Equal(t, expectedOutput.Players, acc.Metrics[0].Fields["players"])
assert.Equal(t, expectedOutput.Sim, acc.Metrics[0].Fields["sv_ms"])
assert.Equal(t, expectedOutput.Variance, acc.Metrics[0].Fields["variance_ms"])
assert.Equal(t, expectedOutput.Tick, acc.Metrics[0].Fields["tick_ms"])
}
func requestMock(_ string, _ string) (string, error) {
return testInput, nil
}
func NewCSGOStats() *CSGO {
return &CSGO{
Servers: [][]string{
{"1.2.3.4:1234", "password"},
},
}
}