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/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/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/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)
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -85,6 +85,7 @@ require (
|
|||
github.com/influxdata/wlog v0.0.0-20160411224016-7c63b0a71ef8
|
||||
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect
|
||||
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/karrick/godirwalk v1.16.1
|
||||
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/pgx v3.6.0+incompatible h1:bJeo4JdVbDAW8KB2m8XkFeo8CPipREoG37BwEoKGz+Q=
|
||||
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/go.mod h1:LUWPSnzNPGRubM8pk0inANGitpiMOOxihXx0+53llXI=
|
||||
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/couchdb"
|
||||
_ "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/directory_monitor"
|
||||
_ "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