From 3c989a7b1b8a0d85332e4832a32dd894d8195419 Mon Sep 17 00:00:00 2001 From: trearcul Date: Tue, 15 Sep 2020 13:59:38 +0200 Subject: [PATCH] MDStat enrichment Added parsing of - RAID personality - Sync/check percent and remaining minutes - Assigned partitions/devices Expected test data updated accordingly Signed-off-by: trearcul --- fixtures.ttar | 2 +- mdstat.go | 124 +++++++++-- mdstat_test.go | 558 +++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 643 insertions(+), 41 deletions(-) diff --git a/fixtures.ttar b/fixtures.ttar index 12494d742..9d651be56 100644 --- a/fixtures.ttar +++ b/fixtures.ttar @@ -2004,7 +2004,7 @@ md9 : active raid1 sdc2[2] sdd2[3] sdb2[1] sda2[0] sde[4](F) sdf[5](F) sdg[6](S) md10 : active raid0 sda1[0] sdb1[1] 314159265 blocks 64k chunks -md11 : active (auto-read-only) raid1 sdb2[0] sdc2[1] sdc3[2](F) hda[4](S) ssdc2[3](S) +md11 : active (auto-read-only) raid1 sdb2[0] sdc2[1] sdc3[2](F) hdb[4](S) ssdc2[3](S) 4190208 blocks super 1.2 [2/2] [UU] resync=PENDING diff --git a/mdstat.go b/mdstat.go index 98e37aa8c..41ccdb636 100644 --- a/mdstat.go +++ b/mdstat.go @@ -22,16 +22,26 @@ import ( ) var ( + deviceLineRE = regexp.MustCompile(`(\w+\d+) : (\w+) ?(?:\(.+?\))? ?(\w+)? ((?:\w+\d*\[\d+\](?:\(\w\))? ?)+)`) statusLineRE = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[[U_]+\]`) - recoveryLineRE = regexp.MustCompile(`\((\d+)/\d+\)`) + recoveryLineRE = regexp.MustCompile(`(?:(\d{1,3}\.\d)%) \((\d+)/\d+\).+?(\d+\.\d)min`) + devicesStrRE = regexp.MustCompile(`(\w+\d*)\[(\d+)\](?:\((\w)\))?`) ) +type MDAssignedDevice struct { + Name string + Role int64 + State string +} + // MDStat holds info parsed from /proc/mdstat. type MDStat struct { // Name of the device. Name string // activity-state of the device. ActivityState string + // Raid personality of the device. + Personality string // Number of active disks. DisksActive int64 // Total number of disks the device requires. @@ -44,6 +54,12 @@ type MDStat struct { BlocksTotal int64 // Number of blocks on the device that are in sync. BlocksSynced int64 + // Percentage of blocks on the device that are in sync. + PercentSynced float64 + // Remaining minutes to complete sync + RemainingSyncMinutes float64 + // List of assigned devices + AssignedDevices []MDAssignedDevice } // MDStat parses an mdstat-file (/proc/mdstat) and returns a slice of @@ -74,12 +90,25 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { continue } - deviceFields := strings.Fields(line) - if len(deviceFields) < 3 { - return nil, fmt.Errorf("not enough fields in mdline (expected at least 3): %s", line) + matches := deviceLineRE.FindStringSubmatch(line) + if len(matches) < 4 { + return nil, fmt.Errorf("not enough fields in mdline (expected at least 4): %s", line) + } + mdName := matches[1] // mdx + state := matches[2] // active or inactive + var personality string + assignedDevicesStr := matches[3] + + if len(matches) == 5 { + personality = matches[3] + assignedDevicesStr = matches[4] + } + + assignedDevices, err := evalAssignedDevices(assignedDevicesStr) + + if err != nil { + return nil, err } - mdName := deviceFields[0] // mdx - state := deviceFields[2] // active or inactive if len(lines) <= i+3 { return nil, fmt.Errorf( @@ -105,6 +134,8 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { // 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 + syncedPercent := float64(100) + remainingSyncMinutes := float64(0) recovering := strings.Contains(lines[syncLineIdx], "recovery") resyncing := strings.Contains(lines[syncLineIdx], "resync") checking := strings.Contains(lines[syncLineIdx], "check") @@ -123,8 +154,9 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { if strings.Contains(lines[syncLineIdx], "PENDING") || strings.Contains(lines[syncLineIdx], "DELAYED") { syncedBlocks = 0 + syncedPercent = 0 } else { - syncedBlocks, err = evalRecoveryLine(lines[syncLineIdx]) + syncedPercent, syncedBlocks, remainingSyncMinutes, err = evalRecoveryLine(lines[syncLineIdx]) if err != nil { return nil, fmt.Errorf("error parsing sync line in md device %s: %s", mdName, err) } @@ -132,14 +164,18 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { } mdStats = append(mdStats, MDStat{ - Name: mdName, - ActivityState: state, - DisksActive: active, - DisksFailed: fail, - DisksSpare: spare, - DisksTotal: total, - BlocksTotal: size, - BlocksSynced: syncedBlocks, + Name: mdName, + ActivityState: state, + Personality: personality, + DisksActive: active, + DisksFailed: fail, + DisksSpare: spare, + DisksTotal: total, + BlocksTotal: size, + BlocksSynced: syncedBlocks, + PercentSynced: syncedPercent, + RemainingSyncMinutes: remainingSyncMinutes, + AssignedDevices: assignedDevices, }) } @@ -182,16 +218,62 @@ func evalStatusLine(deviceLine, statusLine string) (active, total, size int64, e return active, total, size, nil } -func evalRecoveryLine(recoveryLine string) (syncedBlocks int64, err error) { +func evalRecoveryLine(recoveryLine string) (syncedPercent float64, syncedBlocks int64, remainMinutes float64, err error) { matches := recoveryLineRE.FindStringSubmatch(recoveryLine) - if len(matches) != 2 { - return 0, fmt.Errorf("unexpected recoveryLine: %s", recoveryLine) + if len(matches) != 4 { + return 0.0, 0, 0.0, fmt.Errorf("unexpected recoveryLine: %s", recoveryLine) + } + + syncedPercent, err = strconv.ParseFloat(matches[1], 64) + if err != nil { + return 0.0, 0, 0.0, fmt.Errorf("%s in recoveryLine: %s", err, recoveryLine) + } + + syncedBlocks, err = strconv.ParseInt(matches[2], 10, 64) + if err != nil { + return 0.0, 0, 0.0, fmt.Errorf("%s in recoveryLine: %s", err, recoveryLine) } - syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64) + remainMinutes, err = strconv.ParseFloat(matches[3], 64) if err != nil { - return 0, fmt.Errorf("%s in recoveryLine: %s", err, recoveryLine) + return 0.0, 0, 0.0, fmt.Errorf("%s in recoveryLine: %s", err, recoveryLine) + } + + return syncedPercent, syncedBlocks, remainMinutes, nil +} + +func evalAssignedDevices(assignedDevicesStr string) ([]MDAssignedDevice, error) { + fields := strings.Fields(assignedDevicesStr) + assignedDevices := make([]MDAssignedDevice, len(fields)) + + for i, d := range fields { + matches := devicesStrRE.FindStringSubmatch(d) + if len(matches) < 3 { + return nil, fmt.Errorf("couldn't find all the substring matches: %s", d) + } + + name := matches[1] + state := "active" + role, err := strconv.ParseInt(matches[2], 10, 64) + if err != nil { + return nil, err + } + + if len(matches) == 4 { + switch matches[3] { + case "S": + state = "spare" + case "F": + state = "failed" + } + } + + assignedDevices[i] = MDAssignedDevice{ + Name: name, + Role: role, + State: state, + } } - return syncedBlocks, nil + return assignedDevices, nil } diff --git a/mdstat_test.go b/mdstat_test.go index 540476536..fd1cbe8fb 100644 --- a/mdstat_test.go +++ b/mdstat_test.go @@ -13,7 +13,10 @@ package procfs -import "testing" +import ( + "reflect" + "testing" +) func TestFS_MDStat(t *testing.T) { fs := getProcFixtures(t) @@ -24,30 +27,547 @@ func TestFS_MDStat(t *testing.T) { } refs := map[string]MDStat{ - "md127": {Name: "md127", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 312319552, BlocksSynced: 312319552}, - "md0": {Name: "md0", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 248896, BlocksSynced: 248896}, - "md4": {Name: "md4", ActivityState: "inactive", DisksActive: 0, DisksTotal: 0, DisksFailed: 1, DisksSpare: 1, BlocksTotal: 4883648, BlocksSynced: 4883648}, - "md6": {Name: "md6", ActivityState: "recovering", DisksActive: 1, DisksTotal: 2, DisksFailed: 1, DisksSpare: 1, BlocksTotal: 195310144, BlocksSynced: 16775552}, - "md3": {Name: "md3", ActivityState: "active", DisksActive: 8, DisksTotal: 8, DisksFailed: 0, DisksSpare: 2, BlocksTotal: 5853468288, BlocksSynced: 5853468288}, - "md8": {Name: "md8", ActivityState: "resyncing", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 2, BlocksTotal: 195310144, BlocksSynced: 16775552}, - "md7": {Name: "md7", ActivityState: "active", DisksActive: 3, DisksTotal: 4, DisksFailed: 1, DisksSpare: 0, BlocksTotal: 7813735424, BlocksSynced: 7813735424}, - "md9": {Name: "md9", ActivityState: "resyncing", DisksActive: 4, DisksTotal: 4, DisksSpare: 1, DisksFailed: 2, BlocksTotal: 523968, BlocksSynced: 0}, - "md10": {Name: "md10", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 314159265, BlocksSynced: 314159265}, - "md11": {Name: "md11", ActivityState: "resyncing", DisksActive: 2, DisksTotal: 2, DisksFailed: 1, DisksSpare: 2, BlocksTotal: 4190208, BlocksSynced: 0}, - "md12": {Name: "md12", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksSpare: 0, DisksFailed: 0, BlocksTotal: 3886394368, BlocksSynced: 3886394368}, - "md120": {Name: "md120", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 2095104, BlocksSynced: 2095104}, - "md126": {Name: "md126", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 1855870976, BlocksSynced: 1855870976}, - "md219": {Name: "md219", ActivityState: "inactive", DisksTotal: 0, DisksFailed: 0, DisksActive: 0, DisksSpare: 3, BlocksTotal: 7932, BlocksSynced: 7932}, - "md00": {Name: "md00", ActivityState: "active", DisksActive: 1, DisksTotal: 1, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 4186624, BlocksSynced: 4186624}, - "md101": {Name: "md101", ActivityState: "active", DisksActive: 3, DisksTotal: 3, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 322560, BlocksSynced: 322560}, - "md201": {Name: "md201", ActivityState: "checking", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksSpare: 0, BlocksTotal: 1993728, BlocksSynced: 114176}, + "md127": { + Name: "md127", + ActivityState: "active", + Personality: "raid1", + DisksActive: 2, + DisksTotal: 2, + DisksFailed: 0, + DisksSpare: 0, + BlocksTotal: 312319552, + BlocksSynced: 312319552, + PercentSynced: 100, + RemainingSyncMinutes: 0, + AssignedDevices: []MDAssignedDevice{ + { + Name: "sdi2", + Role: 0, + State: "active", + }, + { + Name: "sdj2", + Role: 1, + State: "active", + }, + }, + }, + "md0": { + Name: "md0", + ActivityState: "active", + Personality: "raid1", + DisksActive: 2, + DisksTotal: 2, + DisksFailed: 0, + DisksSpare: 0, + BlocksTotal: 248896, + BlocksSynced: 248896, + PercentSynced: 100, + RemainingSyncMinutes: 0, + AssignedDevices: []MDAssignedDevice{ + { + Name: "sdi1", + Role: 0, + State: "active", + }, + { + Name: "sdj1", + Role: 1, + State: "active", + }, + }, + }, + "md4": { + Name: "md4", + ActivityState: "inactive", + Personality: "raid1", + DisksActive: 0, + DisksTotal: 0, + DisksFailed: 1, + DisksSpare: 1, + BlocksTotal: 4883648, + BlocksSynced: 4883648, + PercentSynced: 100, + RemainingSyncMinutes: 0, + AssignedDevices: []MDAssignedDevice{ + { + Name: "sda3", + Role: 0, + State: "failed", + }, + { + Name: "sdb3", + Role: 1, + State: "spare", + }, + }, + }, + "md6": { + Name: "md6", + ActivityState: "recovering", + Personality: "raid1", + DisksActive: 1, + DisksTotal: 2, + DisksFailed: 1, + DisksSpare: 1, + BlocksTotal: 195310144, + BlocksSynced: 16775552, + PercentSynced: 8.5, + RemainingSyncMinutes: 17.0, + AssignedDevices: []MDAssignedDevice{ + { + Name: "sdb2", + Role: 2, + State: "failed", + }, + { + Name: "sdc", + Role: 1, + State: "spare", + }, + { + Name: "sda2", + Role: 0, + State: "active", + }, + }, + }, + "md3": { + Name: "md3", + ActivityState: "active", + Personality: "raid6", + DisksActive: 8, + DisksTotal: 8, + DisksFailed: 0, + DisksSpare: 2, + BlocksTotal: 5853468288, + BlocksSynced: 5853468288, + PercentSynced: 100, + RemainingSyncMinutes: 0, + AssignedDevices: []MDAssignedDevice{ + { + Name: "sda1", + Role: 8, + State: "active", + }, + { + Name: "sdh1", + Role: 7, + State: "active", + }, + { + Name: "sdg1", + Role: 6, + State: "active", + }, + { + Name: "sdf1", + Role: 5, + State: "active", + }, + { + Name: "sde1", + Role: 11, + State: "active", + }, + { + Name: "sdd1", + Role: 3, + State: "active", + }, + { + Name: "sdc1", + Role: 10, + State: "active", + }, + { + Name: "sdb1", + Role: 9, + State: "active", + }, + { + Name: "sdd1", + Role: 10, + State: "spare", + }, + { + Name: "sdd2", + Role: 11, + State: "spare", + }, + }, + }, + "md8": { + Name: "md8", + ActivityState: "resyncing", + Personality: "raid1", + DisksActive: 2, + DisksTotal: 2, + DisksFailed: 0, + DisksSpare: 2, + BlocksTotal: 195310144, + BlocksSynced: 16775552, + PercentSynced: 8.5, + RemainingSyncMinutes: 17.0, + AssignedDevices: []MDAssignedDevice{ + { + Name: "sdb1", + Role: 1, + State: "active", + }, + { + Name: "sda1", + Role: 0, + State: "active", + }, + { + Name: "sdc", + Role: 2, + State: "spare", + }, + { + Name: "sde", + Role: 3, + State: "spare", + }, + }, + }, + "md7": { + Name: "md7", + ActivityState: "active", + Personality: "raid6", + DisksActive: 3, + DisksTotal: 4, + DisksFailed: 1, + DisksSpare: 0, + BlocksTotal: 7813735424, + BlocksSynced: 7813735424, + PercentSynced: 100, + RemainingSyncMinutes: 0, + AssignedDevices: []MDAssignedDevice{ + { + Name: "sdb1", + Role: 0, + State: "active", + }, + { + Name: "sde1", + Role: 3, + State: "active", + }, + { + Name: "sdd1", + Role: 2, + State: "active", + }, + { + Name: "sdc1", + Role: 1, + State: "failed", + }, + }, + }, + "md9": { + Name: "md9", + ActivityState: "resyncing", + Personality: "raid1", + DisksActive: 4, + DisksTotal: 4, + DisksSpare: 1, + DisksFailed: 2, + BlocksTotal: 523968, + BlocksSynced: 0, + PercentSynced: 0, + RemainingSyncMinutes: 0, + AssignedDevices: []MDAssignedDevice{ + { + Name: "sdc2", + Role: 2, + State: "active", + }, + { + Name: "sdd2", + Role: 3, + State: "active", + }, + { + Name: "sdb2", + Role: 1, + State: "active", + }, + { + Name: "sda2", + Role: 0, + State: "active", + }, + { + Name: "sde", + Role: 4, + State: "failed", + }, + { + Name: "sdf", + Role: 5, + State: "failed", + }, + { + Name: "sdg", + Role: 6, + State: "spare", + }, + }, + }, + "md10": { + Name: "md10", + ActivityState: "active", + Personality: "raid0", + DisksActive: 2, + DisksTotal: 2, + DisksFailed: 0, + DisksSpare: 0, + BlocksTotal: 314159265, + BlocksSynced: 314159265, + PercentSynced: 100, + RemainingSyncMinutes: 0, + AssignedDevices: []MDAssignedDevice{ + { + Name: "sda1", + Role: 0, + State: "active", + }, + { + Name: "sdb1", + Role: 1, + State: "active", + }, + }, + }, + "md11": { + Name: "md11", + ActivityState: "resyncing", + Personality: "raid1", + DisksActive: 2, + DisksTotal: 2, + DisksFailed: 1, + DisksSpare: 2, + BlocksTotal: 4190208, + BlocksSynced: 0, + PercentSynced: 0, + RemainingSyncMinutes: 0, + AssignedDevices: []MDAssignedDevice{ + { + Name: "sdb2", + Role: 0, + State: "active", + }, + { + Name: "sdc2", + Role: 1, + State: "active", + }, + { + Name: "sdc3", + Role: 2, + State: "failed", + }, + { + Name: "hdb", + Role: 4, + State: "spare", + }, + { + Name: "ssdc2", + Role: 3, + State: "spare", + }, + }, + }, + "md12": { + Name: "md12", + ActivityState: "active", + Personality: "raid0", + DisksActive: 2, + DisksTotal: 2, + DisksSpare: 0, + DisksFailed: 0, + BlocksTotal: 3886394368, + BlocksSynced: 3886394368, + PercentSynced: 100, + RemainingSyncMinutes: 0, + AssignedDevices: []MDAssignedDevice{ + { + Name: "sdc2", + Role: 0, + State: "active", + }, + { + Name: "sdd2", + Role: 1, + State: "active", + }, + }, + }, + "md120": { + Name: "md120", + ActivityState: "active", + Personality: "linear", + DisksActive: 2, + DisksTotal: 2, + DisksFailed: 0, + DisksSpare: 0, + BlocksTotal: 2095104, + BlocksSynced: 2095104, + PercentSynced: 100, + RemainingSyncMinutes: 0, + AssignedDevices: []MDAssignedDevice{ + { + Name: "sda1", + Role: 1, + State: "active", + }, + { + Name: "sdb1", + Role: 0, + State: "active", + }, + }, + }, + "md126": { + Name: "md126", + ActivityState: "active", + Personality: "raid0", + DisksActive: 2, + DisksTotal: 2, + DisksFailed: 0, + DisksSpare: 0, + BlocksTotal: 1855870976, + BlocksSynced: 1855870976, + PercentSynced: 100, + RemainingSyncMinutes: 0, + AssignedDevices: []MDAssignedDevice{ + { + Name: "sdb", + Role: 1, + State: "active", + }, + { + Name: "sdc", + Role: 0, + State: "active", + }, + }, + }, + "md219": { + Name: "md219", + ActivityState: "inactive", + DisksTotal: 0, + DisksFailed: 0, + DisksActive: 0, + DisksSpare: 3, + BlocksTotal: 7932, + BlocksSynced: 7932, + PercentSynced: 100, + RemainingSyncMinutes: 0, + AssignedDevices: []MDAssignedDevice{ + { + Name: "sdb", + Role: 2, + State: "spare", + }, + { + Name: "sdc", + Role: 1, + State: "spare", + }, + { + Name: "sda", + Role: 0, + State: "spare", + }, + }, + }, + "md00": { + Name: "md00", + ActivityState: "active", + Personality: "raid0", + DisksActive: 1, + DisksTotal: 1, + DisksFailed: 0, + DisksSpare: 0, + BlocksTotal: 4186624, + BlocksSynced: 4186624, + PercentSynced: 100, + RemainingSyncMinutes: 0, + AssignedDevices: []MDAssignedDevice{ + { + Name: "xvdb", + Role: 0, + State: "active", + }, + }, + }, + "md101": { + Name: "md101", + ActivityState: "active", + Personality: "raid0", + DisksActive: 3, + DisksTotal: 3, + DisksFailed: 0, + DisksSpare: 0, + BlocksTotal: 322560, + BlocksSynced: 322560, + PercentSynced: 100, + RemainingSyncMinutes: 0, + AssignedDevices: []MDAssignedDevice{ + { + Name: "sdb", + Role: 2, + State: "active", + }, + { + Name: "sdd", + Role: 1, + State: "active", + }, + { + Name: "sdc", + Role: 0, + State: "active", + }, + }, + }, + "md201": { + Name: "md201", + ActivityState: "checking", + Personality: "raid1", + DisksActive: 2, + DisksTotal: 2, + DisksFailed: 0, + DisksSpare: 0, + BlocksTotal: 1993728, + BlocksSynced: 114176, + PercentSynced: 5.7, + RemainingSyncMinutes: 0.2, + AssignedDevices: []MDAssignedDevice{ + { + Name: "sda3", + Role: 0, + State: "active", + }, + { + Name: "sdb3", + Role: 1, + State: "active", + }, + }, + }, } if want, have := len(refs), len(mdStats); want != have { t.Errorf("want %d parsed md-devices, have %d", want, have) } for _, md := range mdStats { - if want, have := refs[md.Name], md; want != have { + if want, have := refs[md.Name], md; !reflect.DeepEqual(want, have) { t.Errorf("%s: want %v, have %v", md.Name, want, have) } }