firehol/netdata

View on GitHub
src/go/plugin/go.d/modules/memcached/collect.go

Summary

Maintainability
A
1 hr
Test Coverage
// SPDX-License-Identifier: GPL-3.0-or-later

package memcached

import (
    "bufio"
    "bytes"
    "errors"
    "strconv"
    "strings"
)

// https://github.com/memcached/memcached/blob/b1aefcdf8a265f8a5126e8aa107a50988fa1ec35/doc/protocol.txt#L1267
var statsMetrics = map[string]bool{
    "limit_maxbytes":       true,
    "bytes":                true,
    "bytes_read":           true,
    "bytes_written":        true,
    "cas_badval":           true,
    "cas_hits":             true,
    "cas_misses":           true,
    "cmd_get":              true,
    "cmd_set":              true,
    "cmd_touch":            true,
    "curr_connections":     true,
    "curr_items":           true,
    "decr_hits":            true,
    "decr_misses":          true,
    "delete_hits":          true,
    "delete_misses":        true,
    "evictions":            true,
    "get_hits":             true,
    "get_misses":           true,
    "incr_hits":            true,
    "incr_misses":          true,
    "reclaimed":            true,
    "rejected_connections": true,
    "total_connections":    true,
    "total_items":          true,
    "touch_hits":           true,
    "touch_misses":         true,
}

func (m *Memcached) collect() (map[string]int64, error) {
    if m.conn == nil {
        conn, err := m.establishConn()
        if err != nil {
            return nil, err
        }
        m.conn = conn
    }

    stats, err := m.conn.queryStats()
    if err != nil {
        m.conn.disconnect()
        m.conn = nil
        return nil, err
    }

    mx := make(map[string]int64)

    if err := m.collectStats(mx, stats); err != nil {
        return nil, err
    }

    return mx, nil
}

func (m *Memcached) collectStats(mx map[string]int64, stats []byte) error {
    if len(stats) == 0 {
        return errors.New("empty stats response")
    }

    var n int
    sc := bufio.NewScanner(bytes.NewReader(stats))

    for sc.Scan() {
        line := strings.TrimSpace(sc.Text())

        switch {
        case strings.HasPrefix(line, "STAT"):
            key, value := getStatKeyValue(line)
            if !statsMetrics[key] {
                continue
            }
            if v, err := strconv.ParseInt(value, 10, 64); err == nil {
                mx[key] = v
                n++
            }
        case strings.HasPrefix(line, "ERROR"):
            return errors.New("received ERROR response")
        }
    }

    if n == 0 {
        return errors.New("unexpected memcached response")
    }

    mx["avail"] = mx["limit_maxbytes"] - mx["bytes"]

    return nil
}

func (m *Memcached) establishConn() (memcachedConn, error) {
    conn := m.newMemcachedConn(m.Config)

    if err := conn.connect(); err != nil {
        return nil, err
    }

    return conn, nil
}

func getStatKeyValue(line string) (string, string) {
    line = strings.TrimPrefix(line, "STAT ")
    i := strings.IndexByte(line, ' ')
    if i < 0 {
        return "", ""
    }
    return line[:i], line[i+1:]
}