• 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

90.27
/exporter/key_groups.go
1
package exporter
2

3
import (
4
        "encoding/csv"
5
        "fmt"
6
        "log/slog"
7
        "sort"
8
        "strings"
9
        "time"
10

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

15
type keyGroupMetrics struct {
16
        keyGroup    string
17
        count       int64
18
        memoryUsage int64
19
}
20

21
type overflowedKeyGroupMetrics struct {
22
        topMemoryUsageKeyGroups   []*keyGroupMetrics
23
        overflowKeyGroupAggregate keyGroupMetrics
24
        keyGroupsCount            int64
25
}
26

27
type keyGroupsScrapeResult struct {
28
        duration          time.Duration
29
        metrics           []map[string]*keyGroupMetrics
30
        overflowedMetrics []*overflowedKeyGroupMetrics
31
}
32

33
func (e *Exporter) extractKeyGroupMetrics(ch chan<- prometheus.Metric, c redis.Conn, dbCount int) {
2,096✔
34
        allDbKeyGroupMetrics := e.gatherKeyGroupsMetricsForAllDatabases(c, dbCount)
2,096✔
35
        if allDbKeyGroupMetrics == nil {
2,096✔
36
                return
×
37
        }
×
38
        for db, dbKeyGroupMetrics := range allDbKeyGroupMetrics.metrics {
30,817✔
39
                dbLabel := fmt.Sprintf("db%d", db)
28,721✔
40
                registerKeyGroupMetrics := func(metrics *keyGroupMetrics) {
28,739✔
41
                        e.registerConstMetricGauge(
18✔
42
                                ch,
18✔
43
                                "key_group_count",
18✔
44
                                float64(metrics.count),
18✔
45
                                dbLabel,
18✔
46
                                metrics.keyGroup,
18✔
47
                        )
18✔
48
                        e.registerConstMetricGauge(
18✔
49
                                ch,
18✔
50
                                "key_group_memory_usage_bytes",
18✔
51
                                float64(metrics.memoryUsage),
18✔
52
                                dbLabel,
18✔
53
                                metrics.keyGroup,
18✔
54
                        )
18✔
55
                }
18✔
56
                if allDbKeyGroupMetrics.overflowedMetrics[db] != nil {
28,724✔
57
                        overflowedMetrics := allDbKeyGroupMetrics.overflowedMetrics[db]
3✔
58
                        for _, metrics := range overflowedMetrics.topMemoryUsageKeyGroups {
6✔
59
                                registerKeyGroupMetrics(metrics)
3✔
60
                        }
3✔
61
                        registerKeyGroupMetrics(&overflowedMetrics.overflowKeyGroupAggregate)
3✔
62
                        e.registerConstMetricGauge(ch, "number_of_distinct_key_groups", float64(overflowedMetrics.keyGroupsCount), dbLabel)
3✔
63
                } else if dbKeyGroupMetrics != nil {
28,747✔
64
                        for _, metrics := range dbKeyGroupMetrics {
41✔
65
                                registerKeyGroupMetrics(metrics)
12✔
66
                        }
12✔
67
                        e.registerConstMetricGauge(ch, "number_of_distinct_key_groups", float64(len(dbKeyGroupMetrics)), dbLabel)
29✔
68
                }
69
        }
70
        e.registerConstMetricGauge(ch, "last_key_groups_scrape_duration_milliseconds", float64(allDbKeyGroupMetrics.duration.Milliseconds()))
2,096✔
71
}
72

73
func (e *Exporter) gatherKeyGroupsMetricsForAllDatabases(c redis.Conn, dbCount int) *keyGroupsScrapeResult {
2,096✔
74
        start := time.Now()
2,096✔
75
        allMetrics := &keyGroupsScrapeResult{
2,096✔
76
                metrics:           make([]map[string]*keyGroupMetrics, dbCount),
2,096✔
77
                overflowedMetrics: make([]*overflowedKeyGroupMetrics, dbCount),
2,096✔
78
        }
2,096✔
79
        defer func() {
4,192✔
80
                allMetrics.duration = time.Since(start)
2,096✔
81
        }()
2,096✔
82
        if strings.TrimSpace(e.options.CheckKeyGroups) == "" {
4,190✔
83
                return allMetrics
2,094✔
84
        }
2,094✔
85
        keyGroups, err := csv.NewReader(
2✔
86
                strings.NewReader(e.options.CheckKeyGroups),
2✔
87
        ).Read()
2✔
88
        if err != nil {
2✔
NEW
89
                slog.Error("Failed to parse key groups as csv", "error", err)
×
90
                return allMetrics
×
91
        }
×
92
        for i, v := range keyGroups {
6✔
93
                keyGroups[i] = strings.TrimSpace(v)
4✔
94
        }
4✔
95

96
        keyGroupsNoEmptyStrings := make([]string, 0)
2✔
97
        for _, v := range keyGroups {
6✔
98
                if len(v) > 0 {
8✔
99
                        keyGroupsNoEmptyStrings = append(keyGroupsNoEmptyStrings, v)
4✔
100
                }
4✔
101
        }
102
        if len(keyGroupsNoEmptyStrings) == 0 {
2✔
103
                return allMetrics
×
104
        }
×
105
        for db := 0; db < dbCount; db++ {
34✔
106
                if _, err := doRedisCmd(c, "SELECT", db); err != nil {
32✔
NEW
107
                        slog.Error("Couldn't select database when getting key info", "db", db, "error", err)
×
108
                        continue
×
109
                }
110
                allGroups, err := gatherKeyGroupMetrics(c, e.options.CheckKeysBatchSize, keyGroupsNoEmptyStrings)
32✔
111
                if err != nil {
32✔
NEW
112
                        slog.Error("Error gathering key group metrics", "error", err)
×
113
                        continue
×
114
                }
115
                allMetrics.metrics[db] = allGroups
32✔
116
                if int64(len(allGroups)) > e.options.MaxDistinctKeyGroups {
35✔
117
                        metricsSlice := make([]*keyGroupMetrics, 0, len(allGroups))
3✔
118
                        for _, v := range allGroups {
51✔
119
                                metricsSlice = append(metricsSlice, v)
48✔
120
                        }
48✔
121
                        sort.Slice(metricsSlice, func(i, j int) bool {
186✔
122
                                if metricsSlice[i].memoryUsage == metricsSlice[j].memoryUsage {
250✔
123
                                        if metricsSlice[i].count == metricsSlice[j].count {
134✔
124
                                                return metricsSlice[i].keyGroup < metricsSlice[j].keyGroup
67✔
125
                                        }
67✔
126
                                        return metricsSlice[i].count < metricsSlice[j].count
×
127
                                }
128
                                return metricsSlice[i].memoryUsage > metricsSlice[j].memoryUsage
116✔
129
                        })
130
                        var overflowedCount, overflowedMemoryUsage int64
3✔
131
                        for _, v := range metricsSlice[e.options.MaxDistinctKeyGroups:] {
48✔
132
                                overflowedCount += v.count
45✔
133
                                overflowedMemoryUsage += v.memoryUsage
45✔
134
                        }
45✔
135
                        allMetrics.overflowedMetrics[db] = &overflowedKeyGroupMetrics{
3✔
136
                                topMemoryUsageKeyGroups: metricsSlice[:e.options.MaxDistinctKeyGroups],
3✔
137
                                overflowKeyGroupAggregate: keyGroupMetrics{
3✔
138
                                        keyGroup:    "overflow",
3✔
139
                                        count:       overflowedCount,
3✔
140
                                        memoryUsage: overflowedMemoryUsage,
3✔
141
                                },
3✔
142
                                keyGroupsCount: int64(len(allGroups)),
3✔
143
                        }
3✔
144
                }
145
        }
146
        return allMetrics
2✔
147
}
148

149
func gatherKeyGroupMetrics(c redis.Conn, batchSize int64, keyGroups []string) (map[string]*keyGroupMetrics, error) {
32✔
150
        allGroups := make(map[string]*keyGroupMetrics)
32✔
151
        keysAndArgs := []interface{}{0, batchSize}
32✔
152
        for _, keyGroup := range keyGroups {
96✔
153
                keysAndArgs = append(keysAndArgs, keyGroup)
64✔
154
        }
64✔
155

156
        script := redis.NewScript(
32✔
157
                0,
32✔
158
                `
32✔
159
local result = {}
32✔
160
local batch = redis.call("SCAN", ARGV[1], "COUNT", ARGV[2])
32✔
161
local groups = {}
32✔
162
local usage = 0
32✔
163
local group_index = 0
32✔
164
local group = nil
32✔
165
local value = {}
32✔
166
local key_match_result = {}
32✔
167
local status = false
32✔
168
local err = nil
32✔
169
for i=3,#ARGV do
32✔
170
  status, err = pcall(string.find, " ", ARGV[i])
32✔
171
  if not status then
32✔
172
    error(err .. ARGV[i])
32✔
173
  end
32✔
174
end
32✔
175
for i,key in ipairs(batch[2]) do
32✔
176
  local reply = redis.pcall("MEMORY", "USAGE", key)  
32✔
177
  if type(reply) == "number" then
32✔
178
    usage = reply;
32✔
179
  end
32✔
180
  group = nil
32✔
181
  for i=3,#ARGV do
32✔
182
    key_match_result = {string.find(key, ARGV[i])}
32✔
183
    if key_match_result[1] ~= nil then
32✔
184
      group = table.concat({unpack(key_match_result, 3,  #key_match_result)},  "")
32✔
185
      break
32✔
186
    end
32✔
187
  end
32✔
188
  if group == nil then
32✔
189
     group = "unclassified"
32✔
190
  end
32✔
191
  value = groups[group]
32✔
192
  if value == nil then
32✔
193
     groups[group] = {1, usage}
32✔
194
  else
32✔
195
     groups[group] = {value[1] + 1, value[2] + usage}
32✔
196
  end
32✔
197
end
32✔
198
for group,value in pairs(groups) do
32✔
199
  result[#result+1] = {group, value[1], value[2]}
32✔
200
end
32✔
201
return {batch[1], result}`,
32✔
202
        )
32✔
203

32✔
204
        for {
64✔
205
                arr, err := redis.Values(script.Do(c, keysAndArgs...))
32✔
206
                if err != nil {
32✔
207
                        return nil, err
×
208
                }
×
209

210
                if len(arr) != 2 {
32✔
211
                        return nil, fmt.Errorf("invalid response from key group metrics lua script for groups: %s", strings.Join(keyGroups, ", "))
×
212
                }
×
213

214
                groups, _ := redis.Values(arr[1], nil)
32✔
215

32✔
216
                for _, group := range groups {
92✔
217
                        metricsArr, _ := redis.Values(group, nil)
60✔
218
                        name, _ := redis.String(metricsArr[0], nil)
60✔
219
                        count, _ := redis.Int64(metricsArr[1], nil)
60✔
220
                        memoryUsage, _ := redis.Int64(metricsArr[2], nil)
60✔
221

60✔
222
                        if currentMetrics, ok := allGroups[name]; ok {
60✔
223
                                currentMetrics.count += count
×
224
                                currentMetrics.memoryUsage += memoryUsage
×
225
                        } else {
60✔
226
                                allGroups[name] = &keyGroupMetrics{
60✔
227
                                        keyGroup:    name,
60✔
228
                                        count:       count,
60✔
229
                                        memoryUsage: memoryUsage,
60✔
230
                                }
60✔
231
                        }
60✔
232

233
                }
234
                if keysAndArgs[0], _ = redis.Int(arr[0], nil); keysAndArgs[0].(int) == 0 {
64✔
235
                        break
32✔
236
                }
237
        }
238
        return allGroups, nil
32✔
239
}
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