1
2
3
4
5
6
7
8
9
10
11
12
13
14 package procfs
15
16
17
18
19
20
21
22
23 import (
24 "bufio"
25 "fmt"
26 "io"
27 "strconv"
28 "strings"
29 "time"
30 )
31
32
33 const (
34 deviceEntryLen = 8
35
36 fieldBytesLen = 8
37 fieldEventsLen = 27
38
39 statVersion10 = "1.0"
40 statVersion11 = "1.1"
41
42 fieldTransport10TCPLen = 10
43 fieldTransport10UDPLen = 7
44
45 fieldTransport11TCPLen = 13
46 fieldTransport11UDPLen = 10
47
48
49
50 fieldTransport11RDMAMaxLen = 28
51
52
53
54 fieldTransport11RDMAMinLen = 20
55 )
56
57
58 type Mount struct {
59
60 Device string
61
62 Mount string
63
64 Type string
65
66
67 Stats MountStats
68 }
69
70
71
72 type MountStats interface {
73 mountStats()
74 }
75
76
77 type MountStatsNFS struct {
78
79 StatVersion string
80
81 Opts map[string]string
82
83 Age time.Duration
84
85 Bytes NFSBytesStats
86
87 Events NFSEventsStats
88
89 Operations []NFSOperationStats
90
91 Transport NFSTransportStats
92 }
93
94
95 func (m MountStatsNFS) mountStats() {}
96
97
98
99 type NFSBytesStats struct {
100
101 Read uint64
102
103 Write uint64
104
105 DirectRead uint64
106
107 DirectWrite uint64
108
109 ReadTotal uint64
110
111 WriteTotal uint64
112
113 ReadPages uint64
114
115 WritePages uint64
116 }
117
118
119 type NFSEventsStats struct {
120
121 InodeRevalidate uint64
122
123 DnodeRevalidate uint64
124
125 DataInvalidate uint64
126
127 AttributeInvalidate uint64
128
129 VFSOpen uint64
130
131 VFSLookup uint64
132
133 VFSAccess uint64
134
135 VFSUpdatePage uint64
136
137 VFSReadPage uint64
138
139 VFSReadPages uint64
140
141 VFSWritePage uint64
142
143 VFSWritePages uint64
144
145 VFSGetdents uint64
146
147 VFSSetattr uint64
148
149 VFSFlush uint64
150
151 VFSFsync uint64
152
153 VFSLock uint64
154
155 VFSFileRelease uint64
156
157 CongestionWait uint64
158
159 Truncation uint64
160
161 WriteExtension uint64
162
163 SillyRename uint64
164
165 ShortRead uint64
166
167 ShortWrite uint64
168
169
170 JukeboxDelay uint64
171
172 PNFSRead uint64
173
174 PNFSWrite uint64
175 }
176
177
178 type NFSOperationStats struct {
179
180 Operation string
181
182 Requests uint64
183
184 Transmissions uint64
185
186 MajorTimeouts uint64
187
188 BytesSent uint64
189
190 BytesReceived uint64
191
192 CumulativeQueueMilliseconds uint64
193
194 CumulativeTotalResponseMilliseconds uint64
195
196 CumulativeTotalRequestMilliseconds uint64
197
198 AverageRTTMilliseconds float64
199
200 Errors uint64
201 }
202
203
204
205 type NFSTransportStats struct {
206
207 Protocol string
208
209 Port uint64
210
211
212 Bind uint64
213
214 Connect uint64
215
216
217 ConnectIdleTime uint64
218
219 IdleTimeSeconds uint64
220
221 Sends uint64
222
223 Receives uint64
224
225
226 BadTransactionIDs uint64
227
228
229 CumulativeActiveRequests uint64
230
231
232 CumulativeBacklog uint64
233
234
235
236
237 MaximumRPCSlotsUsed uint64
238
239
240 CumulativeSendingQueue uint64
241
242
243 CumulativePendingQueue uint64
244
245
246
247
248
249 ReadChunkCount uint64
250 WriteChunkCount uint64
251 ReplyChunkCount uint64
252 TotalRdmaRequest uint64
253
254
255 PullupCopyCount uint64
256 HardwayRegisterCount uint64
257 FailedMarshalCount uint64
258 BadReplyCount uint64
259 MrsRecovered uint64
260 MrsOrphaned uint64
261 MrsAllocated uint64
262 EmptySendctxQ uint64
263
264
265 TotalRdmaReply uint64
266 FixupCopyCount uint64
267 ReplyWaitsForSend uint64
268 LocalInvNeeded uint64
269 NomsgCallCount uint64
270 BcallCount uint64
271 }
272
273
274
275
276 func parseMountStats(r io.Reader) ([]*Mount, error) {
277 const (
278 device = "device"
279 statVersionPrefix = "statvers="
280
281 nfs3Type = "nfs"
282 nfs4Type = "nfs4"
283 )
284
285 var mounts []*Mount
286
287 s := bufio.NewScanner(r)
288 for s.Scan() {
289
290 ss := strings.Fields(string(s.Bytes()))
291 if len(ss) == 0 || ss[0] != device {
292 continue
293 }
294
295 m, err := parseMount(ss)
296 if err != nil {
297 return nil, err
298 }
299
300
301 if len(ss) > deviceEntryLen {
302
303 if m.Type != nfs3Type && m.Type != nfs4Type {
304 return nil, fmt.Errorf("%w: Cannot parse MountStats for %q", ErrFileParse, m.Type)
305 }
306
307 statVersion := strings.TrimPrefix(ss[8], statVersionPrefix)
308
309 stats, err := parseMountStatsNFS(s, statVersion)
310 if err != nil {
311 return nil, err
312 }
313
314 m.Stats = stats
315 }
316
317 mounts = append(mounts, m)
318 }
319
320 return mounts, s.Err()
321 }
322
323
324
325
326 func parseMount(ss []string) (*Mount, error) {
327 if len(ss) < deviceEntryLen {
328 return nil, fmt.Errorf("%w: Invalid device %q", ErrFileParse, ss)
329 }
330
331
332
333 format := []struct {
334 i int
335 s string
336 }{
337 {i: 0, s: "device"},
338 {i: 2, s: "mounted"},
339 {i: 3, s: "on"},
340 {i: 5, s: "with"},
341 {i: 6, s: "fstype"},
342 }
343
344 for _, f := range format {
345 if ss[f.i] != f.s {
346 return nil, fmt.Errorf("%w: Invalid device %q", ErrFileParse, ss)
347 }
348 }
349
350 return &Mount{
351 Device: ss[1],
352 Mount: ss[4],
353 Type: ss[7],
354 }, nil
355 }
356
357
358
359 func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, error) {
360
361 const (
362 fieldOpts = "opts:"
363 fieldAge = "age:"
364 fieldBytes = "bytes:"
365 fieldEvents = "events:"
366 fieldPerOpStats = "per-op"
367 fieldTransport = "xprt:"
368 )
369
370 stats := &MountStatsNFS{
371 StatVersion: statVersion,
372 }
373
374 for s.Scan() {
375 ss := strings.Fields(string(s.Bytes()))
376 if len(ss) == 0 {
377 break
378 }
379
380 switch ss[0] {
381 case fieldOpts:
382 if len(ss) < 2 {
383 return nil, fmt.Errorf("%w: Incomplete information for NFS stats: %v", ErrFileParse, ss)
384 }
385 if stats.Opts == nil {
386 stats.Opts = map[string]string{}
387 }
388 for _, opt := range strings.Split(ss[1], ",") {
389 split := strings.Split(opt, "=")
390 if len(split) == 2 {
391 stats.Opts[split[0]] = split[1]
392 } else {
393 stats.Opts[opt] = ""
394 }
395 }
396 case fieldAge:
397 if len(ss) < 2 {
398 return nil, fmt.Errorf("%w: Incomplete information for NFS stats: %v", ErrFileParse, ss)
399 }
400
401 d, err := time.ParseDuration(ss[1] + "s")
402 if err != nil {
403 return nil, err
404 }
405
406 stats.Age = d
407 case fieldBytes:
408 if len(ss) < 2 {
409 return nil, fmt.Errorf("%w: Incomplete information for NFS stats: %v", ErrFileParse, ss)
410 }
411 bstats, err := parseNFSBytesStats(ss[1:])
412 if err != nil {
413 return nil, err
414 }
415
416 stats.Bytes = *bstats
417 case fieldEvents:
418 if len(ss) < 2 {
419 return nil, fmt.Errorf("%w: Incomplete information for NFS events: %v", ErrFileParse, ss)
420 }
421 estats, err := parseNFSEventsStats(ss[1:])
422 if err != nil {
423 return nil, err
424 }
425
426 stats.Events = *estats
427 case fieldTransport:
428 if len(ss) < 3 {
429 return nil, fmt.Errorf("%w: Incomplete information for NFS transport stats: %v", ErrFileParse, ss)
430 }
431
432 tstats, err := parseNFSTransportStats(ss[1:], statVersion)
433 if err != nil {
434 return nil, err
435 }
436
437 stats.Transport = *tstats
438 }
439
440
441
442
443
444 if ss[0] == fieldPerOpStats {
445 break
446 }
447 }
448
449 if err := s.Err(); err != nil {
450 return nil, err
451 }
452
453
454 perOpStats, err := parseNFSOperationStats(s)
455 if err != nil {
456 return nil, err
457 }
458
459 stats.Operations = perOpStats
460
461 return stats, nil
462 }
463
464
465
466 func parseNFSBytesStats(ss []string) (*NFSBytesStats, error) {
467 if len(ss) != fieldBytesLen {
468 return nil, fmt.Errorf("%w: Invalid NFS bytes stats: %v", ErrFileParse, ss)
469 }
470
471 ns := make([]uint64, 0, fieldBytesLen)
472 for _, s := range ss {
473 n, err := strconv.ParseUint(s, 10, 64)
474 if err != nil {
475 return nil, err
476 }
477
478 ns = append(ns, n)
479 }
480
481 return &NFSBytesStats{
482 Read: ns[0],
483 Write: ns[1],
484 DirectRead: ns[2],
485 DirectWrite: ns[3],
486 ReadTotal: ns[4],
487 WriteTotal: ns[5],
488 ReadPages: ns[6],
489 WritePages: ns[7],
490 }, nil
491 }
492
493
494
495 func parseNFSEventsStats(ss []string) (*NFSEventsStats, error) {
496 if len(ss) != fieldEventsLen {
497 return nil, fmt.Errorf("%w: invalid NFS events stats: %v", ErrFileParse, ss)
498 }
499
500 ns := make([]uint64, 0, fieldEventsLen)
501 for _, s := range ss {
502 n, err := strconv.ParseUint(s, 10, 64)
503 if err != nil {
504 return nil, err
505 }
506
507 ns = append(ns, n)
508 }
509
510 return &NFSEventsStats{
511 InodeRevalidate: ns[0],
512 DnodeRevalidate: ns[1],
513 DataInvalidate: ns[2],
514 AttributeInvalidate: ns[3],
515 VFSOpen: ns[4],
516 VFSLookup: ns[5],
517 VFSAccess: ns[6],
518 VFSUpdatePage: ns[7],
519 VFSReadPage: ns[8],
520 VFSReadPages: ns[9],
521 VFSWritePage: ns[10],
522 VFSWritePages: ns[11],
523 VFSGetdents: ns[12],
524 VFSSetattr: ns[13],
525 VFSFlush: ns[14],
526 VFSFsync: ns[15],
527 VFSLock: ns[16],
528 VFSFileRelease: ns[17],
529 CongestionWait: ns[18],
530 Truncation: ns[19],
531 WriteExtension: ns[20],
532 SillyRename: ns[21],
533 ShortRead: ns[22],
534 ShortWrite: ns[23],
535 JukeboxDelay: ns[24],
536 PNFSRead: ns[25],
537 PNFSWrite: ns[26],
538 }, nil
539 }
540
541
542
543
544 func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
545 const (
546
547 minFields = 9
548 )
549
550 var ops []NFSOperationStats
551
552 for s.Scan() {
553 ss := strings.Fields(string(s.Bytes()))
554 if len(ss) == 0 {
555
556
557 break
558 }
559
560 if len(ss) < minFields {
561 return nil, fmt.Errorf("%w: invalid NFS per-operations stats: %v", ErrFileParse, ss)
562 }
563
564
565 ns := make([]uint64, 0, minFields-1)
566 for _, st := range ss[1:] {
567 n, err := strconv.ParseUint(st, 10, 64)
568 if err != nil {
569 return nil, err
570 }
571
572 ns = append(ns, n)
573 }
574 opStats := NFSOperationStats{
575 Operation: strings.TrimSuffix(ss[0], ":"),
576 Requests: ns[0],
577 Transmissions: ns[1],
578 MajorTimeouts: ns[2],
579 BytesSent: ns[3],
580 BytesReceived: ns[4],
581 CumulativeQueueMilliseconds: ns[5],
582 CumulativeTotalResponseMilliseconds: ns[6],
583 CumulativeTotalRequestMilliseconds: ns[7],
584 }
585 if ns[0] != 0 {
586 opStats.AverageRTTMilliseconds = float64(ns[6]) / float64(ns[0])
587 }
588
589 if len(ns) > 8 {
590 opStats.Errors = ns[8]
591 }
592
593 ops = append(ops, opStats)
594 }
595
596 return ops, s.Err()
597 }
598
599
600
601 func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats, error) {
602
603 protocol := ss[0]
604 ss = ss[1:]
605
606 switch statVersion {
607 case statVersion10:
608 var expectedLength int
609 if protocol == "tcp" {
610 expectedLength = fieldTransport10TCPLen
611 } else if protocol == "udp" {
612 expectedLength = fieldTransport10UDPLen
613 } else {
614 return nil, fmt.Errorf("%w: Invalid NFS protocol \"%s\" in stats 1.0 statement: %v", ErrFileParse, protocol, ss)
615 }
616 if len(ss) != expectedLength {
617 return nil, fmt.Errorf("%w: Invalid NFS transport stats 1.0 statement: %v", ErrFileParse, ss)
618 }
619 case statVersion11:
620 var expectedLength int
621 if protocol == "tcp" {
622 expectedLength = fieldTransport11TCPLen
623 } else if protocol == "udp" {
624 expectedLength = fieldTransport11UDPLen
625 } else if protocol == "rdma" {
626 expectedLength = fieldTransport11RDMAMinLen
627 } else {
628 return nil, fmt.Errorf("%w: invalid NFS protocol \"%s\" in stats 1.1 statement: %v", ErrFileParse, protocol, ss)
629 }
630 if (len(ss) != expectedLength && (protocol == "tcp" || protocol == "udp")) ||
631 (protocol == "rdma" && len(ss) < expectedLength) {
632 return nil, fmt.Errorf("%w: invalid NFS transport stats 1.1 statement: %v, protocol: %v", ErrFileParse, ss, protocol)
633 }
634 default:
635 return nil, fmt.Errorf("%s: Unrecognized NFS transport stats version: %q, protocol: %v", ErrFileParse, statVersion, protocol)
636 }
637
638
639
640
641
642
643
644
645
646
647 ns := make([]uint64, fieldTransport11RDMAMaxLen+3)
648 for i, s := range ss {
649 n, err := strconv.ParseUint(s, 10, 64)
650 if err != nil {
651 return nil, err
652 }
653
654 ns[i] = n
655 }
656
657
658
659
660
661
662
663 if protocol == "udp" {
664 ns = append(ns[:2], append(make([]uint64, 3), ns[2:]...)...)
665 } else if protocol == "tcp" {
666 ns = append(ns[:fieldTransport11TCPLen], make([]uint64, fieldTransport11RDMAMaxLen-fieldTransport11TCPLen+3)...)
667 } else if protocol == "rdma" {
668 ns = append(ns[:fieldTransport10TCPLen], append(make([]uint64, 3), ns[fieldTransport10TCPLen:]...)...)
669 }
670
671 return &NFSTransportStats{
672
673 Protocol: protocol,
674 Port: ns[0],
675 Bind: ns[1],
676 Connect: ns[2],
677 ConnectIdleTime: ns[3],
678 IdleTimeSeconds: ns[4],
679 Sends: ns[5],
680 Receives: ns[6],
681 BadTransactionIDs: ns[7],
682 CumulativeActiveRequests: ns[8],
683 CumulativeBacklog: ns[9],
684
685
686
687 MaximumRPCSlotsUsed: ns[10],
688 CumulativeSendingQueue: ns[11],
689 CumulativePendingQueue: ns[12],
690
691
692
693 ReadChunkCount: ns[13],
694 WriteChunkCount: ns[14],
695 ReplyChunkCount: ns[15],
696 TotalRdmaRequest: ns[16],
697 PullupCopyCount: ns[17],
698 HardwayRegisterCount: ns[18],
699 FailedMarshalCount: ns[19],
700 BadReplyCount: ns[20],
701 MrsRecovered: ns[21],
702 MrsOrphaned: ns[22],
703 MrsAllocated: ns[23],
704 EmptySendctxQ: ns[24],
705 TotalRdmaReply: ns[25],
706 FixupCopyCount: ns[26],
707 ReplyWaitsForSend: ns[27],
708 LocalInvNeeded: ns[28],
709 NomsgCallCount: ns[29],
710 BcallCount: ns[30],
711 }, nil
712 }
713
View as plain text