blob: 4c4493bfa5052afd2521dc128f4bfcc6c5034bb7 [file] [log] [blame]
khenaidoo59ce9dd2019-11-11 13:05:32 -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 (
khenaidoo26721882021-08-11 17:42:52 -040025 statusLineRE = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[[U_]+\]`)
26 recoveryLineRE = regexp.MustCompile(`\((\d+)/\d+\)`)
27 componentDeviceRE = regexp.MustCompile(`(.*)\[\d+\]`)
khenaidoo59ce9dd2019-11-11 13:05:32 -050028)
29
30// MDStat holds info parsed from /proc/mdstat.
31type MDStat struct {
32 // Name of the device.
33 Name string
34 // activity-state of the device.
35 ActivityState string
36 // Number of active disks.
37 DisksActive int64
khenaidoo26721882021-08-11 17:42:52 -040038 // Total number of disks the device requires.
khenaidoo59ce9dd2019-11-11 13:05:32 -050039 DisksTotal int64
khenaidoo26721882021-08-11 17:42:52 -040040 // Number of failed disks.
41 DisksFailed int64
42 // Spare disks in the device.
43 DisksSpare int64
khenaidoo59ce9dd2019-11-11 13:05:32 -050044 // Number of blocks the device holds.
45 BlocksTotal int64
46 // Number of blocks on the device that are in sync.
47 BlocksSynced int64
khenaidoo26721882021-08-11 17:42:52 -040048 // Name of md component devices
49 Devices []string
khenaidoo59ce9dd2019-11-11 13:05:32 -050050}
51
52// MDStat parses an mdstat-file (/proc/mdstat) and returns a slice of
53// structs containing the relevant info. More information available here:
54// https://raid.wiki.kernel.org/index.php/Mdstat
55func (fs FS) MDStat() ([]MDStat, error) {
56 data, err := ioutil.ReadFile(fs.proc.Path("mdstat"))
57 if err != nil {
khenaidoo26721882021-08-11 17:42:52 -040058 return nil, err
khenaidoo59ce9dd2019-11-11 13:05:32 -050059 }
60 mdstat, err := parseMDStat(data)
61 if err != nil {
khenaidoo26721882021-08-11 17:42:52 -040062 return nil, fmt.Errorf("error parsing mdstat %q: %w", fs.proc.Path("mdstat"), err)
khenaidoo59ce9dd2019-11-11 13:05:32 -050063 }
64 return mdstat, nil
65}
66
67// parseMDStat parses data from mdstat file (/proc/mdstat) and returns a slice of
68// structs containing the relevant info.
khenaidoo26721882021-08-11 17:42:52 -040069func parseMDStat(mdStatData []byte) ([]MDStat, error) {
khenaidoo59ce9dd2019-11-11 13:05:32 -050070 mdStats := []MDStat{}
khenaidoo26721882021-08-11 17:42:52 -040071 lines := strings.Split(string(mdStatData), "\n")
72
73 for i, line := range lines {
74 if strings.TrimSpace(line) == "" || line[0] == ' ' ||
75 strings.HasPrefix(line, "Personalities") ||
76 strings.HasPrefix(line, "unused") {
khenaidoo59ce9dd2019-11-11 13:05:32 -050077 continue
78 }
79
khenaidoo26721882021-08-11 17:42:52 -040080 deviceFields := strings.Fields(line)
khenaidoo59ce9dd2019-11-11 13:05:32 -050081 if len(deviceFields) < 3 {
khenaidoo26721882021-08-11 17:42:52 -040082 return nil, fmt.Errorf("not enough fields in mdline (expected at least 3): %s", line)
khenaidoo59ce9dd2019-11-11 13:05:32 -050083 }
khenaidoo26721882021-08-11 17:42:52 -040084 mdName := deviceFields[0] // mdx
85 state := deviceFields[2] // active or inactive
khenaidoo59ce9dd2019-11-11 13:05:32 -050086
87 if len(lines) <= i+3 {
khenaidoo26721882021-08-11 17:42:52 -040088 return nil, fmt.Errorf("error parsing %q: too few lines for md device", mdName)
khenaidoo59ce9dd2019-11-11 13:05:32 -050089 }
90
khenaidoo26721882021-08-11 17:42:52 -040091 // Failed disks have the suffix (F) & Spare disks have the suffix (S).
92 fail := int64(strings.Count(line, "(F)"))
93 spare := int64(strings.Count(line, "(S)"))
94 active, total, size, err := evalStatusLine(lines[i], lines[i+1])
95
khenaidoo59ce9dd2019-11-11 13:05:32 -050096 if err != nil {
khenaidoo26721882021-08-11 17:42:52 -040097 return nil, fmt.Errorf("error parsing md device lines: %w", err)
khenaidoo59ce9dd2019-11-11 13:05:32 -050098 }
99
100 syncLineIdx := i + 2
101 if strings.Contains(lines[i+2], "bitmap") { // skip bitmap line
102 syncLineIdx++
103 }
104
khenaidoo26721882021-08-11 17:42:52 -0400105 // If device is syncing at the moment, get the number of currently
khenaidoo59ce9dd2019-11-11 13:05:32 -0500106 // synced bytes, otherwise that number equals the size of the device.
107 syncedBlocks := size
khenaidoo26721882021-08-11 17:42:52 -0400108 recovering := strings.Contains(lines[syncLineIdx], "recovery")
109 resyncing := strings.Contains(lines[syncLineIdx], "resync")
110 checking := strings.Contains(lines[syncLineIdx], "check")
111
112 // Append recovery and resyncing state info.
113 if recovering || resyncing || checking {
114 if recovering {
115 state = "recovering"
116 } else if checking {
117 state = "checking"
118 } else {
119 state = "resyncing"
120 }
121
122 // Handle case when resync=PENDING or resync=DELAYED.
123 if strings.Contains(lines[syncLineIdx], "PENDING") ||
124 strings.Contains(lines[syncLineIdx], "DELAYED") {
125 syncedBlocks = 0
126 } else {
127 syncedBlocks, err = evalRecoveryLine(lines[syncLineIdx])
128 if err != nil {
129 return nil, fmt.Errorf("error parsing sync line in md device %q: %w", mdName, err)
130 }
khenaidoo59ce9dd2019-11-11 13:05:32 -0500131 }
132 }
133
134 mdStats = append(mdStats, MDStat{
135 Name: mdName,
khenaidoo26721882021-08-11 17:42:52 -0400136 ActivityState: state,
khenaidoo59ce9dd2019-11-11 13:05:32 -0500137 DisksActive: active,
khenaidoo26721882021-08-11 17:42:52 -0400138 DisksFailed: fail,
139 DisksSpare: spare,
khenaidoo59ce9dd2019-11-11 13:05:32 -0500140 DisksTotal: total,
141 BlocksTotal: size,
142 BlocksSynced: syncedBlocks,
khenaidoo26721882021-08-11 17:42:52 -0400143 Devices: evalComponentDevices(deviceFields),
khenaidoo59ce9dd2019-11-11 13:05:32 -0500144 })
145 }
146
147 return mdStats, nil
148}
149
khenaidoo26721882021-08-11 17:42:52 -0400150func evalStatusLine(deviceLine, statusLine string) (active, total, size int64, err error) {
151
152 sizeStr := strings.Fields(statusLine)[0]
153 size, err = strconv.ParseInt(sizeStr, 10, 64)
154 if err != nil {
155 return 0, 0, 0, fmt.Errorf("unexpected statusLine %q: %w", statusLine, err)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500156 }
157
khenaidoo26721882021-08-11 17:42:52 -0400158 if strings.Contains(deviceLine, "raid0") || strings.Contains(deviceLine, "linear") {
159 // In the device deviceLine, only disks have a number associated with them in [].
160 total = int64(strings.Count(deviceLine, "["))
161 return total, total, size, nil
162 }
163
164 if strings.Contains(deviceLine, "inactive") {
165 return 0, 0, size, nil
166 }
167
168 matches := statusLineRE.FindStringSubmatch(statusLine)
169 if len(matches) != 4 {
170 return 0, 0, 0, fmt.Errorf("couldn't find all the substring matches: %s", statusLine)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500171 }
172
173 total, err = strconv.ParseInt(matches[2], 10, 64)
174 if err != nil {
khenaidoo26721882021-08-11 17:42:52 -0400175 return 0, 0, 0, fmt.Errorf("unexpected statusLine %q: %w", statusLine, err)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500176 }
177
178 active, err = strconv.ParseInt(matches[3], 10, 64)
179 if err != nil {
khenaidoo26721882021-08-11 17:42:52 -0400180 return 0, 0, 0, fmt.Errorf("unexpected statusLine %q: %w", statusLine, err)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500181 }
182
183 return active, total, size, nil
184}
185
khenaidoo26721882021-08-11 17:42:52 -0400186func evalRecoveryLine(recoveryLine string) (syncedBlocks int64, err error) {
187 matches := recoveryLineRE.FindStringSubmatch(recoveryLine)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500188 if len(matches) != 2 {
khenaidoo26721882021-08-11 17:42:52 -0400189 return 0, fmt.Errorf("unexpected recoveryLine: %s", recoveryLine)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500190 }
191
192 syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64)
193 if err != nil {
khenaidoo26721882021-08-11 17:42:52 -0400194 return 0, fmt.Errorf("error parsing int from recoveryLine %q: %w", recoveryLine, err)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500195 }
196
197 return syncedBlocks, nil
198}
khenaidoo26721882021-08-11 17:42:52 -0400199
200func evalComponentDevices(deviceFields []string) []string {
201 mdComponentDevices := make([]string, 0)
202 if len(deviceFields) > 3 {
203 for _, field := range deviceFields[4:] {
204 match := componentDeviceRE.FindStringSubmatch(field)
205 if match == nil {
206 continue
207 }
208 mdComponentDevices = append(mdComponentDevices, match[1])
209 }
210 }
211
212 return mdComponentDevices
213}