1
2
3
4
5
6
7
8
9
10
11
12
13
14 package blockdevice
15
16 import (
17 "bufio"
18 "errors"
19 "fmt"
20 "io"
21 "os"
22 "strings"
23
24 "github.com/prometheus/procfs/internal/fs"
25 "github.com/prometheus/procfs/internal/util"
26 )
27
28
29 type Info struct {
30 MajorNumber uint32
31 MinorNumber uint32
32 DeviceName string
33 }
34
35
36
37
38
39 type IOStats struct {
40
41 ReadIOs uint64
42
43
44 ReadMerges uint64
45
46 ReadSectors uint64
47
48 ReadTicks uint64
49
50 WriteIOs uint64
51
52 WriteMerges uint64
53
54 WriteSectors uint64
55
56 WriteTicks uint64
57
58 IOsInProgress uint64
59
60
61 IOsTotalTicks uint64
62
63
64 WeightedIOTicks uint64
65
66 DiscardIOs uint64
67
68 DiscardMerges uint64
69
70 DiscardSectors uint64
71
72 DiscardTicks uint64
73
74 FlushRequestsCompleted uint64
75
76 TimeSpentFlushing uint64
77 }
78
79
80 type Diskstats struct {
81 Info
82 IOStats
83
84
85
86
87 IoStatsCount int
88 }
89
90
91
92
93
94 type BlockQueueStats struct {
95
96 AddRandom uint64
97
98 DAX uint64
99
100
101 DiscardGranularity uint64
102
103
104 DiscardMaxHWBytes uint64
105
106 DiscardMaxBytes uint64
107
108 HWSectorSize uint64
109
110 IOPoll uint64
111
112
113
114 IOPollDelay int64
115
116 IOTimeout uint64
117
118 IOStats uint64
119
120 LogicalBlockSize uint64
121
122 MaxHWSectorsKB uint64
123
124
125 MaxIntegritySegments uint64
126
127 MaxSectorsKB uint64
128
129 MaxSegments uint64
130
131 MaxSegmentSize uint64
132
133 MinimumIOSize uint64
134
135
136 NoMerges uint64
137
138 NRRequests uint64
139
140 OptimalIOSize uint64
141
142 PhysicalBlockSize uint64
143
144 ReadAHeadKB uint64
145
146 Rotational uint64
147
148
149 RQAffinity uint64
150
151 SchedulerList []string
152
153 SchedulerCurrent string
154
155 WriteCache string
156
157
158 WriteSameMaxBytes uint64
159
160 WBTLatUSec int64
161
162
163 ThrottleSampleTime *uint64
164
165
166 Zoned string
167
168 NRZones uint64
169
170
171 ChunkSectors uint64
172
173 FUA uint64
174
175 MaxDiscardSegments uint64
176
177
178 WriteZeroesMaxBytes uint64
179 }
180
181
182
183
184 type DeviceMapperInfo struct {
185
186 Name string
187
188
189 RqBasedSeqIOMergeDeadline uint64
190
191 Suspended uint64
192
193 UseBlkMQ uint64
194
195 UUID string
196 }
197
198
199 type UnderlyingDeviceInfo struct {
200
201 DeviceNames []string
202 }
203
204 const (
205 procDiskstatsPath = "diskstats"
206 procDiskstatsFormat = "%d %d %s %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d"
207 sysBlockPath = "block"
208 sysBlockStatFormat = "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d"
209 sysBlockQueue = "queue"
210 sysBlockDM = "dm"
211 sysUnderlyingDev = "slaves"
212 )
213
214
215
216 type FS struct {
217 proc *fs.FS
218 sys *fs.FS
219 }
220
221
222
223 func NewDefaultFS() (FS, error) {
224 return NewFS(fs.DefaultProcMountPoint, fs.DefaultSysMountPoint)
225 }
226
227
228
229 func NewFS(procMountPoint string, sysMountPoint string) (FS, error) {
230 if strings.TrimSpace(procMountPoint) == "" {
231 procMountPoint = fs.DefaultProcMountPoint
232 }
233 procfs, err := fs.NewFS(procMountPoint)
234 if err != nil {
235 return FS{}, err
236 }
237 if strings.TrimSpace(sysMountPoint) == "" {
238 sysMountPoint = fs.DefaultSysMountPoint
239 }
240 sysfs, err := fs.NewFS(sysMountPoint)
241 if err != nil {
242 return FS{}, err
243 }
244 return FS{&procfs, &sysfs}, nil
245 }
246
247
248
249 func (fs FS) ProcDiskstats() ([]Diskstats, error) {
250 file, err := os.Open(fs.proc.Path(procDiskstatsPath))
251 if err != nil {
252 return nil, err
253 }
254 defer file.Close()
255 return parseProcDiskstats(file)
256 }
257
258 func parseProcDiskstats(r io.Reader) ([]Diskstats, error) {
259 var (
260 diskstats []Diskstats
261 scanner = bufio.NewScanner(r)
262 err error
263 )
264 for scanner.Scan() {
265 d := &Diskstats{}
266 d.IoStatsCount, err = fmt.Sscanf(scanner.Text(), procDiskstatsFormat,
267 &d.MajorNumber,
268 &d.MinorNumber,
269 &d.DeviceName,
270 &d.ReadIOs,
271 &d.ReadMerges,
272 &d.ReadSectors,
273 &d.ReadTicks,
274 &d.WriteIOs,
275 &d.WriteMerges,
276 &d.WriteSectors,
277 &d.WriteTicks,
278 &d.IOsInProgress,
279 &d.IOsTotalTicks,
280 &d.WeightedIOTicks,
281 &d.DiscardIOs,
282 &d.DiscardMerges,
283 &d.DiscardSectors,
284 &d.DiscardTicks,
285 &d.FlushRequestsCompleted,
286 &d.TimeSpentFlushing,
287 )
288
289
290 if err != nil && !errors.Is(err, io.EOF) {
291 return diskstats, err
292 }
293 if d.IoStatsCount >= 14 {
294 diskstats = append(diskstats, *d)
295 }
296 }
297 return diskstats, scanner.Err()
298 }
299
300
301 func (fs FS) SysBlockDevices() ([]string, error) {
302 deviceDirs, err := os.ReadDir(fs.sys.Path(sysBlockPath))
303 if err != nil {
304 return nil, err
305 }
306 devices := []string{}
307 for _, deviceDir := range deviceDirs {
308 devices = append(devices, deviceDir.Name())
309 }
310 return devices, nil
311 }
312
313
314
315
316 func (fs FS) SysBlockDeviceStat(device string) (IOStats, int, error) {
317 bytes, err := os.ReadFile(fs.sys.Path(sysBlockPath, device, "stat"))
318 if err != nil {
319 return IOStats{}, 0, err
320 }
321 return parseSysBlockDeviceStat(bytes)
322 }
323
324 func parseSysBlockDeviceStat(data []byte) (IOStats, int, error) {
325 stat := IOStats{}
326 count, err := fmt.Sscanf(strings.TrimSpace(string(data)), sysBlockStatFormat,
327 &stat.ReadIOs,
328 &stat.ReadMerges,
329 &stat.ReadSectors,
330 &stat.ReadTicks,
331 &stat.WriteIOs,
332 &stat.WriteMerges,
333 &stat.WriteSectors,
334 &stat.WriteTicks,
335 &stat.IOsInProgress,
336 &stat.IOsTotalTicks,
337 &stat.WeightedIOTicks,
338 &stat.DiscardIOs,
339 &stat.DiscardMerges,
340 &stat.DiscardSectors,
341 &stat.DiscardTicks,
342 &stat.FlushRequestsCompleted,
343 &stat.TimeSpentFlushing,
344 )
345
346 if errors.Is(err, io.EOF) {
347 return stat, count, nil
348 }
349 return stat, count, err
350 }
351
352
353 func (fs FS) SysBlockDeviceQueueStats(device string) (BlockQueueStats, error) {
354 stat := BlockQueueStats{}
355
356 for file, p := range map[string]*uint64{
357 "add_random": &stat.AddRandom,
358 "dax": &stat.DAX,
359 "discard_granularity": &stat.DiscardGranularity,
360 "discard_max_hw_bytes": &stat.DiscardMaxHWBytes,
361 "discard_max_bytes": &stat.DiscardMaxBytes,
362 "hw_sector_size": &stat.HWSectorSize,
363 "io_poll": &stat.IOPoll,
364 "io_timeout": &stat.IOTimeout,
365 "iostats": &stat.IOStats,
366 "logical_block_size": &stat.LogicalBlockSize,
367 "max_hw_sectors_kb": &stat.MaxHWSectorsKB,
368 "max_integrity_segments": &stat.MaxIntegritySegments,
369 "max_sectors_kb": &stat.MaxSectorsKB,
370 "max_segments": &stat.MaxSegments,
371 "max_segment_size": &stat.MaxSegmentSize,
372 "minimum_io_size": &stat.MinimumIOSize,
373 "nomerges": &stat.NoMerges,
374 "nr_requests": &stat.NRRequests,
375 "optimal_io_size": &stat.OptimalIOSize,
376 "physical_block_size": &stat.PhysicalBlockSize,
377 "read_ahead_kb": &stat.ReadAHeadKB,
378 "rotational": &stat.Rotational,
379 "rq_affinity": &stat.RQAffinity,
380 "write_same_max_bytes": &stat.WriteSameMaxBytes,
381 "nr_zones": &stat.NRZones,
382 "chunk_sectors": &stat.ChunkSectors,
383 "fua": &stat.FUA,
384 "max_discard_segments": &stat.MaxDiscardSegments,
385 "write_zeroes_max_bytes": &stat.WriteZeroesMaxBytes,
386 } {
387 val, err := util.ReadUintFromFile(fs.sys.Path(sysBlockPath, device, sysBlockQueue, file))
388 if err != nil {
389 return BlockQueueStats{}, err
390 }
391 *p = val
392 }
393
394 for file, p := range map[string]*int64{
395 "io_poll_delay": &stat.IOPollDelay,
396 "wbt_lat_usec": &stat.WBTLatUSec,
397 } {
398 val, err := util.ReadIntFromFile(fs.sys.Path(sysBlockPath, device, sysBlockQueue, file))
399 if err != nil {
400 return BlockQueueStats{}, err
401 }
402 *p = val
403 }
404
405 for file, p := range map[string]*string{
406 "write_cache": &stat.WriteCache,
407 "zoned": &stat.Zoned,
408 } {
409 val, err := util.SysReadFile(fs.sys.Path(sysBlockPath, device, sysBlockQueue, file))
410 if err != nil {
411 return BlockQueueStats{}, err
412 }
413 *p = val
414 }
415 scheduler, err := util.SysReadFile(fs.sys.Path(sysBlockPath, device, sysBlockQueue, "scheduler"))
416 if err != nil {
417 return BlockQueueStats{}, err
418 }
419 var schedulers []string
420 xs := strings.Split(scheduler, " ")
421 for _, s := range xs {
422 if strings.HasPrefix(s, "[") && strings.HasSuffix(s, "]") {
423 s = s[1 : len(s)-1]
424 stat.SchedulerCurrent = s
425 }
426 schedulers = append(schedulers, s)
427 }
428 stat.SchedulerList = schedulers
429
430 throttleSampleTime, err := util.ReadUintFromFile(fs.sys.Path(sysBlockPath, device, sysBlockQueue, "throttle_sample_time"))
431 if err == nil {
432 stat.ThrottleSampleTime = &throttleSampleTime
433 }
434 return stat, nil
435 }
436
437 func (fs FS) SysBlockDeviceMapperInfo(device string) (DeviceMapperInfo, error) {
438 info := DeviceMapperInfo{}
439
440 for file, p := range map[string]*uint64{
441 "rq_based_seq_io_merge_deadline": &info.RqBasedSeqIOMergeDeadline,
442 "suspended": &info.Suspended,
443 "use_blk_mq": &info.UseBlkMQ,
444 } {
445 val, err := util.ReadUintFromFile(fs.sys.Path(sysBlockPath, device, sysBlockDM, file))
446 if err != nil {
447 return DeviceMapperInfo{}, err
448 }
449 *p = val
450 }
451
452 for file, p := range map[string]*string{
453 "name": &info.Name,
454 "uuid": &info.UUID,
455 } {
456 val, err := util.SysReadFile(fs.sys.Path(sysBlockPath, device, sysBlockDM, file))
457 if err != nil {
458 return DeviceMapperInfo{}, err
459 }
460 *p = val
461 }
462 return info, nil
463 }
464
465 func (fs FS) SysBlockDeviceUnderlyingDevices(device string) (UnderlyingDeviceInfo, error) {
466 underlyingDir, err := os.Open(fs.sys.Path(sysBlockPath, device, sysUnderlyingDev))
467 if err != nil {
468 return UnderlyingDeviceInfo{}, err
469 }
470 underlying, err := underlyingDir.Readdirnames(0)
471 if err != nil {
472 return UnderlyingDeviceInfo{}, err
473 }
474 return UnderlyingDeviceInfo{DeviceNames: underlying}, nil
475
476 }
477
View as plain text