mdstat.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. // Copyright 2018 The Prometheus Authors
  2. // Licensed under the Apache License, Version 2.0 (the "License");
  3. // you may not use this file except in compliance with the License.
  4. // You may obtain a copy of the License at
  5. //
  6. // http://www.apache.org/licenses/LICENSE-2.0
  7. //
  8. // Unless required by applicable law or agreed to in writing, software
  9. // distributed under the License is distributed on an "AS IS" BASIS,
  10. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. // See the License for the specific language governing permissions and
  12. // limitations under the License.
  13. package procfs
  14. import (
  15. "fmt"
  16. "io/ioutil"
  17. "regexp"
  18. "strconv"
  19. "strings"
  20. )
  21. var (
  22. statuslineRE = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[[U_]+\]`)
  23. buildlineRE = regexp.MustCompile(`\((\d+)/\d+\)`)
  24. )
  25. // MDStat holds info parsed from /proc/mdstat.
  26. type MDStat struct {
  27. // Name of the device.
  28. Name string
  29. // activity-state of the device.
  30. ActivityState string
  31. // Number of active disks.
  32. DisksActive int64
  33. // Total number of disks the device consists of.
  34. DisksTotal int64
  35. // Number of blocks the device holds.
  36. BlocksTotal int64
  37. // Number of blocks on the device that are in sync.
  38. BlocksSynced int64
  39. }
  40. // MDStat parses an mdstat-file (/proc/mdstat) and returns a slice of
  41. // structs containing the relevant info. More information available here:
  42. // https://raid.wiki.kernel.org/index.php/Mdstat
  43. func (fs FS) MDStat() ([]MDStat, error) {
  44. data, err := ioutil.ReadFile(fs.proc.Path("mdstat"))
  45. if err != nil {
  46. return nil, fmt.Errorf("error parsing mdstat %s: %s", fs.proc.Path("mdstat"), err)
  47. }
  48. mdstat, err := parseMDStat(data)
  49. if err != nil {
  50. return nil, fmt.Errorf("error parsing mdstat %s: %s", fs.proc.Path("mdstat"), err)
  51. }
  52. return mdstat, nil
  53. }
  54. // parseMDStat parses data from mdstat file (/proc/mdstat) and returns a slice of
  55. // structs containing the relevant info.
  56. func parseMDStat(mdstatData []byte) ([]MDStat, error) {
  57. mdStats := []MDStat{}
  58. lines := strings.Split(string(mdstatData), "\n")
  59. for i, l := range lines {
  60. if strings.TrimSpace(l) == "" || l[0] == ' ' ||
  61. strings.HasPrefix(l, "Personalities") || strings.HasPrefix(l, "unused") {
  62. continue
  63. }
  64. deviceFields := strings.Fields(l)
  65. if len(deviceFields) < 3 {
  66. return nil, fmt.Errorf("not enough fields in mdline (expected at least 3): %s", l)
  67. }
  68. mdName := deviceFields[0]
  69. activityState := deviceFields[2]
  70. if len(lines) <= i+3 {
  71. return mdStats, fmt.Errorf("missing lines for md device %s", mdName)
  72. }
  73. active, total, size, err := evalStatusLine(lines[i+1])
  74. if err != nil {
  75. return nil, err
  76. }
  77. syncLineIdx := i + 2
  78. if strings.Contains(lines[i+2], "bitmap") { // skip bitmap line
  79. syncLineIdx++
  80. }
  81. // If device is recovering/syncing at the moment, get the number of currently
  82. // synced bytes, otherwise that number equals the size of the device.
  83. syncedBlocks := size
  84. if strings.Contains(lines[syncLineIdx], "recovery") || strings.Contains(lines[syncLineIdx], "resync") {
  85. syncedBlocks, err = evalRecoveryLine(lines[syncLineIdx])
  86. if err != nil {
  87. return nil, err
  88. }
  89. }
  90. mdStats = append(mdStats, MDStat{
  91. Name: mdName,
  92. ActivityState: activityState,
  93. DisksActive: active,
  94. DisksTotal: total,
  95. BlocksTotal: size,
  96. BlocksSynced: syncedBlocks,
  97. })
  98. }
  99. return mdStats, nil
  100. }
  101. func evalStatusLine(statusline string) (active, total, size int64, err error) {
  102. matches := statuslineRE.FindStringSubmatch(statusline)
  103. if len(matches) != 4 {
  104. return 0, 0, 0, fmt.Errorf("unexpected statusline: %s", statusline)
  105. }
  106. size, err = strconv.ParseInt(matches[1], 10, 64)
  107. if err != nil {
  108. return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err)
  109. }
  110. total, err = strconv.ParseInt(matches[2], 10, 64)
  111. if err != nil {
  112. return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err)
  113. }
  114. active, err = strconv.ParseInt(matches[3], 10, 64)
  115. if err != nil {
  116. return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err)
  117. }
  118. return active, total, size, nil
  119. }
  120. func evalRecoveryLine(buildline string) (syncedBlocks int64, err error) {
  121. matches := buildlineRE.FindStringSubmatch(buildline)
  122. if len(matches) != 2 {
  123. return 0, fmt.Errorf("unexpected buildline: %s", buildline)
  124. }
  125. syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64)
  126. if err != nil {
  127. return 0, fmt.Errorf("%s in buildline: %s", err, buildline)
  128. }
  129. return syncedBlocks, nil
  130. }