master
go 127 lines 2.61 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package spigotmc
4
5 import (
6 "context"
7 "errors"
8 "fmt"
9 "regexp"
10 "strconv"
11 "strings"
12 )
13
14 const precision = 100
15
16 var (
17 reTPS = regexp.MustCompile(`(?ms)(?P<tps_1min>\d+.\d+),.*?(?P<tps_5min>\d+.\d+),.*?(?P<tps_15min>\d+.\d+).*?$.*?(?P<mem_used>\d+)/(?P<mem_alloc>\d+)[^:]+:\s*(?P<mem_max>\d+)`)
18 reList = regexp.MustCompile(`(?P<players>\d+)/?(?P<hidden_players>\d+)?.*?(?P<total_players>\d+)`)
19 reCleanResp = regexp.MustCompile(`§.`)
20 )
21
22 func (c *Collector) collect() (map[string]int64, error) {
23 if c.conn == nil {
24 conn, err := c.establishConn()
25 if err != nil {
26 return nil, err
27 }
28 c.conn = conn
29 }
30
31 mx := make(map[string]int64)
32
33 if err := c.collectTPS(mx); err != nil {
34 c.Cleanup(context.Background())
35 return nil, fmt.Errorf("failed to collect '%s': %v", cmdTPS, err)
36 }
37 if err := c.collectList(mx); err != nil {
38 c.Cleanup(context.Background())
39 return nil, fmt.Errorf("failed to collect '%s': %v", cmdList, err)
40 }
41
42 return mx, nil
43 }
44
45 func (c *Collector) collectTPS(mx map[string]int64) error {
46 resp, err := c.conn.queryTps()
47 if err != nil {
48 return err
49 }
50
51 c.Debugf("cmd '%s' response: %s", cmdTPS, resp)
52
53 if err := parseResponse(resp, reTPS, func(s string, f float64) {
54 switch {
55 case strings.HasPrefix(s, "tps"):
56 f *= precision
57 case strings.HasPrefix(s, "mem"):
58 f *= 1024 * 1024 // mb to bytes
59 }
60 mx[s] = int64(f)
61 }); err != nil {
62 return err
63 }
64
65 return nil
66 }
67
68 func (c *Collector) collectList(mx map[string]int64) error {
69 resp, err := c.conn.queryList()
70 if err != nil {
71 return err
72 }
73 c.Debugf("cmd '%s' response: %s", cmdList, resp)
74
75 var players int64
76 if err := parseResponse(resp, reList, func(s string, f float64) {
77 switch s {
78 case "players", "hidden_players":
79 players += int64(f)
80 }
81 }); err != nil {
82 return err
83 }
84
85 mx["players"] = players
86
87 return nil
88 }
89
90 func parseResponse(resp string, re *regexp.Regexp, fn func(string, float64)) error {
91 if resp == "" {
92 return errors.New("empty response")
93 }
94
95 resp = reCleanResp.ReplaceAllString(resp, "")
96
97 matches := re.FindStringSubmatch(resp)
98 if len(matches) == 0 {
99 return errors.New("regexp does not match")
100 }
101
102 for i, name := range re.SubexpNames() {
103 if name == "" || len(matches) <= i || matches[i] == "" {
104 continue
105 }
106 val := matches[i]
107
108 v, err := strconv.ParseFloat(val, 64)
109 if err != nil {
110 return fmt.Errorf("failed to parse key '%s' value '%s': %v", name, val, err)
111 }
112
113 fn(name, v)
114 }
115
116 return nil
117 }
118
119 func (c *Collector) establishConn() (rconConn, error) {
120 conn := c.newConn(c.Config)
121
122 if err := conn.connect(); err != nil {
123 return nil, err
124 }
125
126 return conn, nil
127 }