mdstat.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. package procfs
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "path"
  6. "regexp"
  7. "strconv"
  8. "strings"
  9. )
  10. var (
  11. statuslineRE = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[[U_]+\]`)
  12. buildlineRE = regexp.MustCompile(`\((\d+)/\d+\)`)
  13. )
  14. // MDStat holds info parsed from /proc/mdstat.
  15. type MDStat struct {
  16. // Name of the device.
  17. Name string
  18. // activity-state of the device.
  19. ActivityState string
  20. // Number of active disks.
  21. DisksActive int64
  22. // Total number of disks the device consists of.
  23. DisksTotal int64
  24. // Number of blocks the device holds.
  25. BlocksTotal int64
  26. // Number of blocks on the device that are in sync.
  27. BlocksSynced int64
  28. }
  29. // ParseMDStat parses an mdstat-file and returns a struct with the relevant infos.
  30. func (fs FS) ParseMDStat() (mdstates []MDStat, err error) {
  31. mdStatusFilePath := path.Join(string(fs), "mdstat")
  32. content, err := ioutil.ReadFile(mdStatusFilePath)
  33. if err != nil {
  34. return []MDStat{}, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err)
  35. }
  36. mdStatusFile := string(content)
  37. lines := strings.Split(mdStatusFile, "\n")
  38. var currentMD string
  39. // Each md has at least the deviceline, statusline and one empty line afterwards
  40. // so we will have probably something of the order len(lines)/3 devices
  41. // so we use that for preallocation.
  42. estimateMDs := len(lines) / 3
  43. mdStates := make([]MDStat, 0, estimateMDs)
  44. for i, l := range lines {
  45. if l == "" {
  46. // Skip entirely empty lines.
  47. continue
  48. }
  49. if l[0] == ' ' {
  50. // Those lines are not the beginning of a md-section.
  51. continue
  52. }
  53. if strings.HasPrefix(l, "Personalities") || strings.HasPrefix(l, "unused") {
  54. // We aren't interested in lines with general info.
  55. continue
  56. }
  57. mainLine := strings.Split(l, " ")
  58. if len(mainLine) < 3 {
  59. return mdStates, fmt.Errorf("error parsing mdline: %s", l)
  60. }
  61. currentMD = mainLine[0] // name of md-device
  62. activityState := mainLine[2] // activity status of said md-device
  63. if len(lines) <= i+3 {
  64. return mdStates, fmt.Errorf("error parsing %s: entry for %s has fewer lines than expected", mdStatusFilePath, currentMD)
  65. }
  66. active, total, size, err := evalStatusline(lines[i+1]) // parse statusline, always present
  67. if err != nil {
  68. return mdStates, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err)
  69. }
  70. //
  71. // Now get the number of synced blocks.
  72. //
  73. // Get the line number of the syncing-line.
  74. var j int
  75. if strings.Contains(lines[i+2], "bitmap") { // then skip the bitmap line
  76. j = i + 3
  77. } else {
  78. j = i + 2
  79. }
  80. // If device is syncing at the moment, get the number of currently synced bytes,
  81. // otherwise that number equals the size of the device.
  82. syncedBlocks := size
  83. if strings.Contains(lines[j], "recovery") || strings.Contains(lines[j], "resync") {
  84. syncedBlocks, err = evalBuildline(lines[j])
  85. if err != nil {
  86. return mdStates, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err)
  87. }
  88. }
  89. mdStates = append(mdStates, MDStat{currentMD, activityState, active, total, size, syncedBlocks})
  90. }
  91. return mdStates, nil
  92. }
  93. func evalStatusline(statusline string) (active, total, size int64, err error) {
  94. matches := statuslineRE.FindStringSubmatch(statusline)
  95. // +1 to make it more obvious that the whole string containing the info is also returned as matches[0].
  96. if len(matches) != 3+1 {
  97. return 0, 0, 0, fmt.Errorf("unexpected number matches found in statusline: %s", statusline)
  98. }
  99. size, err = strconv.ParseInt(matches[1], 10, 64)
  100. if err != nil {
  101. return 0, 0, 0, fmt.Errorf("%s in statusline: %s", err, statusline)
  102. }
  103. total, err = strconv.ParseInt(matches[2], 10, 64)
  104. if err != nil {
  105. return 0, 0, 0, fmt.Errorf("%s in statusline: %s", err, statusline)
  106. }
  107. active, err = strconv.ParseInt(matches[3], 10, 64)
  108. if err != nil {
  109. return 0, 0, 0, fmt.Errorf("%s in statusline: %s", err, statusline)
  110. }
  111. return active, total, size, nil
  112. }
  113. // Gets the size that has already been synced out of the sync-line.
  114. func evalBuildline(buildline string) (int64, error) {
  115. matches := buildlineRE.FindStringSubmatch(buildline)
  116. // +1 to make it more obvious that the whole string containing the info is also returned as matches[0].
  117. if len(matches) < 1+1 {
  118. return 0, fmt.Errorf("too few matches found in buildline: %s", buildline)
  119. }
  120. if len(matches) > 1+1 {
  121. return 0, fmt.Errorf("too many matches found in buildline: %s", buildline)
  122. }
  123. syncedSize, err := strconv.ParseInt(matches[1], 10, 64)
  124. if err != nil {
  125. return 0, fmt.Errorf("%s in buildline: %s", err, buildline)
  126. }
  127. return syncedSize, nil
  128. }