summaryrefslogtreecommitdiffstats
path: root/src/go/plugin/go.d/modules/zfspool/collect_zpool_list_vdev.go
diff options
context:
space:
mode:
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.go138
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
+}