cubefs

Форк
0
266 строк · 8.5 Кб
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

14
package procfs
15

16
import (
17
	"fmt"
18
	"os"
19
	"regexp"
20
	"strconv"
21
	"strings"
22
)
23

24
var (
25
	statusLineRE         = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[([U_]+)\]`)
26
	recoveryLineBlocksRE = regexp.MustCompile(`\((\d+)/\d+\)`)
27
	recoveryLinePctRE    = regexp.MustCompile(`= (.+)%`)
28
	recoveryLineFinishRE = regexp.MustCompile(`finish=(.+)min`)
29
	recoveryLineSpeedRE  = regexp.MustCompile(`speed=(.+)[A-Z]`)
30
	componentDeviceRE    = regexp.MustCompile(`(.*)\[\d+\]`)
31
)
32

33
// MDStat holds info parsed from /proc/mdstat.
34
type MDStat struct {
35
	// Name of the device.
36
	Name string
37
	// activity-state of the device.
38
	ActivityState string
39
	// Number of active disks.
40
	DisksActive int64
41
	// Total number of disks the device requires.
42
	DisksTotal int64
43
	// Number of failed disks.
44
	DisksFailed int64
45
	// Number of "down" disks. (the _ indicator in the status line)
46
	DisksDown int64
47
	// Spare disks in the device.
48
	DisksSpare int64
49
	// Number of blocks the device holds.
50
	BlocksTotal int64
51
	// Number of blocks on the device that are in sync.
52
	BlocksSynced int64
53
	// progress percentage of current sync
54
	BlocksSyncedPct float64
55
	// estimated finishing time for current sync (in minutes)
56
	BlocksSyncedFinishTime float64
57
	// current sync speed (in Kilobytes/sec)
58
	BlocksSyncedSpeed float64
59
	// Name of md component devices
60
	Devices []string
61
}
62

63
// MDStat parses an mdstat-file (/proc/mdstat) and returns a slice of
64
// structs containing the relevant info.  More information available here:
65
// https://raid.wiki.kernel.org/index.php/Mdstat
66
func (fs FS) MDStat() ([]MDStat, error) {
67
	data, err := os.ReadFile(fs.proc.Path("mdstat"))
68
	if err != nil {
69
		return nil, err
70
	}
71
	mdstat, err := parseMDStat(data)
72
	if err != nil {
73
		return nil, fmt.Errorf("error parsing mdstat %q: %w", fs.proc.Path("mdstat"), err)
74
	}
75
	return mdstat, nil
76
}
77

78
// parseMDStat parses data from mdstat file (/proc/mdstat) and returns a slice of
79
// structs containing the relevant info.
80
func parseMDStat(mdStatData []byte) ([]MDStat, error) {
81
	mdStats := []MDStat{}
82
	lines := strings.Split(string(mdStatData), "\n")
83

84
	for i, line := range lines {
85
		if strings.TrimSpace(line) == "" || line[0] == ' ' ||
86
			strings.HasPrefix(line, "Personalities") ||
87
			strings.HasPrefix(line, "unused") {
88
			continue
89
		}
90

91
		deviceFields := strings.Fields(line)
92
		if len(deviceFields) < 3 {
93
			return nil, fmt.Errorf("not enough fields in mdline (expected at least 3): %s", line)
94
		}
95
		mdName := deviceFields[0] // mdx
96
		state := deviceFields[2]  // active or inactive
97

98
		if len(lines) <= i+3 {
99
			return nil, fmt.Errorf("error parsing %q: too few lines for md device", mdName)
100
		}
101

102
		// Failed disks have the suffix (F) & Spare disks have the suffix (S).
103
		fail := int64(strings.Count(line, "(F)"))
104
		spare := int64(strings.Count(line, "(S)"))
105
		active, total, down, size, err := evalStatusLine(lines[i], lines[i+1])
106

107
		if err != nil {
108
			return nil, fmt.Errorf("error parsing md device lines: %w", err)
109
		}
110

111
		syncLineIdx := i + 2
112
		if strings.Contains(lines[i+2], "bitmap") { // skip bitmap line
113
			syncLineIdx++
114
		}
115

116
		// If device is syncing at the moment, get the number of currently
117
		// synced bytes, otherwise that number equals the size of the device.
118
		syncedBlocks := size
119
		speed := float64(0)
120
		finish := float64(0)
121
		pct := float64(0)
122
		recovering := strings.Contains(lines[syncLineIdx], "recovery")
123
		resyncing := strings.Contains(lines[syncLineIdx], "resync")
124
		checking := strings.Contains(lines[syncLineIdx], "check")
125

126
		// Append recovery and resyncing state info.
127
		if recovering || resyncing || checking {
128
			if recovering {
129
				state = "recovering"
130
			} else if checking {
131
				state = "checking"
132
			} else {
133
				state = "resyncing"
134
			}
135

136
			// Handle case when resync=PENDING or resync=DELAYED.
137
			if strings.Contains(lines[syncLineIdx], "PENDING") ||
138
				strings.Contains(lines[syncLineIdx], "DELAYED") {
139
				syncedBlocks = 0
140
			} else {
141
				syncedBlocks, pct, finish, speed, err = evalRecoveryLine(lines[syncLineIdx])
142
				if err != nil {
143
					return nil, fmt.Errorf("error parsing sync line in md device %q: %w", mdName, err)
144
				}
145
			}
146
		}
147

148
		mdStats = append(mdStats, MDStat{
149
			Name:                   mdName,
150
			ActivityState:          state,
151
			DisksActive:            active,
152
			DisksFailed:            fail,
153
			DisksDown:              down,
154
			DisksSpare:             spare,
155
			DisksTotal:             total,
156
			BlocksTotal:            size,
157
			BlocksSynced:           syncedBlocks,
158
			BlocksSyncedPct:        pct,
159
			BlocksSyncedFinishTime: finish,
160
			BlocksSyncedSpeed:      speed,
161
			Devices:                evalComponentDevices(deviceFields),
162
		})
163
	}
164

165
	return mdStats, nil
166
}
167

168
func evalStatusLine(deviceLine, statusLine string) (active, total, down, size int64, err error) {
169
	statusFields := strings.Fields(statusLine)
170
	if len(statusFields) < 1 {
171
		return 0, 0, 0, 0, fmt.Errorf("unexpected statusLine %q", statusLine)
172
	}
173

174
	sizeStr := statusFields[0]
175
	size, err = strconv.ParseInt(sizeStr, 10, 64)
176
	if err != nil {
177
		return 0, 0, 0, 0, fmt.Errorf("unexpected statusLine %q: %w", statusLine, err)
178
	}
179

180
	if strings.Contains(deviceLine, "raid0") || strings.Contains(deviceLine, "linear") {
181
		// In the device deviceLine, only disks have a number associated with them in [].
182
		total = int64(strings.Count(deviceLine, "["))
183
		return total, total, 0, size, nil
184
	}
185

186
	if strings.Contains(deviceLine, "inactive") {
187
		return 0, 0, 0, size, nil
188
	}
189

190
	matches := statusLineRE.FindStringSubmatch(statusLine)
191
	if len(matches) != 5 {
192
		return 0, 0, 0, 0, fmt.Errorf("couldn't find all the substring matches: %s", statusLine)
193
	}
194

195
	total, err = strconv.ParseInt(matches[2], 10, 64)
196
	if err != nil {
197
		return 0, 0, 0, 0, fmt.Errorf("unexpected statusLine %q: %w", statusLine, err)
198
	}
199

200
	active, err = strconv.ParseInt(matches[3], 10, 64)
201
	if err != nil {
202
		return 0, 0, 0, 0, fmt.Errorf("unexpected statusLine %q: %w", statusLine, err)
203
	}
204
	down = int64(strings.Count(matches[4], "_"))
205

206
	return active, total, down, size, nil
207
}
208

209
func evalRecoveryLine(recoveryLine string) (syncedBlocks int64, pct float64, finish float64, speed float64, err error) {
210
	matches := recoveryLineBlocksRE.FindStringSubmatch(recoveryLine)
211
	if len(matches) != 2 {
212
		return 0, 0, 0, 0, fmt.Errorf("unexpected recoveryLine: %s", recoveryLine)
213
	}
214

215
	syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64)
216
	if err != nil {
217
		return 0, 0, 0, 0, fmt.Errorf("error parsing int from recoveryLine %q: %w", recoveryLine, err)
218
	}
219

220
	// Get percentage complete
221
	matches = recoveryLinePctRE.FindStringSubmatch(recoveryLine)
222
	if len(matches) != 2 {
223
		return syncedBlocks, 0, 0, 0, fmt.Errorf("unexpected recoveryLine matching percentage: %s", recoveryLine)
224
	}
225
	pct, err = strconv.ParseFloat(strings.TrimSpace(matches[1]), 64)
226
	if err != nil {
227
		return syncedBlocks, 0, 0, 0, fmt.Errorf("error parsing float from recoveryLine %q: %w", recoveryLine, err)
228
	}
229

230
	// Get time expected left to complete
231
	matches = recoveryLineFinishRE.FindStringSubmatch(recoveryLine)
232
	if len(matches) != 2 {
233
		return syncedBlocks, pct, 0, 0, fmt.Errorf("unexpected recoveryLine matching est. finish time: %s", recoveryLine)
234
	}
235
	finish, err = strconv.ParseFloat(matches[1], 64)
236
	if err != nil {
237
		return syncedBlocks, pct, 0, 0, fmt.Errorf("error parsing float from recoveryLine %q: %w", recoveryLine, err)
238
	}
239

240
	// Get recovery speed
241
	matches = recoveryLineSpeedRE.FindStringSubmatch(recoveryLine)
242
	if len(matches) != 2 {
243
		return syncedBlocks, pct, finish, 0, fmt.Errorf("unexpected recoveryLine matching speed: %s", recoveryLine)
244
	}
245
	speed, err = strconv.ParseFloat(matches[1], 64)
246
	if err != nil {
247
		return syncedBlocks, pct, finish, 0, fmt.Errorf("error parsing float from recoveryLine %q: %w", recoveryLine, err)
248
	}
249

250
	return syncedBlocks, pct, finish, speed, nil
251
}
252

253
func evalComponentDevices(deviceFields []string) []string {
254
	mdComponentDevices := make([]string, 0)
255
	if len(deviceFields) > 3 {
256
		for _, field := range deviceFields[4:] {
257
			match := componentDeviceRE.FindStringSubmatch(field)
258
			if match == nil {
259
				continue
260
			}
261
			mdComponentDevices = append(mdComponentDevices, match[1])
262
		}
263
	}
264

265
	return mdComponentDevices
266
}
267

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.