• 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

95.29
/exporter/clients.go
1
package exporter
2

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

10
        "github.com/gomodule/redigo/redis"
11
        "github.com/prometheus/client_golang/prometheus"
12
)
13

14
type ClientInfo struct {
15
        Id,
16
        Name,
17
        User,
18
        Flags,
19
        Db,
20
        Host,
21
        Port,
22
        Resp string
23
        CreatedAt,
24
        IdleSince,
25
        Sub,
26
        Psub,
27
        Ssub,
28
        Watch,
29
        Qbuf,
30
        QbufFree,
31
        Obl,
32
        Oll,
33
        OMem,
34
        TotMem int64
35
}
36

37
/*
38
Valid Examples
39
id=11 addr=127.0.0.1:63508 fd=8 name= age=6321 idle=6320 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=setex user=default resp=2
40
id=14 addr=127.0.0.1:64958 fd=9 name= age=5 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client user=default resp=3
41
id=40253233 addr=fd40:1481:21:dbe0:7021:300:a03:1a06:44426 fd=19 name= age=782 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 argv-mem=10 obl=0 oll=0 omem=0 tot-mem=61466 ow=0 owmem=0 events=r cmd=client user=default lib-name=redis-py lib-ver=5.0.1 numops=9
42
*/
43
func parseClientListString(clientInfo string) (*ClientInfo, bool) {
25✔
44
        if matched, _ := regexp.MatchString(`^id=\d+ addr=\S+`, clientInfo); !matched {
30✔
45
                return nil, false
5✔
46
        }
5✔
47
        connectedClient := ClientInfo{}
20✔
48
        connectedClient.Ssub = -1  // mark it as missing - introduced in Redis 7.0.3
20✔
49
        connectedClient.Watch = -1 // mark it as missing - introduced in Redis 7.4
20✔
50
        for _, kvPart := range strings.Split(clientInfo, " ") {
284✔
51
                vPart := strings.Split(kvPart, "=")
264✔
52
                if len(vPart) != 2 {
264✔
NEW
53
                        slog.Debug("Invalid format for client list string", "kvPart", kvPart)
×
54
                        return nil, false
×
55
                }
×
56

57
                switch vPart[0] {
264✔
58
                case "id":
20✔
59
                        connectedClient.Id = vPart[1]
20✔
60
                case "name":
10✔
61
                        connectedClient.Name = vPart[1]
10✔
62
                case "user":
6✔
63
                        connectedClient.User = vPart[1]
6✔
64
                case "age":
10✔
65
                        createdAt, err := durationFieldToTimestamp(vPart[1])
10✔
66
                        if err != nil {
11✔
67
                                slog.Debug("could not parse 'age' field", "value", vPart[1], "error", err.Error())
1✔
68
                                return nil, false
1✔
69
                        }
1✔
70
                        connectedClient.CreatedAt = createdAt
9✔
71
                case "idle":
9✔
72
                        idleSinceTs, err := durationFieldToTimestamp(vPart[1])
9✔
73
                        if err != nil {
10✔
74
                                slog.Debug("could not parse 'idle' field", "value", vPart[1], "error", err.Error())
1✔
75
                                return nil, false
1✔
76
                        }
1✔
77
                        connectedClient.IdleSince = idleSinceTs
8✔
78
                case "flags":
8✔
79
                        connectedClient.Flags = vPart[1]
8✔
80
                case "db":
8✔
81
                        connectedClient.Db = vPart[1]
8✔
82
                case "sub":
9✔
83
                        connectedClient.Sub, _ = strconv.ParseInt(vPart[1], 10, 64)
9✔
84
                case "psub":
9✔
85
                        connectedClient.Psub, _ = strconv.ParseInt(vPart[1], 10, 64)
9✔
86
                case "ssub":
6✔
87
                        connectedClient.Ssub, _ = strconv.ParseInt(vPart[1], 10, 64)
6✔
88
                case "watch":
6✔
89
                        connectedClient.Watch, _ = strconv.ParseInt(vPart[1], 10, 64)
6✔
90
                case "qbuf":
9✔
91
                        connectedClient.Qbuf, _ = strconv.ParseInt(vPart[1], 10, 64)
9✔
92
                case "qbuf-free":
9✔
93
                        connectedClient.QbufFree, _ = strconv.ParseInt(vPart[1], 10, 64)
9✔
94
                case "obl":
9✔
95
                        connectedClient.Obl, _ = strconv.ParseInt(vPart[1], 10, 64)
9✔
96
                case "oll":
9✔
97
                        connectedClient.Oll, _ = strconv.ParseInt(vPart[1], 10, 64)
9✔
98
                case "omem":
9✔
99
                        connectedClient.OMem, _ = strconv.ParseInt(vPart[1], 10, 64)
9✔
100
                case "tot-mem":
9✔
101
                        connectedClient.TotMem, _ = strconv.ParseInt(vPart[1], 10, 64)
9✔
102
                case "addr":
20✔
103
                        hostPortString := strings.Split(vPart[1], ":")
20✔
104
                        if len(hostPortString) < 2 {
20✔
NEW
105
                                slog.Debug("Invalid value for 'addr' found in client info")
×
106
                                return nil, false
×
107
                        }
×
108
                        connectedClient.Host = strings.Join(hostPortString[:len(hostPortString)-1], ":")
20✔
109
                        connectedClient.Port = hostPortString[len(hostPortString)-1]
20✔
110
                case "resp":
5✔
111
                        connectedClient.Resp = vPart[1]
5✔
112
                }
113
        }
114

115
        return &connectedClient, true
18✔
116
}
117

118
func durationFieldToTimestamp(field string) (int64, error) {
30✔
119
        parsed, err := strconv.ParseInt(field, 10, 64)
30✔
120
        if err != nil {
33✔
121
                return 0, err
3✔
122
        }
3✔
123
        return time.Now().Unix() - parsed, nil
27✔
124
}
125

126
func (e *Exporter) extractConnectedClientMetrics(ch chan<- prometheus.Metric, c redis.Conn) {
4✔
127
        reply, err := redis.String(doRedisCmd(c, "CLIENT", "LIST"))
4✔
128
        if err != nil {
4✔
NEW
129
                slog.Error("CLIENT LIST err", "error", err)
×
130
                return
×
131
        }
×
132
        e.parseConnectedClientMetrics(reply, ch)
4✔
133
}
134

135
func (e *Exporter) parseConnectedClientMetrics(input string, ch chan<- prometheus.Metric) {
4✔
136

4✔
137
        for _, s := range strings.Split(input, "\n") {
12✔
138
                info, ok := parseClientListString(s)
8✔
139
                if !ok {
12✔
140
                        slog.Debug("parseClientListString - couldn't parse input", "input", s)
4✔
141
                        continue
4✔
142
                }
143
                clientInfoLabels := []string{"id", "name", "flags", "db", "host"}
4✔
144
                clientInfoLabelValues := []string{info.Id, info.Name, info.Flags, info.Db, info.Host}
4✔
145

4✔
146
                if e.options.ExportClientsInclPort {
5✔
147
                        clientInfoLabels = append(clientInfoLabels, "port")
1✔
148
                        clientInfoLabelValues = append(clientInfoLabelValues, info.Port)
1✔
149
                }
1✔
150

151
                if user := info.User; user != "" {
8✔
152
                        clientInfoLabels = append(clientInfoLabels, "user")
4✔
153
                        clientInfoLabelValues = append(clientInfoLabelValues, user)
4✔
154
                }
4✔
155

156
                // introduced in Redis 7.0
157
                if resp := info.Resp; resp != "" {
8✔
158
                        clientInfoLabels = append(clientInfoLabels, "resp")
4✔
159
                        clientInfoLabelValues = append(clientInfoLabelValues, resp)
4✔
160
                }
4✔
161

162
                e.createMetricDescription("connected_client_info", clientInfoLabels)
4✔
163
                e.registerConstMetricGauge(
4✔
164
                        ch, "connected_client_info", 1.0,
4✔
165
                        clientInfoLabelValues...,
4✔
166
                )
4✔
167

4✔
168
                clientBaseLabels := []string{"id", "name"}
4✔
169
                clientBaseLabelsValues := []string{info.Id, info.Name}
4✔
170

4✔
171
                for _, metricName := range []string{
4✔
172
                        "connected_client_output_buffer_memory_usage_bytes",
4✔
173
                        "connected_client_total_memory_consumed_bytes",
4✔
174
                        "connected_client_created_at_timestamp",
4✔
175
                        "connected_client_idle_since_timestamp",
4✔
176
                        "connected_client_channel_subscriptions_count",
4✔
177
                        "connected_client_pattern_matching_subscriptions_count",
4✔
178
                        "connected_client_query_buffer_length_bytes",
4✔
179
                        "connected_client_query_buffer_free_space_bytes",
4✔
180
                        "connected_client_output_buffer_length_bytes",
4✔
181
                        "connected_client_output_list_length",
4✔
182
                } {
44✔
183
                        e.createMetricDescription(metricName, clientBaseLabels)
40✔
184
                }
40✔
185

186
                e.registerConstMetricGauge(
4✔
187
                        ch, "connected_client_output_buffer_memory_usage_bytes", float64(info.OMem),
4✔
188
                        clientBaseLabelsValues...,
4✔
189
                )
4✔
190

4✔
191
                e.registerConstMetricGauge(
4✔
192
                        ch, "connected_client_total_memory_consumed_bytes", float64(info.TotMem),
4✔
193
                        clientBaseLabelsValues...,
4✔
194
                )
4✔
195

4✔
196
                e.registerConstMetricGauge(
4✔
197
                        ch, "connected_client_created_at_timestamp", float64(info.CreatedAt),
4✔
198
                        clientBaseLabelsValues...,
4✔
199
                )
4✔
200

4✔
201
                e.registerConstMetricGauge(
4✔
202
                        ch, "connected_client_idle_since_timestamp", float64(info.IdleSince),
4✔
203
                        clientBaseLabelsValues...,
4✔
204
                )
4✔
205

4✔
206
                e.registerConstMetricGauge(
4✔
207
                        ch, "connected_client_channel_subscriptions_count", float64(info.Sub),
4✔
208
                        clientBaseLabelsValues...,
4✔
209
                )
4✔
210

4✔
211
                e.registerConstMetricGauge(
4✔
212
                        ch, "connected_client_pattern_matching_subscriptions_count", float64(info.Psub),
4✔
213
                        clientBaseLabelsValues...,
4✔
214
                )
4✔
215

4✔
216
                e.registerConstMetricGauge(
4✔
217
                        ch, "connected_client_query_buffer_length_bytes", float64(info.Qbuf),
4✔
218
                        clientBaseLabelsValues...,
4✔
219
                )
4✔
220

4✔
221
                e.registerConstMetricGauge(
4✔
222
                        ch, "connected_client_query_buffer_free_space_bytes", float64(info.QbufFree),
4✔
223
                        clientBaseLabelsValues...,
4✔
224
                )
4✔
225

4✔
226
                e.registerConstMetricGauge(
4✔
227
                        ch, "connected_client_output_buffer_length_bytes", float64(info.Obl),
4✔
228
                        clientBaseLabelsValues...,
4✔
229
                )
4✔
230

4✔
231
                e.registerConstMetricGauge(
4✔
232
                        ch, "connected_client_output_list_length", float64(info.Oll),
4✔
233
                        clientBaseLabelsValues...,
4✔
234
                )
4✔
235

4✔
236
                if info.Ssub != -1 {
8✔
237
                        e.createMetricDescription("connected_client_shard_channel_subscriptions_count", clientBaseLabels)
4✔
238
                        e.registerConstMetricGauge(
4✔
239
                                ch, "connected_client_shard_channel_subscriptions_count", float64(info.Ssub),
4✔
240
                                clientBaseLabelsValues...,
4✔
241
                        )
4✔
242
                }
4✔
243
                if info.Watch != -1 {
8✔
244
                        e.createMetricDescription("connected_client_shard_channel_watched_keys", clientBaseLabels)
4✔
245
                        e.registerConstMetricGauge(
4✔
246
                                ch, "connected_client_shard_channel_watched_keys", float64(info.Watch),
4✔
247
                                clientBaseLabelsValues...,
4✔
248
                        )
4✔
249
                }
4✔
250
        }
251
}
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