| // Copyright 2018 The Prometheus Authors |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package procfs |
| |
| import ( |
| "fmt" |
| "io/ioutil" |
| "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 |
| } |
| |
| // MDStat parses an mdstat-file (/proc/mdstat) and returns a slice of |
| // structs containing the relevant info. More information available here: |
| // https://raid.wiki.kernel.org/index.php/Mdstat |
| func (fs FS) MDStat() ([]MDStat, error) { |
| data, err := ioutil.ReadFile(fs.proc.Path("mdstat")) |
| if err != nil { |
| return nil, fmt.Errorf("error parsing mdstat %s: %s", fs.proc.Path("mdstat"), err) |
| } |
| mdstat, err := parseMDStat(data) |
| if err != nil { |
| return nil, fmt.Errorf("error parsing mdstat %s: %s", fs.proc.Path("mdstat"), err) |
| } |
| return mdstat, nil |
| } |
| |
| // parseMDStat parses data from mdstat file (/proc/mdstat) and returns a slice of |
| // structs containing the relevant info. |
| func parseMDStat(mdstatData []byte) ([]MDStat, error) { |
| mdStats := []MDStat{} |
| lines := strings.Split(string(mdstatData), "\n") |
| for i, l := range lines { |
| if strings.TrimSpace(l) == "" || l[0] == ' ' || |
| strings.HasPrefix(l, "Personalities") || strings.HasPrefix(l, "unused") { |
| continue |
| } |
| |
| deviceFields := strings.Fields(l) |
| if len(deviceFields) < 3 { |
| return nil, fmt.Errorf("not enough fields in mdline (expected at least 3): %s", l) |
| } |
| mdName := deviceFields[0] |
| activityState := deviceFields[2] |
| |
| if len(lines) <= i+3 { |
| return mdStats, fmt.Errorf("missing lines for md device %s", mdName) |
| } |
| |
| active, total, size, err := evalStatusLine(lines[i+1]) |
| if err != nil { |
| return nil, err |
| } |
| |
| syncLineIdx := i + 2 |
| if strings.Contains(lines[i+2], "bitmap") { // skip bitmap line |
| syncLineIdx++ |
| } |
| |
| // If device is recovering/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[syncLineIdx], "recovery") || strings.Contains(lines[syncLineIdx], "resync") { |
| syncedBlocks, err = evalRecoveryLine(lines[syncLineIdx]) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| mdStats = append(mdStats, MDStat{ |
| Name: mdName, |
| ActivityState: activityState, |
| DisksActive: active, |
| DisksTotal: total, |
| BlocksTotal: size, |
| BlocksSynced: syncedBlocks, |
| }) |
| } |
| |
| return mdStats, nil |
| } |
| |
| func evalStatusLine(statusline string) (active, total, size int64, err error) { |
| matches := statuslineRE.FindStringSubmatch(statusline) |
| if len(matches) != 4 { |
| return 0, 0, 0, fmt.Errorf("unexpected statusline: %s", statusline) |
| } |
| |
| size, err = strconv.ParseInt(matches[1], 10, 64) |
| if err != nil { |
| return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err) |
| } |
| |
| total, err = strconv.ParseInt(matches[2], 10, 64) |
| if err != nil { |
| return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err) |
| } |
| |
| active, err = strconv.ParseInt(matches[3], 10, 64) |
| if err != nil { |
| return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err) |
| } |
| |
| return active, total, size, nil |
| } |
| |
| func evalRecoveryLine(buildline string) (syncedBlocks int64, err error) { |
| matches := buildlineRE.FindStringSubmatch(buildline) |
| if len(matches) != 2 { |
| return 0, fmt.Errorf("unexpected buildline: %s", buildline) |
| } |
| |
| syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64) |
| if err != nil { |
| return 0, fmt.Errorf("%s in buildline: %s", err, buildline) |
| } |
| |
| return syncedBlocks, nil |
| } |