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 }