-
Notifications
You must be signed in to change notification settings - Fork 349
feat: expose MD raid component devices #674
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
robbat2
wants to merge
7
commits into
prometheus:master
Choose a base branch
from
robbat2:rjohnson/mdstat-devices
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
06e19a7
Support parse raid type for mdstat
ImSingee 2e01e40
support linear type in mdstat
ImSingee e1162a5
change default type to unknown
ImSingee baf5a5a
optimize raid type check
ImSingee 22c345a
feat: expose MD raid component devices
robbat2 8a15205
fix: update mdstat_test for reshaping testcase
robbat2 e505e3a
doc: lint fix
robbat2 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,13 +27,34 @@ var ( | |
recoveryLinePctRE = regexp.MustCompile(`= (.+)%`) | ||
recoveryLineFinishRE = regexp.MustCompile(`finish=(.+)min`) | ||
recoveryLineSpeedRE = regexp.MustCompile(`speed=(.+)[A-Z]`) | ||
componentDeviceRE = regexp.MustCompile(`(.*)\[\d+\]`) | ||
componentDeviceRE = regexp.MustCompile(`(.*)\[(\d+)\](\([SF]+\))?`) | ||
personalitiesPrefix = "Personalities : " | ||
) | ||
|
||
type MDStatComponent struct { | ||
// Name of the component device. | ||
Name string | ||
// DescriptorIndex number of component device, e.g. the order in the superblock. | ||
DescriptorIndex int32 | ||
// Flags per Linux drivers/md/md.[ch] as of v6.12-rc1 | ||
// Subset that are exposed in mdstat | ||
WriteMostly bool | ||
Journal bool | ||
Faulty bool // "Faulty" is what kernel source uses for "(F)" | ||
Spare bool | ||
Replacement bool | ||
// Some additional flags that are NOT exposed in procfs today; they may | ||
// be available via sysfs. | ||
// In_sync, Bitmap_sync, Blocked, WriteErrorSeen, FaultRecorded, | ||
// BlockedBadBlocks, WantReplacement, Candidate, ... | ||
} | ||
|
||
// MDStat holds info parsed from /proc/mdstat. | ||
type MDStat struct { | ||
// Name of the device. | ||
Name string | ||
// raid type of the device. | ||
Type string | ||
// activity-state of the device. | ||
ActivityState string | ||
// Number of active disks. | ||
|
@@ -58,8 +79,8 @@ type MDStat struct { | |
BlocksSyncedFinishTime float64 | ||
// current sync speed (in Kilobytes/sec) | ||
BlocksSyncedSpeed float64 | ||
// Name of md component devices | ||
Devices []string | ||
// component devices | ||
Devices []MDStatComponent | ||
} | ||
|
||
// MDStat parses an mdstat-file (/proc/mdstat) and returns a slice of | ||
|
@@ -80,28 +101,52 @@ func (fs FS) MDStat() ([]MDStat, error) { | |
// parseMDStat parses data from mdstat file (/proc/mdstat) and returns a slice of | ||
// structs containing the relevant info. | ||
func parseMDStat(mdStatData []byte) ([]MDStat, error) { | ||
// TODO: | ||
// - parse global hotspares from the "unused devices" line. | ||
mdStats := []MDStat{} | ||
lines := strings.Split(string(mdStatData), "\n") | ||
knownRaidTypes := make(map[string]bool) | ||
|
||
for i, line := range lines { | ||
if strings.TrimSpace(line) == "" || line[0] == ' ' || | ||
strings.HasPrefix(line, "Personalities") || | ||
strings.HasPrefix(line, "unused") { | ||
continue | ||
} | ||
// Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10] | ||
if len(knownRaidTypes) == 0 && strings.HasPrefix(line, personalitiesPrefix) { | ||
personalities := strings.Fields(line[len(personalitiesPrefix):]) | ||
for _, word := range personalities { | ||
word := word[1 : len(word)-1] | ||
knownRaidTypes[word] = true | ||
} | ||
continue | ||
} | ||
|
||
deviceFields := strings.Fields(line) | ||
if len(deviceFields) < 3 { | ||
return nil, fmt.Errorf("%w: Expected 3+ lines, got %q", ErrFileParse, line) | ||
} | ||
mdName := deviceFields[0] // mdx | ||
state := deviceFields[2] // active or inactive | ||
state := deviceFields[2] // active, inactive, broken | ||
|
||
mdType := "unknown" // raid1, raid5, etc. | ||
var deviceStartIndex int | ||
if len(deviceFields) > 3 { // mdType may be in the 3rd or 4th field | ||
if isRaidType(deviceFields[3], knownRaidTypes) { | ||
mdType = deviceFields[3] | ||
deviceStartIndex = 4 | ||
} else if len(deviceFields) > 4 && isRaidType(deviceFields[4], knownRaidTypes) { | ||
// if the 3rd field is (...), the 4th field is the mdType | ||
mdType = deviceFields[4] | ||
deviceStartIndex = 5 | ||
} | ||
} | ||
|
||
if len(lines) <= i+3 { | ||
return nil, fmt.Errorf("%w: Too few lines for md device: %q", ErrFileParse, mdName) | ||
} | ||
|
||
// Failed disks have the suffix (F) & Spare disks have the suffix (S). | ||
// Failed (Faulty) disks have the suffix (F) & Spare disks have the suffix (S). | ||
fail := int64(strings.Count(line, "(F)")) | ||
spare := int64(strings.Count(line, "(S)")) | ||
active, total, down, size, err := evalStatusLine(lines[i], lines[i+1]) | ||
|
@@ -151,8 +196,14 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { | |
} | ||
} | ||
|
||
devices, err := evalComponentDevices(deviceFields[deviceStartIndex:]) | ||
if err != nil { | ||
return nil, fmt.Errorf("error parsing components in md device %q: %w", mdName, err) | ||
} | ||
|
||
mdStats = append(mdStats, MDStat{ | ||
Name: mdName, | ||
Type: mdType, | ||
ActivityState: state, | ||
DisksActive: active, | ||
DisksFailed: fail, | ||
|
@@ -165,14 +216,24 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { | |
BlocksSyncedPct: pct, | ||
BlocksSyncedFinishTime: finish, | ||
BlocksSyncedSpeed: speed, | ||
Devices: evalComponentDevices(deviceFields), | ||
Devices: devices, | ||
}) | ||
} | ||
|
||
return mdStats, nil | ||
} | ||
|
||
// check if a string's format is like the mdType | ||
// Rule 1: mdType should not be like (...) | ||
// Rule 2: mdType should not be like sda[0] | ||
// . | ||
func isRaidType(mdType string, knownRaidTypes map[string]bool) bool { | ||
_, ok := knownRaidTypes[mdType] | ||
return !strings.ContainsAny(mdType, "([") && ok | ||
} | ||
|
||
func evalStatusLine(deviceLine, statusLine string) (active, total, down, size int64, err error) { | ||
// e.g. 523968 blocks super 1.2 [4/4] [UUUU] | ||
statusFields := strings.Fields(statusLine) | ||
if len(statusFields) < 1 { | ||
return 0, 0, 0, 0, fmt.Errorf("%w: Unexpected statusline %q: %w", ErrFileParse, statusLine, err) | ||
|
@@ -263,17 +324,29 @@ func evalRecoveryLine(recoveryLine string) (blocksSynced int64, blocksToBeSynced | |
return blocksSynced, blocksToBeSynced, pct, finish, speed, nil | ||
} | ||
|
||
func evalComponentDevices(deviceFields []string) []string { | ||
mdComponentDevices := make([]string, 0) | ||
if len(deviceFields) > 3 { | ||
for _, field := range deviceFields[4:] { | ||
match := componentDeviceRE.FindStringSubmatch(field) | ||
if match == nil { | ||
continue | ||
} | ||
mdComponentDevices = append(mdComponentDevices, match[1]) | ||
func evalComponentDevices(deviceFields []string) ([]MDStatComponent, error) { | ||
mdComponentDevices := make([]MDStatComponent, 0) | ||
for _, field := range deviceFields { | ||
match := componentDeviceRE.FindStringSubmatch(field) | ||
if match == nil { | ||
continue | ||
} | ||
descriptorIndex, err := strconv.ParseInt(match[2], 10, 32) | ||
if err != nil { | ||
return mdComponentDevices, fmt.Errorf("error parsing int from device %q: %w", match[2], err) | ||
} | ||
mdComponentDevices = append(mdComponentDevices, MDStatComponent{ | ||
Name: match[1], | ||
DescriptorIndex: int32(descriptorIndex), | ||
// match may contain one or more of these | ||
// https://github.com/torvalds/linux/blob/7ec462100ef9142344ddbf86f2c3008b97acddbe/drivers/md/md.c#L8376-L8392 | ||
Faulty: strings.Contains(match[3], "(F)"), | ||
Spare: strings.Contains(match[3], "(S)"), | ||
Journal: strings.Contains(match[3], "(J)"), | ||
Replacement: strings.Contains(match[3], "(R)"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh! R stands for Replacement! |
||
WriteMostly: strings.Contains(match[3], "(W)"), | ||
}) | ||
} | ||
|
||
return mdComponentDevices | ||
return mdComponentDevices, nil | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to know