blob: 71c10678250d44aef259cb72216063fcae8cefe7 [file] [log] [blame]
khenaidooab1f7bd2019-11-14 14:00:27 -05001// Copyright 2018 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package procfs
15
16import (
17 "fmt"
18 "io/ioutil"
19 "regexp"
20 "strconv"
21 "strings"
22)
23
24var (
25 statuslineRE = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[[U_]+\]`)
26 buildlineRE = regexp.MustCompile(`\((\d+)/\d+\)`)
27)
28
29// MDStat holds info parsed from /proc/mdstat.
30type MDStat struct {
31 // Name of the device.
32 Name string
33 // activity-state of the device.
34 ActivityState string
35 // Number of active disks.
36 DisksActive int64
37 // Total number of disks the device consists of.
38 DisksTotal int64
39 // Number of blocks the device holds.
40 BlocksTotal int64
41 // Number of blocks on the device that are in sync.
42 BlocksSynced int64
43}
44
45// MDStat parses an mdstat-file (/proc/mdstat) and returns a slice of
46// structs containing the relevant info. More information available here:
47// https://raid.wiki.kernel.org/index.php/Mdstat
48func (fs FS) MDStat() ([]MDStat, error) {
49 data, err := ioutil.ReadFile(fs.proc.Path("mdstat"))
50 if err != nil {
51 return nil, fmt.Errorf("error parsing mdstat %s: %s", fs.proc.Path("mdstat"), err)
52 }
53 mdstat, err := parseMDStat(data)
54 if err != nil {
55 return nil, fmt.Errorf("error parsing mdstat %s: %s", fs.proc.Path("mdstat"), err)
56 }
57 return mdstat, nil
58}
59
60// parseMDStat parses data from mdstat file (/proc/mdstat) and returns a slice of
61// structs containing the relevant info.
62func parseMDStat(mdstatData []byte) ([]MDStat, error) {
63 mdStats := []MDStat{}
64 lines := strings.Split(string(mdstatData), "\n")
65 for i, l := range lines {
66 if strings.TrimSpace(l) == "" || l[0] == ' ' ||
67 strings.HasPrefix(l, "Personalities") || strings.HasPrefix(l, "unused") {
68 continue
69 }
70
71 deviceFields := strings.Fields(l)
72 if len(deviceFields) < 3 {
73 return nil, fmt.Errorf("not enough fields in mdline (expected at least 3): %s", l)
74 }
75 mdName := deviceFields[0]
76 activityState := deviceFields[2]
77
78 if len(lines) <= i+3 {
79 return mdStats, fmt.Errorf("missing lines for md device %s", mdName)
80 }
81
82 active, total, size, err := evalStatusLine(lines[i+1])
83 if err != nil {
84 return nil, err
85 }
86
87 syncLineIdx := i + 2
88 if strings.Contains(lines[i+2], "bitmap") { // skip bitmap line
89 syncLineIdx++
90 }
91
92 // If device is recovering/syncing at the moment, get the number of currently
93 // synced bytes, otherwise that number equals the size of the device.
94 syncedBlocks := size
95 if strings.Contains(lines[syncLineIdx], "recovery") || strings.Contains(lines[syncLineIdx], "resync") {
96 syncedBlocks, err = evalRecoveryLine(lines[syncLineIdx])
97 if err != nil {
98 return nil, err
99 }
100 }
101
102 mdStats = append(mdStats, MDStat{
103 Name: mdName,
104 ActivityState: activityState,
105 DisksActive: active,
106 DisksTotal: total,
107 BlocksTotal: size,
108 BlocksSynced: syncedBlocks,
109 })
110 }
111
112 return mdStats, nil
113}
114
115func evalStatusLine(statusline string) (active, total, size int64, err error) {
116 matches := statuslineRE.FindStringSubmatch(statusline)
117 if len(matches) != 4 {
118 return 0, 0, 0, fmt.Errorf("unexpected statusline: %s", statusline)
119 }
120
121 size, err = strconv.ParseInt(matches[1], 10, 64)
122 if err != nil {
123 return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err)
124 }
125
126 total, err = strconv.ParseInt(matches[2], 10, 64)
127 if err != nil {
128 return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err)
129 }
130
131 active, err = strconv.ParseInt(matches[3], 10, 64)
132 if err != nil {
133 return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err)
134 }
135
136 return active, total, size, nil
137}
138
139func evalRecoveryLine(buildline string) (syncedBlocks int64, err error) {
140 matches := buildlineRE.FindStringSubmatch(buildline)
141 if len(matches) != 2 {
142 return 0, fmt.Errorf("unexpected buildline: %s", buildline)
143 }
144
145 syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64)
146 if err != nil {
147 return 0, fmt.Errorf("%s in buildline: %s", err, buildline)
148 }
149
150 return syncedBlocks, nil
151}