package csgo import ( "encoding/json" "errors" "strconv" "strings" "sync" "time" "github.com/james4k/rcon" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/plugins/inputs" ) 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(acc, ss, requestServer)) }(server) } wg.Wait() return 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") } else { return resp, nil } }