-
Notifications
You must be signed in to change notification settings - Fork 892
/
Copy pathlatency.go
111 lines (90 loc) · 3.13 KB
/
latency.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package exporter
import (
"regexp"
"strconv"
"strings"
"sync"
"github.com/gomodule/redigo/redis"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
)
var (
logLatestErrOnce, logHistogramErrOnce sync.Once
extractUsecRegexp = regexp.MustCompile(`(?m)^cmdstat_([a-zA-Z0-9\|]+):.*usec=([0-9]+).*$`)
)
func (e *Exporter) extractLatencyMetrics(ch chan<- prometheus.Metric, infoAll string, c redis.Conn) {
e.extractLatencyLatestMetrics(ch, c)
e.extractLatencyHistogramMetrics(ch, infoAll, c)
}
func (e *Exporter) extractLatencyLatestMetrics(outChan chan<- prometheus.Metric, redisConn redis.Conn) {
reply, err := redis.Values(doRedisCmd(redisConn, "LATENCY", "LATEST"))
if err != nil {
/*
this can be a little too verbose, see e.g. https://github.com/oliver006/redis_exporter/issues/495
we're logging this only once as an Error and always as Debugf()
*/
logLatestErrOnce.Do(func() {
log.Errorf("WARNING, LOGGED ONCE ONLY: cmd LATENCY LATEST, err: %s", err)
})
log.Debugf("cmd LATENCY LATEST, err: %s", err)
return
}
for _, l := range reply {
if latencyResult, err := redis.Values(l, nil); err == nil {
var eventName string
var spikeLast, spikeDuration, maxLatency int64
if _, err := redis.Scan(latencyResult, &eventName, &spikeLast, &spikeDuration, &maxLatency); err == nil {
spikeDurationSeconds := float64(spikeDuration) / 1e3
e.registerConstMetricGauge(outChan, "latency_spike_last", float64(spikeLast), eventName)
e.registerConstMetricGauge(outChan, "latency_spike_duration_seconds", spikeDurationSeconds, eventName)
}
}
}
}
/*
https://redis.io/docs/latest/commands/latency-histogram/
*/
func (e *Exporter) extractLatencyHistogramMetrics(outChan chan<- prometheus.Metric, infoAll string, redisConn redis.Conn) {
reply, err := redis.Values(doRedisCmd(redisConn, "LATENCY", "HISTOGRAM"))
if err != nil {
logHistogramErrOnce.Do(func() {
log.Errorf("WARNING, LOGGED ONCE ONLY: cmd LATENCY HISTOGRAM, err: %s", err)
})
log.Debugf("cmd LATENCY HISTOGRAM, err: %s", err)
return
}
for i := 0; i < len(reply); i += 2 {
cmd, _ := redis.String(reply[i], nil)
details, _ := redis.Values(reply[i+1], nil)
var totalCalls uint64
var bucketInfo []uint64
if _, err := redis.Scan(details, nil, &totalCalls, nil, &bucketInfo); err != nil {
break
}
buckets := map[float64]uint64{}
for j := 0; j < len(bucketInfo); j += 2 {
usec := float64(bucketInfo[j])
count := bucketInfo[j+1]
buckets[usec] = count
}
totalUsecs := extractTotalUsecForCommand(infoAll, cmd)
labelValues := []string{"cmd"}
e.registerConstHistogram(outChan, "commands_latencies_usec", labelValues, totalCalls, float64(totalUsecs), buckets, cmd)
}
}
func extractTotalUsecForCommand(infoAll string, cmd string) uint64 {
total := uint64(0)
matches := extractUsecRegexp.FindAllStringSubmatch(infoAll, -1)
for _, match := range matches {
if !strings.HasPrefix(match[1], cmd) {
continue
}
usecs, err := strconv.ParseUint(match[2], 10, 0)
if err != nil {
log.Warnf("Unable to parse uint from string \"%s\": %v", match[2], err)
continue
}
total += usecs
}
return total
}