123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- 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
- }
|