source: code/trunk/vendor/github.com/prometheus/procfs/mdstat.go@ 822

Last change on this file since 822 was 822, checked in by yakumo.izuru, 22 months ago

Prefer immortal.run over runit and rc.d, use vendored modules
for convenience.

Signed-off-by: Izuru Yakumo <yakumo.izuru@…>

File size: 8.5 KB
RevLine 
[822]1// 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 "os"
19 "regexp"
20 "strconv"
21 "strings"
22)
23
24var (
25 statusLineRE = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[([U_]+)\]`)
26 recoveryLineBlocksRE = regexp.MustCompile(`\((\d+)/\d+\)`)
27 recoveryLinePctRE = regexp.MustCompile(`= (.+)%`)
28 recoveryLineFinishRE = regexp.MustCompile(`finish=(.+)min`)
29 recoveryLineSpeedRE = regexp.MustCompile(`speed=(.+)[A-Z]`)
30 componentDeviceRE = regexp.MustCompile(`(.*)\[\d+\]`)
31)
32
33// MDStat holds info parsed from /proc/mdstat.
34type MDStat struct {
35 // Name of the device.
36 Name string
37 // activity-state of the device.
38 ActivityState string
39 // Number of active disks.
40 DisksActive int64
41 // Total number of disks the device requires.
42 DisksTotal int64
43 // Number of failed disks.
44 DisksFailed int64
45 // Number of "down" disks. (the _ indicator in the status line)
46 DisksDown int64
47 // Spare disks in the device.
48 DisksSpare int64
49 // Number of blocks the device holds.
50 BlocksTotal int64
51 // Number of blocks on the device that are in sync.
52 BlocksSynced int64
53 // progress percentage of current sync
54 BlocksSyncedPct float64
55 // estimated finishing time for current sync (in minutes)
56 BlocksSyncedFinishTime float64
57 // current sync speed (in Kilobytes/sec)
58 BlocksSyncedSpeed float64
59 // Name of md component devices
60 Devices []string
61}
62
63// MDStat parses an mdstat-file (/proc/mdstat) and returns a slice of
64// structs containing the relevant info. More information available here:
65// https://raid.wiki.kernel.org/index.php/Mdstat
66func (fs FS) MDStat() ([]MDStat, error) {
67 data, err := os.ReadFile(fs.proc.Path("mdstat"))
68 if err != nil {
69 return nil, err
70 }
71 mdstat, err := parseMDStat(data)
72 if err != nil {
73 return nil, fmt.Errorf("error parsing mdstat %q: %w", fs.proc.Path("mdstat"), err)
74 }
75 return mdstat, nil
76}
77
78// parseMDStat parses data from mdstat file (/proc/mdstat) and returns a slice of
79// structs containing the relevant info.
80func parseMDStat(mdStatData []byte) ([]MDStat, error) {
81 mdStats := []MDStat{}
82 lines := strings.Split(string(mdStatData), "\n")
83
84 for i, line := range lines {
85 if strings.TrimSpace(line) == "" || line[0] == ' ' ||
86 strings.HasPrefix(line, "Personalities") ||
87 strings.HasPrefix(line, "unused") {
88 continue
89 }
90
91 deviceFields := strings.Fields(line)
92 if len(deviceFields) < 3 {
93 return nil, fmt.Errorf("not enough fields in mdline (expected at least 3): %s", line)
94 }
95 mdName := deviceFields[0] // mdx
96 state := deviceFields[2] // active or inactive
97
98 if len(lines) <= i+3 {
99 return nil, fmt.Errorf("error parsing %q: too few lines for md device", mdName)
100 }
101
102 // Failed disks have the suffix (F) & Spare disks have the suffix (S).
103 fail := int64(strings.Count(line, "(F)"))
104 spare := int64(strings.Count(line, "(S)"))
105 active, total, down, size, err := evalStatusLine(lines[i], lines[i+1])
106
107 if err != nil {
108 return nil, fmt.Errorf("error parsing md device lines: %w", err)
109 }
110
111 syncLineIdx := i + 2
112 if strings.Contains(lines[i+2], "bitmap") { // skip bitmap line
113 syncLineIdx++
114 }
115
116 // If device is syncing at the moment, get the number of currently
117 // synced bytes, otherwise that number equals the size of the device.
118 syncedBlocks := size
119 speed := float64(0)
120 finish := float64(0)
121 pct := float64(0)
122 recovering := strings.Contains(lines[syncLineIdx], "recovery")
123 resyncing := strings.Contains(lines[syncLineIdx], "resync")
124 checking := strings.Contains(lines[syncLineIdx], "check")
125
126 // Append recovery and resyncing state info.
127 if recovering || resyncing || checking {
128 if recovering {
129 state = "recovering"
130 } else if checking {
131 state = "checking"
132 } else {
133 state = "resyncing"
134 }
135
136 // Handle case when resync=PENDING or resync=DELAYED.
137 if strings.Contains(lines[syncLineIdx], "PENDING") ||
138 strings.Contains(lines[syncLineIdx], "DELAYED") {
139 syncedBlocks = 0
140 } else {
141 syncedBlocks, pct, finish, speed, err = evalRecoveryLine(lines[syncLineIdx])
142 if err != nil {
143 return nil, fmt.Errorf("error parsing sync line in md device %q: %w", mdName, err)
144 }
145 }
146 }
147
148 mdStats = append(mdStats, MDStat{
149 Name: mdName,
150 ActivityState: state,
151 DisksActive: active,
152 DisksFailed: fail,
153 DisksDown: down,
154 DisksSpare: spare,
155 DisksTotal: total,
156 BlocksTotal: size,
157 BlocksSynced: syncedBlocks,
158 BlocksSyncedPct: pct,
159 BlocksSyncedFinishTime: finish,
160 BlocksSyncedSpeed: speed,
161 Devices: evalComponentDevices(deviceFields),
162 })
163 }
164
165 return mdStats, nil
166}
167
168func evalStatusLine(deviceLine, statusLine string) (active, total, down, size int64, err error) {
169 statusFields := strings.Fields(statusLine)
170 if len(statusFields) < 1 {
171 return 0, 0, 0, 0, fmt.Errorf("unexpected statusLine %q", statusLine)
172 }
173
174 sizeStr := statusFields[0]
175 size, err = strconv.ParseInt(sizeStr, 10, 64)
176 if err != nil {
177 return 0, 0, 0, 0, fmt.Errorf("unexpected statusLine %q: %w", statusLine, err)
178 }
179
180 if strings.Contains(deviceLine, "raid0") || strings.Contains(deviceLine, "linear") {
181 // In the device deviceLine, only disks have a number associated with them in [].
182 total = int64(strings.Count(deviceLine, "["))
183 return total, total, 0, size, nil
184 }
185
186 if strings.Contains(deviceLine, "inactive") {
187 return 0, 0, 0, size, nil
188 }
189
190 matches := statusLineRE.FindStringSubmatch(statusLine)
191 if len(matches) != 5 {
192 return 0, 0, 0, 0, fmt.Errorf("couldn't find all the substring matches: %s", statusLine)
193 }
194
195 total, err = strconv.ParseInt(matches[2], 10, 64)
196 if err != nil {
197 return 0, 0, 0, 0, fmt.Errorf("unexpected statusLine %q: %w", statusLine, err)
198 }
199
200 active, err = strconv.ParseInt(matches[3], 10, 64)
201 if err != nil {
202 return 0, 0, 0, 0, fmt.Errorf("unexpected statusLine %q: %w", statusLine, err)
203 }
204 down = int64(strings.Count(matches[4], "_"))
205
206 return active, total, down, size, nil
207}
208
209func evalRecoveryLine(recoveryLine string) (syncedBlocks int64, pct float64, finish float64, speed float64, err error) {
210 matches := recoveryLineBlocksRE.FindStringSubmatch(recoveryLine)
211 if len(matches) != 2 {
212 return 0, 0, 0, 0, fmt.Errorf("unexpected recoveryLine: %s", recoveryLine)
213 }
214
215 syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64)
216 if err != nil {
217 return 0, 0, 0, 0, fmt.Errorf("error parsing int from recoveryLine %q: %w", recoveryLine, err)
218 }
219
220 // Get percentage complete
221 matches = recoveryLinePctRE.FindStringSubmatch(recoveryLine)
222 if len(matches) != 2 {
223 return syncedBlocks, 0, 0, 0, fmt.Errorf("unexpected recoveryLine matching percentage: %s", recoveryLine)
224 }
225 pct, err = strconv.ParseFloat(strings.TrimSpace(matches[1]), 64)
226 if err != nil {
227 return syncedBlocks, 0, 0, 0, fmt.Errorf("error parsing float from recoveryLine %q: %w", recoveryLine, err)
228 }
229
230 // Get time expected left to complete
231 matches = recoveryLineFinishRE.FindStringSubmatch(recoveryLine)
232 if len(matches) != 2 {
233 return syncedBlocks, pct, 0, 0, fmt.Errorf("unexpected recoveryLine matching est. finish time: %s", recoveryLine)
234 }
235 finish, err = strconv.ParseFloat(matches[1], 64)
236 if err != nil {
237 return syncedBlocks, pct, 0, 0, fmt.Errorf("error parsing float from recoveryLine %q: %w", recoveryLine, err)
238 }
239
240 // Get recovery speed
241 matches = recoveryLineSpeedRE.FindStringSubmatch(recoveryLine)
242 if len(matches) != 2 {
243 return syncedBlocks, pct, finish, 0, fmt.Errorf("unexpected recoveryLine matching speed: %s", recoveryLine)
244 }
245 speed, err = strconv.ParseFloat(matches[1], 64)
246 if err != nil {
247 return syncedBlocks, pct, finish, 0, fmt.Errorf("error parsing float from recoveryLine %q: %w", recoveryLine, err)
248 }
249
250 return syncedBlocks, pct, finish, speed, nil
251}
252
253func evalComponentDevices(deviceFields []string) []string {
254 mdComponentDevices := make([]string, 0)
255 if len(deviceFields) > 3 {
256 for _, field := range deviceFields[4:] {
257 match := componentDeviceRE.FindStringSubmatch(field)
258 if match == nil {
259 continue
260 }
261 mdComponentDevices = append(mdComponentDevices, match[1])
262 }
263 }
264
265 return mdComponentDevices
266}
Note: See TracBrowser for help on using the repository browser.