98 lines
2.7 KiB
Go
98 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
firstLineRegex = regexp.MustCompile(`^(.*?)\s:\s(\w+)?\sraid(\d{1,2})\s((?:[\w\d]+\[\d+\](\s|$))+)`)
|
|
secondLineRegex = regexp.MustCompile(`^\s{6}\d+\sblocks\s(?:super\s\d\.\d\s)?(?:level\s\d,\s)?(?:\d+\w\s\w+, )?(?:\w+\s\d\s)?\[(\d+)\/(\d+)\]\s\[([U_]+)\]`)
|
|
actionLineRegex = regexp.MustCompile(`^\s{6}\[[=>.]{21}\]\s\s(\w+?)\s=\s(\d+?\.\d+?)%\s\(\d+?\/\d+?\)\sfinish=(\d+?\.\d+?)(\w+?)\sspeed=(\d+?)([BKMG])\/sec`)
|
|
|
|
deviceRegex = regexp.MustCompile(`^([\w\d]+)\[(\d+)\]$`)
|
|
)
|
|
|
|
func mdstat(file string, pollInterval time.Duration) <-chan RaidState {
|
|
ch := make(chan RaidState, 4)
|
|
go parseMdstatOutput(file, pollInterval, ch)
|
|
return ch
|
|
}
|
|
|
|
func parseMdstatOutput(file string, pollInterval time.Duration, out chan<- RaidState) {
|
|
defer close(out)
|
|
|
|
ticker := time.NewTicker(pollInterval)
|
|
for range ticker.C {
|
|
data, err := os.ReadFile(file)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
lines := strings.Split(string(data), "\n")
|
|
state := RaidState{}
|
|
for _, line := range lines {
|
|
parseLine(&state, line)
|
|
}
|
|
|
|
out <- state
|
|
}
|
|
}
|
|
|
|
func parseLine(state *RaidState, line string) {
|
|
if matches := firstLineRegex.FindStringSubmatch(line); matches != nil {
|
|
parseFirstLine(state, matches)
|
|
} else if matches := secondLineRegex.FindStringSubmatch(line); matches != nil {
|
|
parseSecondLine(state, matches)
|
|
} else if match := actionLineRegex.FindStringSubmatch(line); match != nil {
|
|
parseActionLine(state, matches)
|
|
}
|
|
}
|
|
|
|
func parseFirstLine(state *RaidState, matches []string) {
|
|
state.Name = matches[1]
|
|
state.State = matches[2]
|
|
state.Level = matches[3]
|
|
|
|
devices := strings.Split(matches[4], " ")
|
|
state.Devices = make([]string, len(devices))
|
|
for deviceIndex, device := range devices {
|
|
if match := deviceRegex.FindStringSubmatch(device); match != nil {
|
|
state.Devices[deviceIndex] = match[1]
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseSecondLine(state *RaidState, matches []string) {
|
|
state.RegisteredDeviceCount, _ = strconv.Atoi(matches[1])
|
|
state.UsedDeviceCount, _ = strconv.Atoi(matches[2])
|
|
state.DevicesUp = map[string]bool{}
|
|
for i, rn := range matches[3] {
|
|
state.DevicesUp[state.Devices[i]] = rn == 'U'
|
|
}
|
|
}
|
|
|
|
func parseActionLine(state *RaidState, matches []string) {
|
|
state.Action = new(RaidAction)
|
|
state.Action.Name = matches[1]
|
|
state.Action.Progress, _ = strconv.ParseFloat(matches[2], 64)
|
|
state.Action.Duration = parseFinished(matches[3], matches[4])
|
|
state.Action.DurationFormatted = state.Action.Duration.String()
|
|
state.Action.Finished = time.Now().Add(state.Action.Duration)
|
|
}
|
|
|
|
func parseFinished(value, unit string) time.Duration {
|
|
switch unit {
|
|
case "sec":
|
|
unit = "s"
|
|
case "min":
|
|
unit = "m"
|
|
}
|
|
|
|
dur, _ := time.ParseDuration(value + unit)
|
|
return dur
|
|
}
|