158 lines
4.4 KiB
Go
158 lines
4.4 KiB
Go
package procfs
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"path"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
statuslineRE = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[[U_]+\]`)
|
|
buildlineRE = regexp.MustCompile(`\((\d+)/\d+\)`)
|
|
)
|
|
|
|
// MDStat holds info parsed from /proc/mdstat.
|
|
type MDStat struct {
|
|
// Name of the device.
|
|
Name string
|
|
// activity-state of the device.
|
|
ActivityState string
|
|
// Number of active disks.
|
|
DisksActive int64
|
|
// Total number of disks the device consists of.
|
|
DisksTotal int64
|
|
// Number of blocks the device holds.
|
|
BlocksTotal int64
|
|
// Number of blocks on the device that are in sync.
|
|
BlocksSynced int64
|
|
}
|
|
|
|
// ParseMDStat parses an mdstat-file and returns a struct with the relevant infos.
|
|
func (fs FS) ParseMDStat() (mdstates []MDStat, err error) {
|
|
mdStatusFilePath := path.Join(string(fs), "mdstat")
|
|
content, err := ioutil.ReadFile(mdStatusFilePath)
|
|
if err != nil {
|
|
return []MDStat{}, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err)
|
|
}
|
|
|
|
mdStatusFile := string(content)
|
|
|
|
lines := strings.Split(mdStatusFile, "\n")
|
|
var currentMD string
|
|
|
|
// Each md has at least the deviceline, statusline and one empty line afterwards
|
|
// so we will have probably something of the order len(lines)/3 devices
|
|
// so we use that for preallocation.
|
|
estimateMDs := len(lines) / 3
|
|
mdStates := make([]MDStat, 0, estimateMDs)
|
|
|
|
for i, l := range lines {
|
|
if l == "" {
|
|
// Skip entirely empty lines.
|
|
continue
|
|
}
|
|
|
|
if l[0] == ' ' {
|
|
// Those lines are not the beginning of a md-section.
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(l, "Personalities") || strings.HasPrefix(l, "unused") {
|
|
// We aren't interested in lines with general info.
|
|
continue
|
|
}
|
|
|
|
mainLine := strings.Split(l, " ")
|
|
if len(mainLine) < 3 {
|
|
return mdStates, fmt.Errorf("error parsing mdline: %s", l)
|
|
}
|
|
currentMD = mainLine[0] // name of md-device
|
|
activityState := mainLine[2] // activity status of said md-device
|
|
|
|
if len(lines) <= i+3 {
|
|
return mdStates, fmt.Errorf("error parsing %s: entry for %s has fewer lines than expected", mdStatusFilePath, currentMD)
|
|
}
|
|
|
|
active, total, size, err := evalStatusline(lines[i+1]) // parse statusline, always present
|
|
if err != nil {
|
|
return mdStates, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err)
|
|
}
|
|
|
|
//
|
|
// Now get the number of synced blocks.
|
|
//
|
|
|
|
// Get the line number of the syncing-line.
|
|
var j int
|
|
if strings.Contains(lines[i+2], "bitmap") { // then skip the bitmap line
|
|
j = i + 3
|
|
} else {
|
|
j = i + 2
|
|
}
|
|
|
|
// If device is syncing at the moment, get the number of currently synced bytes,
|
|
// otherwise that number equals the size of the device.
|
|
syncedBlocks := size
|
|
if strings.Contains(lines[j], "recovery") || strings.Contains(lines[j], "resync") {
|
|
syncedBlocks, err = evalBuildline(lines[j])
|
|
if err != nil {
|
|
return mdStates, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err)
|
|
}
|
|
}
|
|
|
|
mdStates = append(mdStates, MDStat{currentMD, activityState, active, total, size, syncedBlocks})
|
|
|
|
}
|
|
|
|
return mdStates, nil
|
|
}
|
|
|
|
func evalStatusline(statusline string) (active, total, size int64, err error) {
|
|
matches := statuslineRE.FindStringSubmatch(statusline)
|
|
|
|
// +1 to make it more obvious that the whole string containing the info is also returned as matches[0].
|
|
if len(matches) != 3+1 {
|
|
return 0, 0, 0, fmt.Errorf("unexpected number matches found in statusline: %s", statusline)
|
|
}
|
|
|
|
size, err = strconv.ParseInt(matches[1], 10, 64)
|
|
if err != nil {
|
|
return 0, 0, 0, fmt.Errorf("%s in statusline: %s", err, statusline)
|
|
}
|
|
|
|
total, err = strconv.ParseInt(matches[2], 10, 64)
|
|
if err != nil {
|
|
return 0, 0, 0, fmt.Errorf("%s in statusline: %s", err, statusline)
|
|
}
|
|
|
|
active, err = strconv.ParseInt(matches[3], 10, 64)
|
|
if err != nil {
|
|
return 0, 0, 0, fmt.Errorf("%s in statusline: %s", err, statusline)
|
|
}
|
|
|
|
return active, total, size, nil
|
|
}
|
|
|
|
// Gets the size that has already been synced out of the sync-line.
|
|
func evalBuildline(buildline string) (int64, error) {
|
|
matches := buildlineRE.FindStringSubmatch(buildline)
|
|
|
|
// +1 to make it more obvious that the whole string containing the info is also returned as matches[0].
|
|
if len(matches) < 1+1 {
|
|
return 0, fmt.Errorf("too few matches found in buildline: %s", buildline)
|
|
}
|
|
|
|
if len(matches) > 1+1 {
|
|
return 0, fmt.Errorf("too many matches found in buildline: %s", buildline)
|
|
}
|
|
|
|
syncedSize, err := strconv.ParseInt(matches[1], 10, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("%s in buildline: %s", err, buildline)
|
|
}
|
|
|
|
return syncedSize, nil
|
|
}
|