79 lines
1.9 KiB
Go
79 lines
1.9 KiB
Go
|
|
package redis
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/redis/go-redis/v9"
|
||
|
|
|
||
|
|
"bugs.watch/agent/internal/aggregate"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Collector queries Redis INFO each sample interval.
|
||
|
|
// All queries run under a 3-second timeout; any failure is silently skipped.
|
||
|
|
// Metrics mapping:
|
||
|
|
// db_conn_pct → used_memory / maxmemory * 100 (skipped when maxmemory=0)
|
||
|
|
// db_threads_running → connected_clients
|
||
|
|
// db_size_bytes → used_memory
|
||
|
|
type Collector struct {
|
||
|
|
client *redis.Client
|
||
|
|
}
|
||
|
|
|
||
|
|
func New(addr string) (*Collector, error) {
|
||
|
|
opts, err := redis.ParseURL(addr)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
opts.DialTimeout = 3 * time.Second
|
||
|
|
opts.ReadTimeout = 3 * time.Second
|
||
|
|
opts.WriteTimeout = 3 * time.Second
|
||
|
|
return &Collector{client: redis.NewClient(opts)}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Collector) Name() string { return "redis" }
|
||
|
|
|
||
|
|
func (c *Collector) Close() { c.client.Close() }
|
||
|
|
|
||
|
|
func (c *Collector) Collect(window *aggregate.Window) {
|
||
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||
|
|
defer cancel()
|
||
|
|
|
||
|
|
info, err := c.client.Info(ctx, "memory", "clients").Result()
|
||
|
|
if err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
fields := parseInfo(info)
|
||
|
|
|
||
|
|
usedMem, _ := strconv.ParseUint(fields["used_memory"], 10, 64)
|
||
|
|
maxMem, _ := strconv.ParseUint(fields["maxmemory"], 10, 64)
|
||
|
|
clients, _ := strconv.ParseUint(fields["connected_clients"], 10, 64)
|
||
|
|
|
||
|
|
if usedMem > 0 {
|
||
|
|
window.SetDBSizeBytes(usedMem)
|
||
|
|
}
|
||
|
|
if clients > 0 {
|
||
|
|
window.AddDBThreadsRunning(float64(clients))
|
||
|
|
}
|
||
|
|
if maxMem > 0 && usedMem > 0 {
|
||
|
|
window.AddDBConnPct(float64(usedMem) / float64(maxMem) * 100)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func parseInfo(s string) map[string]string {
|
||
|
|
out := make(map[string]string)
|
||
|
|
for _, line := range strings.Split(s, "\n") {
|
||
|
|
line = strings.TrimSpace(line)
|
||
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
k, v, ok := strings.Cut(line, ":")
|
||
|
|
if ok {
|
||
|
|
out[strings.TrimSpace(k)] = strings.TrimSpace(v)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return out
|
||
|
|
}
|