master
go 219 lines 5.64 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package pika
4
5 import (
6 "bufio"
7 "regexp"
8 "strconv"
9 "strings"
10
11 "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
12 )
13
14 // https://github.com/Qihoo360/pika/blob/master/src/pika_admin.cc
15 // https://github.com/Qihoo360/pika/blob/a0dbdcf5897dd7800ba8a4d1eafce1595619ddc8/src/pika_admin.cc#L694-L710
16
17 const (
18 infoSectionServer = "# Server"
19 infoSectionData = "# Data"
20 infoSectionClients = "# Clients"
21 infoSectionStats = "# Stats"
22 infoSectionCommandExecCount = "# Command_Exec_Count"
23 infoSectionCPU = "# CPU"
24 infoSectionReplMaster = "# Replication(MASTER)"
25 infoSectionReplSlave = "# Replication(SLAVE)"
26 infoSectionReplMasterSlave = "# Replication(Master && SLAVE)"
27 infoSectionKeyspace = "# Keyspace"
28 )
29
30 var infoSections = map[string]struct{}{
31 infoSectionServer: {},
32 infoSectionData: {},
33 infoSectionClients: {},
34 infoSectionStats: {},
35 infoSectionCommandExecCount: {},
36 infoSectionCPU: {},
37 infoSectionReplMaster: {},
38 infoSectionReplSlave: {},
39 infoSectionReplMasterSlave: {},
40 infoSectionKeyspace: {},
41 }
42
43 func isInfoSection(line string) bool { _, ok := infoSections[line]; return ok }
44
45 func (c *Collector) collectInfo(ms map[string]int64, info string) {
46 var curSection string
47
48 sc := bufio.NewScanner(strings.NewReader(info))
49 for sc.Scan() {
50 line := strings.TrimSpace(sc.Text())
51 if len(line) == 0 {
52 curSection = ""
53 continue
54 }
55 if strings.HasPrefix(line, "#") {
56 if isInfoSection(line) {
57 curSection = line
58 }
59 continue
60 }
61
62 field, value, ok := parseProperty(line)
63 if !ok {
64 continue
65 }
66
67 switch curSection {
68 case infoSectionCommandExecCount:
69 c.collectInfoCommandExecCountProperty(ms, field, value)
70 case infoSectionKeyspace:
71 c.collectInfoKeyspaceProperty(ms, field, value)
72 default:
73 collectNumericValue(ms, field, value)
74 }
75 }
76 }
77
78 var reKeyspaceValue = regexp.MustCompile(`^(.+)_keys=(\d+), expires=(\d+), invalid_keys=(\d+)`)
79
80 func (c *Collector) collectInfoKeyspaceProperty(ms map[string]int64, field, value string) {
81 match := reKeyspaceValue.FindStringSubmatch(value)
82 if match == nil {
83 return
84 }
85
86 dataType, keys, expires, invalid := strings.ToLower(match[1]), match[2], match[3], match[4]
87 collectNumericValue(ms, field+"_"+dataType+"_keys", keys)
88 collectNumericValue(ms, field+"_"+dataType+"_expires_keys", expires)
89 collectNumericValue(ms, field+"_"+dataType+"_invalid_keys", invalid)
90
91 if !c.collectedDbs[field] {
92 c.collectedDbs[field] = true
93 c.addDbToKeyspaceCharts(field)
94 }
95 }
96
97 func (c *Collector) collectInfoCommandExecCountProperty(ms map[string]int64, field, value string) {
98 collectNumericValue(ms, "cmd_"+field+"_calls", value)
99
100 if !c.collectedCommands[field] {
101 c.collectedCommands[field] = true
102 c.addCmdToCommandsCharts(field)
103 }
104 }
105
106 func (c *Collector) addCmdToCommandsCharts(cmd string) {
107 c.addDimToChart(chartCommandsCalls.ID, &collectorapi.Dim{
108 ID: "cmd_" + cmd + "_calls",
109 Name: cmd,
110 Algo: collectorapi.Incremental,
111 })
112 }
113
114 func (c *Collector) addDbToKeyspaceCharts(db string) {
115 c.addDimToChart(chartDbStringsKeys.ID, &collectorapi.Dim{
116 ID: db + "_strings_keys",
117 Name: db,
118 })
119 c.addDimToChart(chartDbStringsExpiresKeys.ID, &collectorapi.Dim{
120 ID: db + "_strings_expires_keys",
121 Name: db,
122 })
123 c.addDimToChart(chartDbStringsInvalidKeys.ID, &collectorapi.Dim{
124 ID: db + "_strings_invalid_keys",
125 Name: db,
126 })
127
128 c.addDimToChart(chartDbHashesKeys.ID, &collectorapi.Dim{
129 ID: db + "_hashes_keys",
130 Name: db,
131 })
132 c.addDimToChart(chartDbHashesExpiresKeys.ID, &collectorapi.Dim{
133 ID: db + "_hashes_expires_keys",
134 Name: db,
135 })
136 c.addDimToChart(chartDbHashesInvalidKeys.ID, &collectorapi.Dim{
137 ID: db + "_hashes_invalid_keys",
138 Name: db,
139 })
140
141 c.addDimToChart(chartDbListsKeys.ID, &collectorapi.Dim{
142 ID: db + "_lists_keys",
143 Name: db,
144 })
145 c.addDimToChart(chartDbListsExpiresKeys.ID, &collectorapi.Dim{
146 ID: db + "_lists_expires_keys",
147 Name: db,
148 })
149 c.addDimToChart(chartDbListsInvalidKeys.ID, &collectorapi.Dim{
150 ID: db + "_lists_invalid_keys",
151 Name: db,
152 })
153
154 c.addDimToChart(chartDbZsetsKeys.ID, &collectorapi.Dim{
155 ID: db + "_zsets_keys",
156 Name: db,
157 })
158 c.addDimToChart(chartDbZsetsExpiresKeys.ID, &collectorapi.Dim{
159 ID: db + "_zsets_expires_keys",
160 Name: db,
161 })
162 c.addDimToChart(chartDbZsetsInvalidKeys.ID, &collectorapi.Dim{
163 ID: db + "_zsets_invalid_keys",
164 Name: db,
165 })
166
167 c.addDimToChart(chartDbSetsKeys.ID, &collectorapi.Dim{
168 ID: db + "_sets_keys",
169 Name: db,
170 })
171 c.addDimToChart(chartDbSetsExpiresKeys.ID, &collectorapi.Dim{
172 ID: db + "_sets_expires_keys",
173 Name: db,
174 })
175 c.addDimToChart(chartDbSetsInvalidKeys.ID, &collectorapi.Dim{
176 ID: db + "_sets_invalid_keys",
177 Name: db,
178 })
179 }
180
181 func (c *Collector) addDimToChart(chartID string, dim *collectorapi.Dim) {
182 chart := c.Charts().Get(chartID)
183 if chart == nil {
184 c.Warningf("error on adding '%s' dimension: can not find '%s' chart", dim.ID, chartID)
185 return
186 }
187 if err := chart.AddDim(dim); err != nil {
188 c.Warning(err)
189 return
190 }
191 chart.MarkNotCreated()
192 }
193
194 func parseProperty(prop string) (field, value string, ok bool) {
195 var sep byte
196 if strings.HasPrefix(prop, "db") {
197 sep = ' '
198 } else {
199 sep = ':'
200 }
201 i := strings.IndexByte(prop, sep)
202 if i == -1 {
203 return "", "", false
204 }
205 field, value = prop[:i], prop[i+1:]
206 return field, value, field != "" && value != ""
207 }
208
209 func collectNumericValue(ms map[string]int64, field, value string) {
210 v, err := strconv.ParseFloat(value, 64)
211 if err != nil {
212 return
213 }
214 if strings.IndexByte(value, '.') == -1 {
215 ms[field] = int64(v)
216 } else {
217 ms[field] = int64(v * precision)
218 }
219 }