Skip to content

Commit 82e8db0

Browse files
authored
Merge pull request #1878 from Hexta/zfs_zpool_state
Expose zfs zpool state
2 parents e422cda + 519203e commit 82e8db0

File tree

6 files changed

+144
-4
lines changed

6 files changed

+144
-4
lines changed

collector/fixtures/e2e-output.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3827,6 +3827,20 @@ node_zfs_zpool_rtime{zpool="poolz1"} 9.82909164e+09
38273827
# TYPE node_zfs_zpool_rupdate untyped
38283828
node_zfs_zpool_rupdate{zpool="pool1"} 7.921048984922e+13
38293829
node_zfs_zpool_rupdate{zpool="poolz1"} 1.10734831944501e+14
3830+
# HELP node_zfs_zpool_state kstat.zfs.misc.state
3831+
# TYPE node_zfs_zpool_state gauge
3832+
node_zfs_zpool_state{state="degraded",zpool="pool1"} 0
3833+
node_zfs_zpool_state{state="degraded",zpool="poolz1"} 1
3834+
node_zfs_zpool_state{state="faulted",zpool="pool1"} 0
3835+
node_zfs_zpool_state{state="faulted",zpool="poolz1"} 0
3836+
node_zfs_zpool_state{state="offline",zpool="pool1"} 0
3837+
node_zfs_zpool_state{state="offline",zpool="poolz1"} 0
3838+
node_zfs_zpool_state{state="online",zpool="pool1"} 1
3839+
node_zfs_zpool_state{state="online",zpool="poolz1"} 0
3840+
node_zfs_zpool_state{state="removed",zpool="pool1"} 0
3841+
node_zfs_zpool_state{state="removed",zpool="poolz1"} 0
3842+
node_zfs_zpool_state{state="unavail",zpool="pool1"} 0
3843+
node_zfs_zpool_state{state="unavail",zpool="poolz1"} 0
38303844
# HELP node_zfs_zpool_wcnt kstat.zfs.misc.io.wcnt
38313845
# TYPE node_zfs_zpool_wcnt untyped
38323846
node_zfs_zpool_wcnt{zpool="pool1"} 0
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ONLINE
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DEGRADED

collector/zfs.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type zfsCollector struct {
3737
linuxProcpathBase string
3838
linuxZpoolIoPath string
3939
linuxZpoolObjsetPath string
40+
linuxZpoolStatePath string
4041
linuxPathMap map[string]string
4142
logger log.Logger
4243
}
@@ -47,6 +48,7 @@ func NewZFSCollector(logger log.Logger) (Collector, error) {
4748
linuxProcpathBase: "spl/kstat/zfs",
4849
linuxZpoolIoPath: "/*/io",
4950
linuxZpoolObjsetPath: "/*/objset-*",
51+
linuxZpoolStatePath: "/*/state",
5052
linuxPathMap: map[string]string{
5153
"zfs_abd": "abdstats",
5254
"zfs_arc": "arcstats",
@@ -132,3 +134,18 @@ func (c *zfsCollector) constPoolObjsetMetric(poolName string, datasetName string
132134
datasetName,
133135
)
134136
}
137+
138+
func (c *zfsCollector) constPoolStateMetric(poolName string, stateName string, isActive uint64) prometheus.Metric {
139+
return prometheus.MustNewConstMetric(
140+
prometheus.NewDesc(
141+
prometheus.BuildFQName(namespace, "zfs_zpool", "state"),
142+
"kstat.zfs.misc.state",
143+
[]string{"zpool", "state"},
144+
nil,
145+
),
146+
prometheus.GaugeValue,
147+
float64(isActive),
148+
poolName,
149+
stateName,
150+
)
151+
}

collector/zfs_linux.go

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ const (
4141
// kstatDataString = "7"
4242
)
4343

44+
var zfsPoolStatesName = []string{"online", "degraded", "faulted", "offline", "removed", "unavail"}
45+
4446
func (c *zfsCollector) openProcFile(path string) (*os.File, error) {
4547
file, err := os.Open(procFilePath(path))
4648
if err != nil {
@@ -97,10 +99,6 @@ func (c *zfsCollector) updatePoolStats(ch chan<- prometheus.Metric) error {
9799
return err
98100
}
99101

100-
if zpoolObjsetPaths == nil {
101-
return nil
102-
}
103-
104102
for _, zpoolPath := range zpoolObjsetPaths {
105103
file, err := os.Open(zpoolPath)
106104
if err != nil {
@@ -117,6 +115,34 @@ func (c *zfsCollector) updatePoolStats(ch chan<- prometheus.Metric) error {
117115
return err
118116
}
119117
}
118+
119+
zpoolStatePaths, err := filepath.Glob(procFilePath(filepath.Join(c.linuxProcpathBase, c.linuxZpoolStatePath)))
120+
if err != nil {
121+
return err
122+
}
123+
124+
if zpoolStatePaths == nil {
125+
level.Warn(c.logger).Log("msg", "Not found pool state files")
126+
}
127+
128+
for _, zpoolPath := range zpoolStatePaths {
129+
file, err := os.Open(zpoolPath)
130+
if err != nil {
131+
// this file should exist, but there is a race where an exporting pool can remove the files -- ok to ignore
132+
level.Debug(c.logger).Log("msg", "Cannot open file for reading", "path", zpoolPath)
133+
return errZFSNotAvailable
134+
}
135+
136+
err = c.parsePoolStateFile(file, zpoolPath, func(poolName string, stateName string, isActive uint64) {
137+
ch <- c.constPoolStateMetric(poolName, stateName, isActive)
138+
})
139+
140+
file.Close()
141+
if err != nil {
142+
return err
143+
}
144+
}
145+
120146
return nil
121147
}
122148

@@ -235,3 +261,35 @@ func (c *zfsCollector) parsePoolObjsetFile(reader io.Reader, zpoolPath string, h
235261

236262
return scanner.Err()
237263
}
264+
265+
func (c *zfsCollector) parsePoolStateFile(reader io.Reader, zpoolPath string, handler func(string, string, uint64)) error {
266+
scanner := bufio.NewScanner(reader)
267+
scanner.Scan()
268+
269+
actualStateName, err := scanner.Text(), scanner.Err()
270+
if err != nil {
271+
return err
272+
}
273+
274+
actualStateName = strings.ToLower(actualStateName)
275+
276+
zpoolPathElements := strings.Split(zpoolPath, "/")
277+
pathLen := len(zpoolPathElements)
278+
if pathLen < 2 {
279+
return fmt.Errorf("zpool path did not return at least two elements")
280+
}
281+
282+
zpoolName := zpoolPathElements[pathLen-2]
283+
284+
for _, stateName := range zfsPoolStatesName {
285+
isActive := uint64(0)
286+
287+
if actualStateName == stateName {
288+
isActive = 1
289+
}
290+
291+
handler(zpoolName, stateName, isActive)
292+
}
293+
294+
return nil
295+
}

collector/zfs_linux_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,3 +494,52 @@ func TestVdevMirrorstatsParsing(t *testing.T) {
494494
t.Fatal("VdevMirrorStats parsing handler was not called for some expected sysctls")
495495
}
496496
}
497+
498+
func TestPoolStateParsing(t *testing.T) {
499+
zpoolPaths, err := filepath.Glob("fixtures/proc/spl/kstat/zfs/*/state")
500+
if err != nil {
501+
t.Fatal(err)
502+
}
503+
504+
c := zfsCollector{}
505+
if err != nil {
506+
t.Fatal(err)
507+
}
508+
509+
handlerCalled := false
510+
for _, zpoolPath := range zpoolPaths {
511+
file, err := os.Open(zpoolPath)
512+
if err != nil {
513+
t.Fatal(err)
514+
}
515+
516+
err = c.parsePoolStateFile(file, zpoolPath, func(poolName string, stateName string, isActive uint64) {
517+
handlerCalled = true
518+
519+
if poolName == "pool1" {
520+
if isActive != uint64(1) && stateName == "online" {
521+
t.Fatalf("Incorrect parsed value for online state")
522+
}
523+
if isActive != uint64(0) && stateName != "online" {
524+
t.Fatalf("Incorrect parsed value for online state")
525+
}
526+
}
527+
if poolName == "poolz1" {
528+
if isActive != uint64(1) && stateName == "degraded" {
529+
t.Fatalf("Incorrect parsed value for degraded state")
530+
}
531+
if isActive != uint64(0) && stateName != "degraded" {
532+
t.Fatalf("Incorrect parsed value for degraded state")
533+
}
534+
}
535+
})
536+
file.Close()
537+
if err != nil {
538+
t.Fatal(err)
539+
}
540+
}
541+
if !handlerCalled {
542+
t.Fatal("Zpool parsing handler was not called for some expected sysctls")
543+
}
544+
545+
}

0 commit comments

Comments
 (0)