Add CSGO SRCDS input plugin (#8525)
This commit is contained in:
parent
786dca2d5e
commit
ee09a39de5
|
|
@ -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
1
go.mod
|
|
@ -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
2
go.sum
|
|
@ -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=
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue