netdata/netdata

View on GitHub
src/go/collectors/go.d.plugin/modules/zfspool/collect.go

Summary

Maintainability
A
35 mins
Test Coverage
// SPDX-License-Identifier: GPL-3.0-or-later

package zfspool

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

var zpoolHealthStates = []string{
    "online",
    "degraded",
    "faulted",
    "offline",
    "removed",
    "unavail",
    "suspended",
}

type zpoolStats struct {
    name       string
    sizeBytes  string
    allocBytes string
    freeBytes  string
    fragPerc   string
    capPerc    string
    dedupRatio string
    health     string
}

func (z *ZFSPool) collect() (map[string]int64, error) {
    bs, err := z.exec.list()
    if err != nil {
        return nil, err
    }

    zpools, err := parseZpoolListOutput(bs)
    if err != nil {
        return nil, err
    }

    mx := make(map[string]int64)

    z.collectZpoolListStats(mx, zpools)

    return mx, nil
}

func (z *ZFSPool) collectZpoolListStats(mx map[string]int64, zpools []zpoolStats) {
    seen := make(map[string]bool)

    for _, zpool := range zpools {
        seen[zpool.name] = true

        if !z.zpools[zpool.name] {
            z.addZpoolCharts(zpool.name)
            z.zpools[zpool.name] = true
        }

        px := "zpool_" + zpool.name + "_"

        if v, ok := parseInt(zpool.sizeBytes); ok {
            mx[px+"size"] = v
        }
        if v, ok := parseInt(zpool.freeBytes); ok {
            mx[px+"free"] = v
        }
        if v, ok := parseInt(zpool.allocBytes); ok {
            mx[px+"alloc"] = v
        }
        if v, ok := parseFloat(zpool.capPerc); ok {
            mx[px+"cap"] = int64(v)
        }
        if v, ok := parseFloat(zpool.fragPerc); ok {
            mx[px+"frag"] = int64(v)
        }
        for _, s := range zpoolHealthStates {
            mx[px+"health_state_"+s] = 0
        }
        mx[px+"health_state_"+zpool.health] = 1
    }

    for name := range z.zpools {
        if !seen[name] {
            z.removeZpoolCharts(name)
            delete(z.zpools, name)
        }
    }
}

func parseZpoolListOutput(bs []byte) ([]zpoolStats, error) {
    var lines []string
    sc := bufio.NewScanner(bytes.NewReader(bs))
    for sc.Scan() {
        if text := strings.TrimSpace(sc.Text()); text != "" {
            lines = append(lines, text)
        }

    }
    if len(lines) < 2 {
        return nil, fmt.Errorf("unexpected data: wanted >= 2 lines, got %d", len(lines))
    }

    headers := strings.Fields(lines[0])
    if len(headers) == 0 {
        return nil, fmt.Errorf("unexpected data: missing headers")
    }

    var zpools []zpoolStats

    /*
       # zpool list -p
       NAME          SIZE       ALLOC         FREE  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
       rpool  21367462298  9051643576  12240656794         -     33     42   1.00  ONLINE  -
       zion             -           -            -         -      -      -      -  FAULTED -
    */

    for _, line := range lines[1:] {
        values := strings.Fields(line)
        if len(values) != len(headers) {
            return nil, fmt.Errorf("unequal columns: headers(%d) != values(%d)", len(headers), len(values))
        }

        var zpool zpoolStats

        for i, v := range values {
            v = strings.TrimSpace(v)
            switch strings.ToLower(headers[i]) {
            case "name":
                zpool.name = v
            case "size":
                zpool.sizeBytes = v
            case "alloc":
                zpool.allocBytes = v
            case "free":
                zpool.freeBytes = v
            case "frag":
                zpool.fragPerc = v
            case "cap":
                zpool.capPerc = v
            case "dedup":
                zpool.dedupRatio = v
            case "health":
                zpool.health = strings.ToLower(v)
            }

            if last := i+1 == len(headers); last && zpool.name != "" && zpool.health != "" {
                zpools = append(zpools, zpool)
            }
        }
    }

    if len(zpools) == 0 {
        return nil, fmt.Errorf("unexpected data: missing pools")
    }

    return zpools, nil
}

func parseInt(s string) (int64, bool) {
    if s == "-" {
        return 0, false
    }
    v, err := strconv.ParseInt(s, 10, 64)
    return v, err == nil
}

func parseFloat(s string) (float64, bool) {
    if s == "-" {
        return 0, false
    }
    v, err := strconv.ParseFloat(s, 64)
    return v, err == nil
}