• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

oliver006 / redis_exporter / 17172001870

23 Aug 2025 05:58AM UTC coverage: 84.921% (-0.9%) from 85.87%
17172001870

Pull #1028

github

web-flow
Merge 891f7f01e into 7632b7b20
Pull Request #1028: sirupsen/log --> log/slog

121 of 249 new or added lines in 18 files covered. (48.59%)

6 existing lines in 1 file now uncovered.

2568 of 3024 relevant lines covered (84.92%)

13254.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.96
/exporter/info.go
1
package exporter
2

3
import (
4
        "errors"
5
        "fmt"
6
        "log/slog"
7
        "regexp"
8
        "strconv"
9
        "strings"
10
        "time"
11

12
        "github.com/prometheus/client_golang/prometheus"
13
)
14

15
// precompiled regexps
16

17
// keydb multimaster
18
/*
19
master_host:kdb0.server.local
20
master_port:6377
21
master_1_host:kdb1.server.local
22
master_1_port:6377
23
*/
24
var reMasterHost = regexp.MustCompile(`^master(_[0-9]+)?_host`)
25
var reMasterPort = regexp.MustCompile(`^master(_[0-9]+)?_port`)
26
var reMasterLinkStatus = regexp.MustCompile(`^master(_[0-9]+)?_link_status`)
27

28
// info fieldKey:fieldValue -> metric redis_fieldKey{master_host, master_port} fieldValue
29
var reMasterDirect = regexp.MustCompile(`^(master(_[0-9]+)?_(last_io_seconds_ago|sync_in_progress)|slave_repl_offset)`)
30

31
// numbered slaves
32
/*
33
slave0:ip=10.254.11.1,port=6379,state=online,offset=1751844676,lag=0
34
slave1:ip=10.254.11.2,port=6379,state=online,offset=1751844222,lag=0
35
*/
36

37
var reSlave = regexp.MustCompile(`^slave\d+`)
38

39
const (
40
        InstanceRoleSlave = "slave"
41
)
42

43
func extractVal(s string) (val float64, err error) {
114,061✔
44
        split := strings.Split(s, "=")
114,061✔
45
        if len(split) != 2 {
114,063✔
46
                return 0, fmt.Errorf("nope")
2✔
47
        }
2✔
48
        val, err = strconv.ParseFloat(split[1], 64)
114,059✔
49
        if err != nil {
114,070✔
50
                return 0, fmt.Errorf("nope")
11✔
51
        }
11✔
52
        return
114,048✔
53
}
54

55
func extractPercentileVal(s string) (percentile float64, val float64, err error) {
57,216✔
56
        split := strings.Split(s, "=")
57,216✔
57
        if len(split) != 2 {
57,217✔
58
                return
1✔
59
        }
1✔
60
        percentile, err = strconv.ParseFloat(split[0][1:], 64)
57,215✔
61
        if err != nil {
57,215✔
62
                return
×
63
        }
×
64
        val, err = strconv.ParseFloat(split[1], 64)
57,215✔
65
        return
57,215✔
66
}
67

68
// returns the role of the instance we're scraping (master or slave)
69
func (e *Exporter) extractInfoMetrics(ch chan<- prometheus.Metric, info string, dbCount int) string {
2,096✔
70
        keyValues := map[string]string{}
2,096✔
71
        handledDBs := map[string]bool{}
2,096✔
72
        cmdCount := map[string]uint64{}
2,096✔
73
        cmdSum := map[string]float64{}
2,096✔
74
        cmdLatencyMap := map[string]map[float64]float64{}
2,096✔
75

2,096✔
76
        fieldClass := ""
2,096✔
77
        lines := strings.Split(info, "\n")
2,096✔
78
        masterHost := ""
2,096✔
79
        masterPort := ""
2,096✔
80
        for _, line := range lines {
452,152✔
81
                line = strings.TrimSpace(line)
450,056✔
82
                slog.Debug("info", "line", line)
450,056✔
83
                if len(line) > 0 && strings.HasPrefix(line, "# ") {
475,119✔
84
                        fieldClass = line[2:]
25,063✔
85
                        slog.Debug("set fieldClass", "fieldClass", fieldClass)
25,063✔
86
                        continue
25,063✔
87
                }
88

89
                if (len(line) < 2) || (!strings.Contains(line, ":")) {
450,230✔
90
                        continue
25,237✔
91
                }
92

93
                split := strings.SplitN(line, ":", 2)
399,756✔
94
                fieldKey := split[0]
399,756✔
95
                fieldValue := split[1]
399,756✔
96

399,756✔
97
                keyValues[fieldKey] = fieldValue
399,756✔
98

399,756✔
99
                if reMasterHost.MatchString(fieldKey) {
400,015✔
100
                        masterHost = fieldValue
259✔
101
                }
259✔
102

103
                if reMasterPort.MatchString(fieldKey) {
400,015✔
104
                        masterPort = fieldValue
259✔
105
                }
259✔
106

107
                switch fieldClass {
399,756✔
108

109
                case "Replication":
24,761✔
110
                        if ok := e.handleMetricsReplication(ch, masterHost, masterPort, fieldKey, fieldValue); ok {
26,139✔
111
                                continue
1,378✔
112
                        }
113

114
                case "Server":
48,724✔
115
                        e.handleMetricsServer(ch, fieldKey, fieldValue)
48,724✔
116

117
                case "Commandstats":
28,610✔
118
                        cmd, calls, usecsTotal := e.handleMetricsCommandStats(ch, fieldKey, fieldValue)
28,610✔
119
                        cmdCount[cmd] = uint64(calls)
28,610✔
120
                        cmdSum[cmd] = usecsTotal
28,610✔
121
                        continue
28,610✔
122

123
                case "Latencystats":
19,070✔
124
                        e.handleMetricsLatencyStats(fieldKey, fieldValue, cmdLatencyMap)
19,070✔
125
                        continue
19,070✔
126

127
                case "Errorstats":
977✔
128
                        e.handleMetricsErrorStats(ch, fieldKey, fieldValue)
977✔
129
                        continue
977✔
130

131
                case "Keyspace":
925✔
132
                        if keysTotal, keysEx, avgTTL, keysCached, ok := parseDBKeyspaceString(fieldKey, fieldValue); ok {
1,850✔
133
                                dbName := fieldKey
925✔
134

925✔
135
                                e.registerConstMetricGauge(ch, "db_keys", keysTotal, dbName)
925✔
136
                                e.registerConstMetricGauge(ch, "db_keys_expiring", keysEx, dbName)
925✔
137
                                if keysCached > -1 {
939✔
138
                                        e.registerConstMetricGauge(ch, "db_keys_cached", keysCached, dbName)
14✔
139
                                }
14✔
140
                                if avgTTL > -1 {
1,850✔
141
                                        e.registerConstMetricGauge(ch, "db_avg_ttl_seconds", avgTTL, dbName)
925✔
142
                                }
925✔
143
                                handledDBs[dbName] = true
925✔
144
                                continue
925✔
145
                        }
146

147
                case "Sentinel":
12✔
148
                        e.handleMetricsSentinel(ch, fieldKey, fieldValue)
12✔
149
                }
150

151
                if !e.includeMetric(fieldKey) {
465,256✔
152
                        continue
116,460✔
153
                }
154

155
                e.parseAndRegisterConstMetric(ch, fieldKey, fieldValue)
232,336✔
156
        }
157

158
        // To be able to generate the latency summaries we need the count and sum that we get
159
        // from #Commandstats processing and the percentile info that we get from the #Latencystats processing
160
        e.generateCommandLatencySummaries(ch, cmdLatencyMap, cmdCount, cmdSum)
2,096✔
161

2,096✔
162
        if e.options.InclMetricsForEmptyDatabases {
2,097✔
163
                for dbIndex := 0; dbIndex < dbCount; dbIndex++ {
17✔
164
                        dbName := "db" + strconv.Itoa(dbIndex)
16✔
165
                        if _, exists := handledDBs[dbName]; !exists {
32✔
166
                                e.registerConstMetricGauge(ch, "db_keys", 0, dbName)
16✔
167
                                e.registerConstMetricGauge(ch, "db_keys_expiring", 0, dbName)
16✔
168
                        }
16✔
169
                }
170
        }
171

172
        instanceRole := keyValues["role"]
2,096✔
173

2,096✔
174
        lbls := []string{"role", "redis_version", "redis_build_id", "redis_mode", "os", "maxmemory_policy", "tcp_port", "run_id", "process_id", "master_replid"}
2,096✔
175
        lblVals := []string{
2,096✔
176
                instanceRole,
2,096✔
177
                keyValues["redis_version"],
2,096✔
178
                keyValues["redis_build_id"],
2,096✔
179
                keyValues["redis_mode"],
2,096✔
180
                keyValues["os"],
2,096✔
181
                keyValues["maxmemory_policy"],
2,096✔
182
                keyValues["tcp_port"],
2,096✔
183
                keyValues["run_id"],
2,096✔
184
                keyValues["process_id"],
2,096✔
185
                keyValues["master_replid"],
2,096✔
186
        }
2,096✔
187
        if valkeyVersion, ok := keyValues["valkey_version"]; ok {
2,921✔
188
                lbls = append(lbls, "valkey_version")
825✔
189
                lblVals = append(lblVals, valkeyVersion)
825✔
190
        }
825✔
191
        if valkeyReleaseStage, ok := keyValues["valkey_release_stage"]; ok {
2,763✔
192
                lbls = append(lbls, "valkey_release_stage")
667✔
193
                lblVals = append(lblVals, valkeyReleaseStage)
667✔
194
        }
667✔
195

196
        e.createMetricDescription("instance_info", lbls)
2,096✔
197
        e.registerConstMetricGauge(ch, "instance_info", 1, lblVals...)
2,096✔
198

2,096✔
199
        if instanceRole == InstanceRoleSlave {
2,232✔
200
                e.registerConstMetricGauge(ch, "slave_info", 1,
136✔
201
                        keyValues["master_host"],
136✔
202
                        keyValues["master_port"],
136✔
203
                        keyValues["slave_read_only"])
136✔
204
        }
136✔
205

206
        return instanceRole
2,096✔
207
}
208

209
func (e *Exporter) generateCommandLatencySummaries(ch chan<- prometheus.Metric, cmdLatencyMap map[string]map[float64]float64, cmdCount map[string]uint64, cmdSum map[string]float64) {
2,096✔
210
        for cmd, latencyMap := range cmdLatencyMap {
21,166✔
211
                count, okCount := cmdCount[cmd]
19,070✔
212
                sum, okSum := cmdSum[cmd]
19,070✔
213
                if okCount && okSum {
38,140✔
214
                        e.createMetricDescription("latency_percentiles_usec", []string{"cmd"})
19,070✔
215
                        e.registerConstSummary(ch, "latency_percentiles_usec", count, sum, latencyMap, cmd)
19,070✔
216
                }
19,070✔
217
        }
218
}
219

220
func (e *Exporter) extractClusterInfoMetrics(ch chan<- prometheus.Metric, info string) {
321✔
221
        lines := strings.Split(info, "\r\n")
321✔
222

321✔
223
        for _, line := range lines {
6,099✔
224
                slog.Debug("info", "line", line)
5,778✔
225

5,778✔
226
                split := strings.Split(line, ":")
5,778✔
227
                if len(split) != 2 {
6,099✔
228
                        continue
321✔
229
                }
230
                fieldKey := split[0]
5,457✔
231
                fieldValue := split[1]
5,457✔
232

5,457✔
233
                if !e.includeMetric(fieldKey) {
5,778✔
234
                        continue
321✔
235
                }
236
                e.parseAndRegisterConstMetric(ch, fieldKey, fieldValue)
5,136✔
237
        }
238
}
239

240
/*
241
valid example: db0:keys=1,expires=0,avg_ttl=0,cached_keys=0
242
*/
243
func parseDBKeyspaceString(inputKey string, inputVal string) (keysTotal float64, keysExpiringTotal float64, avgTTL float64, keysCachedTotal float64, ok bool) {
939✔
244
        slog.Debug("parseDBKeyspaceString", "inputKey", inputKey, "inputVal", inputVal)
939✔
245

939✔
246
        if !strings.HasPrefix(inputKey, "db") {
941✔
247
                slog.Debug("parseDBKeyspaceString inputKey not starting with 'db'", "inputKey", inputKey)
2✔
248
                return
2✔
249
        }
2✔
250

251
        split := strings.Split(inputVal, ",")
937✔
252
        if len(split) < 2 || len(split) > 4 {
939✔
253
                slog.Debug("parseDBKeyspaceString strings.Split(inputVal) invalid", "split", split)
2✔
254
                return
2✔
255
        }
2✔
256

257
        var err error
935✔
258
        if keysTotal, err = extractVal(split[0]); err != nil {
938✔
259
                slog.Debug("parseDBKeyspaceString extractVal(split[0]) invalid", "error", err)
3✔
260
                return
3✔
261
        }
3✔
262
        if keysExpiringTotal, err = extractVal(split[1]); err != nil {
933✔
263
                slog.Debug("parseDBKeyspaceString extractVal(split[1]) invalid", "error", err)
1✔
264
                return
1✔
265
        }
1✔
266

267
        avgTTL = -1
931✔
268
        if len(split) > 2 {
1,862✔
269
                if avgTTL, err = extractVal(split[2]); err != nil {
933✔
270
                        slog.Debug("parseDBKeyspaceString extractVal(split[2]) invalid", "error", err)
2✔
271
                        return
2✔
272
                }
2✔
273
                avgTTL /= 1000
929✔
274
        }
275

276
        keysCachedTotal = -1
929✔
277
        if len(split) > 3 {
946✔
278
                if keysCachedTotal, err = extractVal(split[3]); err != nil {
18✔
279
                        slog.Debug("parseDBKeyspaceString extractVal(split[3]) invalid", "error", err)
1✔
280
                        return
1✔
281
                }
1✔
282
        }
283

284
        ok = true
928✔
285
        return
928✔
286
}
287

288
/*
289
slave0:ip=10.254.11.1,port=6379,state=online,offset=1751844676,lag=0
290
slave1:ip=10.254.11.2,port=6379,state=online,offset=1751844222,lag=0
291
*/
292
func parseConnectedSlaveString(slaveName string, keyValues string) (offset float64, ip string, port string, state string, lag float64, ok bool) {
23,734✔
293
        ok = false
23,734✔
294
        if !reSlave.MatchString(slaveName) {
47,119✔
295
                return
23,385✔
296
        }
23,385✔
297
        connectedkeyValues := make(map[string]string)
349✔
298
        for _, kvPart := range strings.Split(keyValues, ",") {
2,265✔
299
                x := strings.Split(kvPart, "=")
1,916✔
300
                if len(x) != 2 {
1,916✔
NEW
301
                        slog.Debug("Invalid format for connected slave string", "kvPart", kvPart)
×
302
                        return
×
303
                }
×
304
                connectedkeyValues[x[0]] = x[1]
1,916✔
305
        }
306
        offset, err := strconv.ParseFloat(connectedkeyValues["offset"], 64)
349✔
307
        if err != nil {
350✔
308
                slog.Debug("Can not parse connected slave offset", "offset", connectedkeyValues["offset"])
1✔
309
                return
1✔
310
        }
1✔
311

312
        if lagStr, exists := connectedkeyValues["lag"]; !exists {
349✔
313
                // Prior to Redis 3.0, "lag" property does not exist
1✔
314
                lag = -1
1✔
315
        } else {
348✔
316
                lag, err = strconv.ParseFloat(lagStr, 64)
347✔
317
                if err != nil {
348✔
318
                        slog.Debug("Can not parse connected slave lag", "lagStr", lagStr)
1✔
319
                        return
1✔
320
                }
1✔
321
        }
322

323
        ok = true
347✔
324
        ip = connectedkeyValues["ip"]
347✔
325
        port = connectedkeyValues["port"]
347✔
326
        state = connectedkeyValues["state"]
347✔
327

347✔
328
        return
347✔
329
}
330

331
func (e *Exporter) handleMetricsReplication(ch chan<- prometheus.Metric, masterHost string, masterPort string, fieldKey string, fieldValue string) bool {
24,761✔
332
        // only slaves have this field
24,761✔
333
        if reMasterLinkStatus.MatchString(fieldKey) {
25,020✔
334
                if fieldValue == "up" {
518✔
335
                        e.registerConstMetricGauge(ch, "master_link_up", 1, masterHost, masterPort)
259✔
336
                } else {
259✔
337
                        e.registerConstMetricGauge(ch, "master_link_up", 0, masterHost, masterPort)
×
338
                }
×
339
                return true
259✔
340
        }
341

342
        if reMasterDirect.MatchString(fieldKey) {
25,279✔
343
                if strings.HasSuffix(fieldKey, "last_io_seconds_ago") {
1,036✔
344
                        fieldKey = "master_last_io_seconds_ago"
259✔
345
                } else if strings.HasSuffix(fieldKey, "sync_in_progress") {
1,036✔
346
                        fieldKey = "master_sync_in_progress"
259✔
347
                }
259✔
348
                val, _ := strconv.Atoi(fieldValue)
777✔
349
                e.registerConstMetricGauge(ch, fieldKey, float64(val), masterHost, masterPort)
777✔
350
                return true
777✔
351
        }
352

353
        // not a slave, try extracting master metrics
354
        if slaveOffset, slaveIP, slavePort, slaveState, slaveLag, ok := parseConnectedSlaveString(fieldKey, fieldValue); ok {
24,067✔
355
                e.registerConstMetricGauge(ch,
342✔
356
                        "connected_slave_offset_bytes",
342✔
357
                        slaveOffset,
342✔
358
                        slaveIP, slavePort, slaveState,
342✔
359
                )
342✔
360

342✔
361
                if slaveLag > -1 {
684✔
362
                        e.registerConstMetricGauge(ch,
342✔
363
                                "connected_slave_lag_seconds",
342✔
364
                                slaveLag,
342✔
365
                                slaveIP, slavePort, slaveState,
342✔
366
                        )
342✔
367
                }
342✔
368
                return true
342✔
369
        }
370

371
        return false
23,383✔
372
}
373

374
func (e *Exporter) handleMetricsServer(ch chan<- prometheus.Metric, fieldKey string, fieldValue string) {
48,724✔
375
        if fieldKey == "uptime_in_seconds" {
50,820✔
376
                if uptime, err := strconv.ParseFloat(fieldValue, 64); err == nil {
4,192✔
377
                        e.registerConstMetricGauge(ch, "start_time_seconds", float64(time.Now().Unix())-uptime)
2,096✔
378
                }
2,096✔
379
        }
380

381
        if fieldKey == "configured_hz" {
50,482✔
382
                if hz, err := strconv.ParseInt(fieldValue, 10, 64); err == nil {
3,516✔
383
                        e.registerConstMetricGauge(ch, "configured_hz", float64(hz))
1,758✔
384
                }
1,758✔
385
        }
386

387
        if fieldKey == "hz" {
50,645✔
388
                if hz, err := strconv.ParseInt(fieldValue, 10, 64); err == nil {
3,842✔
389
                        e.registerConstMetricGauge(ch, "hz", float64(hz))
1,921✔
390
                }
1,921✔
391
        }
392
}
393

394
func parseMetricsCommandStats(fieldKey string, fieldValue string) (cmd string, calls float64, rejectedCalls float64, failedCalls float64, usecTotal float64, extendedStats bool, errorOut error) {
28,620✔
395
        /*
28,620✔
396
                There are 2 formats. (One before Redis 6.2 and one after it)
28,620✔
397
                Format before v6.2:
28,620✔
398
                        cmdstat_get:calls=21,usec=175,usec_per_call=8.33
28,620✔
399
                        cmdstat_set:calls=61,usec=3139,usec_per_call=51.46
28,620✔
400
                        cmdstat_setex:calls=75,usec=1260,usec_per_call=16.80
28,620✔
401
                        cmdstat_georadius_ro:calls=75,usec=1260,usec_per_call=16.80
28,620✔
402
                Format from v6.2 forward:
28,620✔
403
                        cmdstat_get:calls=21,usec=175,usec_per_call=8.33,rejected_calls=0,failed_calls=0
28,620✔
404
                        cmdstat_set:calls=61,usec=3139,usec_per_call=51.46,rejected_calls=0,failed_calls=0
28,620✔
405
                        cmdstat_setex:calls=75,usec=1260,usec_per_call=16.80,rejected_calls=0,failed_calls=0
28,620✔
406
                        cmdstat_georadius_ro:calls=75,usec=1260,usec_per_call=16.80,rejected_calls=0,failed_calls=0
28,620✔
407

28,620✔
408
                broken up like this:
28,620✔
409
                        fieldKey  = cmdstat_get
28,620✔
410
                        fieldValue= calls=21,usec=175,usec_per_call=8.33
28,620✔
411
        */
28,620✔
412

28,620✔
413
        const cmdPrefix = "cmdstat_"
28,620✔
414
        extendedStats = false
28,620✔
415

28,620✔
416
        if !strings.HasPrefix(fieldKey, cmdPrefix) {
28,621✔
417
                errorOut = errors.New("invalid fieldKey")
1✔
418
                return
1✔
419
        }
1✔
420
        cmd = strings.TrimPrefix(fieldKey, cmdPrefix)
28,619✔
421

28,619✔
422
        splitValue := strings.Split(fieldValue, ",")
28,619✔
423
        splitLen := len(splitValue)
28,619✔
424
        if splitLen < 3 {
28,621✔
425
                errorOut = errors.New("invalid fieldValue")
2✔
426
                return
2✔
427
        }
2✔
428

429
        // internal error variable
430
        var err error
28,617✔
431
        calls, err = extractVal(splitValue[0])
28,617✔
432
        if err != nil {
28,618✔
433
                errorOut = errors.New("invalid splitValue[0]")
1✔
434
                return
1✔
435
        }
1✔
436

437
        usecTotal, err = extractVal(splitValue[1])
28,616✔
438
        if err != nil {
28,617✔
439
                errorOut = errors.New("invalid splitValue[1]")
1✔
440
                return
1✔
441
        }
1✔
442

443
        // pre 6.2 did not include rejected/failed calls stats so if we have less than 5 tokens we're done here
444
        if splitLen < 5 {
30,713✔
445
                return
2,098✔
446
        }
2,098✔
447

448
        rejectedCalls, err = extractVal(splitValue[3])
26,517✔
449
        if err != nil {
26,518✔
450
                errorOut = errors.New("invalid rejected_calls while parsing splitValue[3]")
1✔
451
                return
1✔
452
        }
1✔
453

454
        failedCalls, err = extractVal(splitValue[4])
26,516✔
455
        if err != nil {
26,517✔
456
                errorOut = errors.New("invalid failed_calls while parsing splitValue[4]")
1✔
457
                return
1✔
458
        }
1✔
459
        extendedStats = true
26,515✔
460
        return
26,515✔
461
}
462

463
func parseMetricsLatencyStats(fieldKey string, fieldValue string) (cmd string, percentileMap map[float64]float64, errorOut error) {
19,075✔
464
        /*
19,075✔
465
                # Latencystats
19,075✔
466
                latency_percentiles_usec_rpop:p50=0.001,p99=1.003,p99.9=4.015
19,075✔
467
                latency_percentiles_usec_zadd:p50=0.001,p99=1.003,p99.9=4.015
19,075✔
468
                latency_percentiles_usec_hset:p50=0.001,p99=1.003,p99.9=3.007
19,075✔
469
                latency_percentiles_usec_set:p50=0.001,p99=1.003,p99.9=4.015
19,075✔
470
                latency_percentiles_usec_lpop:p50=0.001,p99=1.003,p99.9=4.015
19,075✔
471
                latency_percentiles_usec_lpush:p50=0.001,p99=1.003,p99.9=4.015
19,075✔
472
                latency_percentiles_usec_lrange:p50=17.023,p99=21.119,p99.9=27.007
19,075✔
473
                latency_percentiles_usec_get:p50=0.001,p99=1.003,p99.9=3.007
19,075✔
474
                latency_percentiles_usec_mset:p50=1.003,p99=1.003,p99.9=1.003
19,075✔
475
                latency_percentiles_usec_spop:p50=0.001,p99=1.003,p99.9=1.003
19,075✔
476
                latency_percentiles_usec_incr:p50=0.001,p99=1.003,p99.9=3.007
19,075✔
477
                latency_percentiles_usec_rpush:p50=0.001,p99=1.003,p99.9=4.015
19,075✔
478
                latency_percentiles_usec_zpopmin:p50=0.001,p99=1.003,p99.9=3.007
19,075✔
479
                latency_percentiles_usec_config|resetstat:p50=280.575,p99=280.575,p99.9=280.575
19,075✔
480
                latency_percentiles_usec_config|get:p50=8.031,p99=27.007,p99.9=27.007
19,075✔
481
                latency_percentiles_usec_ping:p50=0.001,p99=1.003,p99.9=1.003
19,075✔
482
                latency_percentiles_usec_sadd:p50=0.001,p99=1.003,p99.9=3.007
19,075✔
483

19,075✔
484
                broken up like this:
19,075✔
485
                        fieldKey  = latency_percentiles_usec_ping
19,075✔
486
                        fieldValue= p50=0.001,p99=1.003,p99.9=3.007
19,075✔
487
        */
19,075✔
488

19,075✔
489
        const cmdPrefix = "latency_percentiles_usec_"
19,075✔
490
        percentileMap = map[float64]float64{}
19,075✔
491

19,075✔
492
        if !strings.HasPrefix(fieldKey, cmdPrefix) {
19,076✔
493
                errorOut = errors.New("invalid fieldKey")
1✔
494
                return
1✔
495
        }
1✔
496
        cmd = strings.TrimPrefix(fieldKey, cmdPrefix)
19,074✔
497
        splitValue := strings.Split(fieldValue, ",")
19,074✔
498
        splitLen := len(splitValue)
19,074✔
499
        if splitLen < 1 {
19,074✔
500
                errorOut = errors.New("invalid fieldValue")
×
501
                return
×
502
        }
×
503
        for pos, kv := range splitValue {
76,290✔
504
                percentile, value, err := extractPercentileVal(kv)
57,216✔
505
                if err != nil {
57,217✔
506
                        errorOut = fmt.Errorf("invalid splitValue[%d]", pos)
1✔
507
                        return
1✔
508
                }
1✔
509
                percentileMap[percentile] = value
57,215✔
510
        }
511
        return
19,073✔
512
}
513

514
func parseMetricsErrorStats(fieldKey string, fieldValue string) (errorType string, count float64, errorOut error) {
981✔
515
        /*
981✔
516
                Format:
981✔
517
                        errorstat_ERR:count=4
981✔
518
                        errorstat_NOAUTH:count=3
981✔
519

981✔
520
                broken up like this:
981✔
521
                        fieldKey  = errorstat_ERR
981✔
522
                        fieldValue= count=3
981✔
523
        */
981✔
524

981✔
525
        const prefix = "errorstat_"
981✔
526

981✔
527
        if !strings.HasPrefix(fieldKey, prefix) {
982✔
528
                errorOut = errors.New("invalid fieldKey. errorstat_ prefix not present")
1✔
529
                return
1✔
530
        }
1✔
531
        errorType = strings.TrimPrefix(fieldKey, prefix)
980✔
532
        count, err := extractVal(fieldValue)
980✔
533
        if err != nil {
982✔
534
                errorOut = errors.New("invalid error type on splitValue[0]")
2✔
535
                return
2✔
536
        }
2✔
537
        return
978✔
538
}
539

540
func (e *Exporter) handleMetricsCommandStats(ch chan<- prometheus.Metric, fieldKey string, fieldValue string) (cmd string, calls float64, usecTotal float64) {
28,610✔
541
        cmd, calls, rejectedCalls, failedCalls, usecTotal, extendedStats, err := parseMetricsCommandStats(fieldKey, fieldValue)
28,610✔
542
        if err != nil {
28,610✔
NEW
543
                slog.Debug("parseMetricsCommandStats err", "fieldKey", fieldKey, "fieldValue", fieldValue, "error", err)
×
544
                return
×
545
        }
×
546
        e.createMetricDescription("commands_total", []string{"cmd"})
28,610✔
547
        e.createMetricDescription("commands_duration_seconds_total", []string{"cmd"})
28,610✔
548
        e.registerConstMetric(ch, "commands_total", calls, prometheus.CounterValue, cmd)
28,610✔
549
        e.registerConstMetric(ch, "commands_duration_seconds_total", usecTotal/1e6, prometheus.CounterValue, cmd)
28,610✔
550
        if extendedStats {
55,124✔
551
                e.createMetricDescription("commands_rejected_calls_total", []string{"cmd"})
26,514✔
552
                e.createMetricDescription("commands_failed_calls_total", []string{"cmd"})
26,514✔
553
                e.registerConstMetric(ch, "commands_rejected_calls_total", rejectedCalls, prometheus.CounterValue, cmd)
26,514✔
554
                e.registerConstMetric(ch, "commands_failed_calls_total", failedCalls, prometheus.CounterValue, cmd)
26,514✔
555
        }
26,514✔
556
        return
28,610✔
557
}
558

559
func (e *Exporter) handleMetricsLatencyStats(fieldKey string, fieldValue string, cmdLatencyMap map[string]map[float64]float64) {
19,070✔
560
        cmd, latencyMap, err := parseMetricsLatencyStats(fieldKey, fieldValue)
19,070✔
561
        if err == nil {
38,140✔
562
                cmdLatencyMap[cmd] = latencyMap
19,070✔
563
        }
19,070✔
564
}
565

566
func (e *Exporter) handleMetricsErrorStats(ch chan<- prometheus.Metric, fieldKey string, fieldValue string) {
977✔
567
        if errorPrefix, count, err := parseMetricsErrorStats(fieldKey, fieldValue); err == nil {
1,954✔
568
                e.registerConstMetric(ch, "errors_total", count, prometheus.CounterValue, errorPrefix)
977✔
569
        }
977✔
570
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc