diff options
Diffstat (limited to 'src/go/plugin/go.d/modules/zfspool/collect_zpool_list_vdev.go')
-rw-r--r-- | src/go/plugin/go.d/modules/zfspool/collect_zpool_list_vdev.go | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/src/go/plugin/go.d/modules/zfspool/collect_zpool_list_vdev.go b/src/go/plugin/go.d/modules/zfspool/collect_zpool_list_vdev.go new file mode 100644 index 000000000..30e1fe4e1 --- /dev/null +++ b/src/go/plugin/go.d/modules/zfspool/collect_zpool_list_vdev.go @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package zfspool + +import ( + "bufio" + "bytes" + "fmt" + "strings" +) + +type vdevEntry struct { + name string + vdev string // The full path of the vdev within the zpool hierarchy. + health string + + // Represents the nesting level of the vdev within the zpool hierarchy, based on indentation. + // A level of -1 indicates the root vdev (the pool itself). + level int +} + +func (z *ZFSPool) collectZpoolListVdev(mx map[string]int64) error { + seen := make(map[string]bool) + + for pool := range z.seenZpools { + bs, err := z.exec.listWithVdev(pool) + if err != nil { + return err + } + + vdevs, err := parseZpoolListVdevOutput(bs) + if err != nil { + return fmt.Errorf("bad zpool list vdev output (pool '%s'): %v", pool, err) + } + + for _, vdev := range vdevs { + if vdev.health == "" || vdev.health == "-" { + continue + } + + seen[vdev.vdev] = true + if !z.seenVdevs[vdev.vdev] { + z.seenVdevs[vdev.vdev] = true + z.addVdevCharts(pool, vdev.vdev) + } + + px := fmt.Sprintf("vdev_%s_", vdev.vdev) + + for _, s := range zpoolHealthStates { + mx[px+"health_state_"+s] = 0 + } + mx[px+"health_state_"+vdev.health] = 1 + } + } + + for name := range z.seenVdevs { + if !seen[name] { + z.removeVdevCharts(name) + delete(z.seenVdevs, name) + } + } + + return nil +} + +func parseZpoolListVdevOutput(bs []byte) ([]vdevEntry, error) { + var headers []string + var vdevs []vdevEntry + sc := bufio.NewScanner(bytes.NewReader(bs)) + + for sc.Scan() { + line := sc.Text() + if line == "" { + continue + } + + if len(headers) == 0 { + if !strings.HasPrefix(line, "NAME") { + return nil, fmt.Errorf("missing headers (line '%s')", line) + } + headers = strings.Fields(line) + continue + } + + values := strings.Fields(line) + if len(values) == 0 || len(values) > len(headers) { + return nil, fmt.Errorf("unexpected columns: headers(%d) values(%d) (line '%s')", len(headers), len(values), line) + } + + vdev := vdevEntry{ + level: len(line) - len(strings.TrimLeft(line, " ")), + } + + for i, v := range values { + switch strings.ToLower(headers[i]) { + case "name": + vdev.name = v + case "health": + vdev.health = strings.ToLower(v) + } + } + + if vdev.name != "" { + if len(vdevs) == 0 { + vdev.level = -1 // Pool + } + vdevs = append(vdevs, vdev) + } + } + + // set parent/child relationships + for i := range vdevs { + v := &vdevs[i] + + switch i { + case 0: + v.vdev = v.name + default: + // find parent with a lower level + for j := i - 1; j >= 0; j-- { + if vdevs[j].level < v.level { + v.vdev = fmt.Sprintf("%s/%s", vdevs[j].vdev, v.name) + break + } + } + if v.vdev == "" { + return nil, fmt.Errorf("no parent for vdev '%s'", v.name) + } + } + } + + // first is Pool + if len(vdevs) < 2 { + return nil, fmt.Errorf("no vdevs found") + } + + return vdevs[1:], nil +} |