1package collectors
2
3// things that are not OS specific.
4// ie: can compile and execute anywhere
5
6import (
7	"bufio"
8	"io"
9	"strconv"
10	"strings"
11
12	"bosun.org/metadata"
13	"bosun.org/opentsdb"
14)
15
16// Check mdadm raid arrays on linux
17// linux.disk.mdadm.state values:
18
19type mdadmState int
20
21const (
22	mdadmUnknown  mdadmState = 0
23	mdadmNormal              = 1 // active or clean state
24	mdadmFailed              = 2 // raid is failed
25	mdadmDegraded            = 3 // raid is degraded
26)
27
28const (
29	mdadmDesc = "raid 0: unknown, 1: normal, 2: failed, 3: degraded"
30	syncDesc  = "percent of spindles synchronization. 100% is fully synced"
31	spinDesc  = "spin 0: failed, 1: active, 2: spare"
32)
33
34type spinState int
35
36// Check individual spindle disks in the array
37const (
38	spinFailed spinState = 0
39	spinActive           = 1
40	spinSpare            = 2
41)
42
43type volumeDetail struct {
44	syncProgress  float32 // progress: between 0..100%
45	state         mdadmState
46	failedSpindle []string
47	activeSpindle []string
48	spareSpindle  []string
49}
50
51func getResync(l string) (progress float32, gotit bool) {
52	prefix := "Rebuild Status : "
53	if !strings.HasPrefix(l, prefix) {
54		return 0, false
55	}
56	l = strings.TrimPrefix(l, prefix)
57	pcentIdx := strings.Index(l, "%")
58	if pcentIdx == -1 {
59		return 0, false
60	}
61
62	f, err := strconv.ParseFloat(l[:pcentIdx], 32)
63	return float32(f), err == nil
64}
65
66func getSpindle(l string) (dev string, gotit bool) {
67	fields := strings.Split(l, " ")
68	size := len(fields)
69	if strings.Contains(fields[size-1], "/dev/sd") {
70		return fields[size-1], true
71	}
72	return "", false
73}
74
75func getState(l string) (state mdadmState, gotit bool) {
76	if !strings.HasPrefix(l, "State : ") {
77		return mdadmUnknown, false
78	}
79	if strings.Contains(l, ", FAILED") {
80		return mdadmFailed, true
81	}
82	if strings.Contains(l, ", degraded") {
83		return mdadmDegraded, true
84	}
85	if strings.Contains(l, "clean") || strings.Contains(l, "active") {
86		return mdadmNormal, true
87	}
88	return mdadmUnknown, false
89}
90
91func parseExamineMdadm(examine io.Reader) (detail volumeDetail) {
92	scanner := bufio.NewScanner(examine)
93	// if there is no progress spotted, assume the disks are in sync
94	detail.syncProgress = 100.0
95	for scanner.Scan() {
96		l := scanner.Text()
97		l = strings.Trim(l, " \t")
98
99		// extract resync status
100		if progress, ok := getResync(l); ok {
101			detail.syncProgress = progress
102		}
103
104		// extract spindles
105		if dev, ok := getSpindle(l); ok {
106			if strings.Contains(l, "active sync") {
107				detail.activeSpindle = append(detail.activeSpindle, dev)
108			} else if strings.Contains(l, "spare") {
109				detail.spareSpindle = append(detail.spareSpindle, dev)
110			} else { // if we don't know, assume it's failed
111				detail.failedSpindle = append(detail.failedSpindle, dev)
112			}
113		}
114
115		// filter State
116		if state, ok := getState(l); ok {
117			detail.state = state
118			// consider failed arrays as 0% resynced
119			if state != mdadmNormal && state != mdadmDegraded {
120				detail.syncProgress = 0.0
121			}
122		}
123	}
124	return detail
125}
126
127func addMetricSpindle(md *opentsdb.MultiDataPoint, names []string, status spinState, volume string) {
128	metric := "linux.disk.mdadm.spindle"
129	for _, name := range names {
130		tags := opentsdb.TagSet{
131			"volume":  volume,
132			"spindle": name,
133		}
134		Add(md, metric, int(status), tags, metadata.Gauge, metadata.StatusCode, spinDesc)
135	}
136}
137
138func addMdadmMetric(md *opentsdb.MultiDataPoint, volume string, detail volumeDetail) {
139	tags := opentsdb.TagSet{"volume": volume}
140	metric := "linux.disk.mdadm.state"
141	Add(md, metric, detail.state, tags, metadata.Gauge, metadata.StatusCode, mdadmDesc)
142
143	metric = "linux.disk.mdadm.sync"
144	Add(md, metric, detail.syncProgress, tags, metadata.Gauge, metadata.Pct, syncDesc)
145
146	addMetricSpindle(md, detail.failedSpindle, spinFailed, volume)
147	addMetricSpindle(md, detail.activeSpindle, spinActive, volume)
148	addMetricSpindle(md, detail.spareSpindle, spinSpare, volume)
149}
150