blob: 50caa73274ebb2af3c3668e3859869de6417a07c [file] [log] [blame]
khenaidoo59ce9dd2019-11-11 13:05:32 -05001// 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
14package procfs
15
16// While implementing parsing of /proc/[pid]/mountstats, this blog was used
17// heavily as a reference:
18// https://utcc.utoronto.ca/~cks/space/blog/linux/NFSMountstatsIndex
19//
20// Special thanks to Chris Siebenmann for all of his posts explaining the
21// various statistics available for NFS.
22
23import (
24 "bufio"
25 "fmt"
26 "io"
27 "strconv"
28 "strings"
29 "time"
30)
31
32// Constants shared between multiple functions.
33const (
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
Abhay Kumar40252eb2025-10-13 13:25:53 +000047
48 // Kernel version >= 4.14 MaxLen
49 // See: https://elixir.bootlin.com/linux/v6.4.8/source/net/sunrpc/xprtrdma/xprt_rdma.h#L393
50 fieldTransport11RDMAMaxLen = 28
51
52 // Kernel version <= 4.2 MinLen
53 // See: https://elixir.bootlin.com/linux/v4.2.8/source/net/sunrpc/xprtrdma/xprt_rdma.h#L331
54 fieldTransport11RDMAMinLen = 20
khenaidoo59ce9dd2019-11-11 13:05:32 -050055)
56
57// A Mount is a device mount parsed from /proc/[pid]/mountstats.
58type Mount struct {
59 // Name of the device.
60 Device string
61 // The mount point of the device.
62 Mount string
63 // The filesystem type used by the device.
64 Type string
65 // If available additional statistics related to this Mount.
66 // Use a type assertion to determine if additional statistics are available.
67 Stats MountStats
68}
69
70// A MountStats is a type which contains detailed statistics for a specific
71// type of Mount.
72type MountStats interface {
73 mountStats()
74}
75
76// A MountStatsNFS is a MountStats implementation for NFSv3 and v4 mounts.
77type MountStatsNFS struct {
78 // The version of statistics provided.
79 StatVersion string
80 // The mount options of the NFS mount.
81 Opts map[string]string
82 // The age of the NFS mount.
83 Age time.Duration
84 // Statistics related to byte counters for various operations.
85 Bytes NFSBytesStats
86 // Statistics related to various NFS event occurrences.
87 Events NFSEventsStats
88 // Statistics broken down by filesystem operation.
89 Operations []NFSOperationStats
90 // Statistics about the NFS RPC transport.
Abhay Kumar40252eb2025-10-13 13:25:53 +000091 Transport []NFSTransportStats
khenaidoo59ce9dd2019-11-11 13:05:32 -050092}
93
94// mountStats implements MountStats.
95func (m MountStatsNFS) mountStats() {}
96
97// A NFSBytesStats contains statistics about the number of bytes read and written
98// by an NFS client to and from an NFS server.
99type NFSBytesStats struct {
100 // Number of bytes read using the read() syscall.
101 Read uint64
102 // Number of bytes written using the write() syscall.
103 Write uint64
104 // Number of bytes read using the read() syscall in O_DIRECT mode.
105 DirectRead uint64
106 // Number of bytes written using the write() syscall in O_DIRECT mode.
107 DirectWrite uint64
108 // Number of bytes read from the NFS server, in total.
109 ReadTotal uint64
110 // Number of bytes written to the NFS server, in total.
111 WriteTotal uint64
112 // Number of pages read directly via mmap()'d files.
113 ReadPages uint64
114 // Number of pages written directly via mmap()'d files.
115 WritePages uint64
116}
117
118// A NFSEventsStats contains statistics about NFS event occurrences.
119type NFSEventsStats struct {
120 // Number of times cached inode attributes are re-validated from the server.
121 InodeRevalidate uint64
122 // Number of times cached dentry nodes are re-validated from the server.
123 DnodeRevalidate uint64
124 // Number of times an inode cache is cleared.
125 DataInvalidate uint64
126 // Number of times cached inode attributes are invalidated.
127 AttributeInvalidate uint64
128 // Number of times files or directories have been open()'d.
129 VFSOpen uint64
130 // Number of times a directory lookup has occurred.
131 VFSLookup uint64
132 // Number of times permissions have been checked.
133 VFSAccess uint64
134 // Number of updates (and potential writes) to pages.
135 VFSUpdatePage uint64
136 // Number of pages read directly via mmap()'d files.
137 VFSReadPage uint64
138 // Number of times a group of pages have been read.
139 VFSReadPages uint64
140 // Number of pages written directly via mmap()'d files.
141 VFSWritePage uint64
142 // Number of times a group of pages have been written.
143 VFSWritePages uint64
144 // Number of times directory entries have been read with getdents().
145 VFSGetdents uint64
146 // Number of times attributes have been set on inodes.
147 VFSSetattr uint64
148 // Number of pending writes that have been forcefully flushed to the server.
149 VFSFlush uint64
150 // Number of times fsync() has been called on directories and files.
151 VFSFsync uint64
152 // Number of times locking has been attempted on a file.
153 VFSLock uint64
154 // Number of times files have been closed and released.
155 VFSFileRelease uint64
156 // Unknown. Possibly unused.
157 CongestionWait uint64
158 // Number of times files have been truncated.
159 Truncation uint64
160 // Number of times a file has been grown due to writes beyond its existing end.
161 WriteExtension uint64
162 // Number of times a file was removed while still open by another process.
163 SillyRename uint64
164 // Number of times the NFS server gave less data than expected while reading.
165 ShortRead uint64
166 // Number of times the NFS server wrote less data than expected while writing.
167 ShortWrite uint64
168 // Number of times the NFS server indicated EJUKEBOX; retrieving data from
169 // offline storage.
170 JukeboxDelay uint64
171 // Number of NFS v4.1+ pNFS reads.
172 PNFSRead uint64
173 // Number of NFS v4.1+ pNFS writes.
174 PNFSWrite uint64
175}
176
177// A NFSOperationStats contains statistics for a single operation.
178type NFSOperationStats struct {
179 // The name of the operation.
180 Operation string
181 // Number of requests performed for this operation.
182 Requests uint64
183 // Number of times an actual RPC request has been transmitted for this operation.
184 Transmissions uint64
185 // Number of times a request has had a major timeout.
186 MajorTimeouts uint64
187 // Number of bytes sent for this operation, including RPC headers and payload.
188 BytesSent uint64
189 // Number of bytes received for this operation, including RPC headers and payload.
190 BytesReceived uint64
191 // Duration all requests spent queued for transmission before they were sent.
192 CumulativeQueueMilliseconds uint64
193 // Duration it took to get a reply back after the request was transmitted.
194 CumulativeTotalResponseMilliseconds uint64
195 // Duration from when a request was enqueued to when it was completely handled.
196 CumulativeTotalRequestMilliseconds uint64
khenaidoo26721882021-08-11 17:42:52 -0400197 // The count of operations that complete with tk_status < 0. These statuses usually indicate error conditions.
198 Errors uint64
khenaidoo59ce9dd2019-11-11 13:05:32 -0500199}
200
201// A NFSTransportStats contains statistics for the NFS mount RPC requests and
202// responses.
203type NFSTransportStats struct {
204 // The transport protocol used for the NFS mount.
205 Protocol string
206 // The local port used for the NFS mount.
207 Port uint64
208 // Number of times the client has had to establish a connection from scratch
209 // to the NFS server.
210 Bind uint64
211 // Number of times the client has made a TCP connection to the NFS server.
212 Connect uint64
213 // Duration (in jiffies, a kernel internal unit of time) the NFS mount has
214 // spent waiting for connections to the server to be established.
215 ConnectIdleTime uint64
216 // Duration since the NFS mount last saw any RPC traffic.
217 IdleTimeSeconds uint64
218 // Number of RPC requests for this mount sent to the NFS server.
219 Sends uint64
220 // Number of RPC responses for this mount received from the NFS server.
221 Receives uint64
222 // Number of times the NFS server sent a response with a transaction ID
223 // unknown to this client.
224 BadTransactionIDs uint64
225 // A running counter, incremented on each request as the current difference
226 // ebetween sends and receives.
227 CumulativeActiveRequests uint64
228 // A running counter, incremented on each request by the current backlog
229 // queue size.
230 CumulativeBacklog uint64
231
232 // Stats below only available with stat version 1.1.
233
234 // Maximum number of simultaneously active RPC requests ever used.
235 MaximumRPCSlotsUsed uint64
236 // A running counter, incremented on each request as the current size of the
237 // sending queue.
238 CumulativeSendingQueue uint64
239 // A running counter, incremented on each request as the current size of the
240 // pending queue.
241 CumulativePendingQueue uint64
Abhay Kumar40252eb2025-10-13 13:25:53 +0000242
243 // Stats below only available with stat version 1.1.
244 // Transport over RDMA
245
246 // accessed when sending a call
247 ReadChunkCount uint64
248 WriteChunkCount uint64
249 ReplyChunkCount uint64
250 TotalRdmaRequest uint64
251
252 // rarely accessed error counters
253 PullupCopyCount uint64
254 HardwayRegisterCount uint64
255 FailedMarshalCount uint64
256 BadReplyCount uint64
257 MrsRecovered uint64
258 MrsOrphaned uint64
259 MrsAllocated uint64
260 EmptySendctxQ uint64
261
262 // accessed when receiving a reply
263 TotalRdmaReply uint64
264 FixupCopyCount uint64
265 ReplyWaitsForSend uint64
266 LocalInvNeeded uint64
267 NomsgCallCount uint64
268 BcallCount uint64
khenaidoo59ce9dd2019-11-11 13:05:32 -0500269}
270
271// parseMountStats parses a /proc/[pid]/mountstats file and returns a slice
272// of Mount structures containing detailed information about each mount.
273// If available, statistics for each mount are parsed as well.
274func parseMountStats(r io.Reader) ([]*Mount, error) {
275 const (
276 device = "device"
277 statVersionPrefix = "statvers="
278
279 nfs3Type = "nfs"
280 nfs4Type = "nfs4"
281 )
282
283 var mounts []*Mount
284
285 s := bufio.NewScanner(r)
286 for s.Scan() {
287 // Only look for device entries in this function
288 ss := strings.Fields(string(s.Bytes()))
289 if len(ss) == 0 || ss[0] != device {
290 continue
291 }
292
293 m, err := parseMount(ss)
294 if err != nil {
295 return nil, err
296 }
297
298 // Does this mount also possess statistics information?
299 if len(ss) > deviceEntryLen {
300 // Only NFSv3 and v4 are supported for parsing statistics
301 if m.Type != nfs3Type && m.Type != nfs4Type {
Abhay Kumar40252eb2025-10-13 13:25:53 +0000302 return nil, fmt.Errorf("%w: Cannot parse MountStats for %q", ErrFileParse, m.Type)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500303 }
304
305 statVersion := strings.TrimPrefix(ss[8], statVersionPrefix)
306
307 stats, err := parseMountStatsNFS(s, statVersion)
308 if err != nil {
309 return nil, err
310 }
311
312 m.Stats = stats
313 }
314
315 mounts = append(mounts, m)
316 }
317
318 return mounts, s.Err()
319}
320
321// parseMount parses an entry in /proc/[pid]/mountstats in the format:
Abhay Kumar40252eb2025-10-13 13:25:53 +0000322//
323// device [device] mounted on [mount] with fstype [type]
khenaidoo59ce9dd2019-11-11 13:05:32 -0500324func parseMount(ss []string) (*Mount, error) {
325 if len(ss) < deviceEntryLen {
Abhay Kumar40252eb2025-10-13 13:25:53 +0000326 return nil, fmt.Errorf("%w: Invalid device %q", ErrFileParse, ss)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500327 }
328
329 // Check for specific words appearing at specific indices to ensure
330 // the format is consistent with what we expect
331 format := []struct {
332 i int
333 s string
334 }{
335 {i: 0, s: "device"},
336 {i: 2, s: "mounted"},
337 {i: 3, s: "on"},
338 {i: 5, s: "with"},
339 {i: 6, s: "fstype"},
340 }
341
342 for _, f := range format {
343 if ss[f.i] != f.s {
Abhay Kumar40252eb2025-10-13 13:25:53 +0000344 return nil, fmt.Errorf("%w: Invalid device %q", ErrFileParse, ss)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500345 }
346 }
347
348 return &Mount{
349 Device: ss[1],
350 Mount: ss[4],
351 Type: ss[7],
352 }, nil
353}
354
355// parseMountStatsNFS parses a MountStatsNFS by scanning additional information
356// related to NFS statistics.
357func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, error) {
358 // Field indicators for parsing specific types of data
359 const (
360 fieldOpts = "opts:"
361 fieldAge = "age:"
362 fieldBytes = "bytes:"
363 fieldEvents = "events:"
364 fieldPerOpStats = "per-op"
365 fieldTransport = "xprt:"
366 )
367
368 stats := &MountStatsNFS{
369 StatVersion: statVersion,
370 }
371
372 for s.Scan() {
373 ss := strings.Fields(string(s.Bytes()))
374 if len(ss) == 0 {
375 break
376 }
khenaidoo59ce9dd2019-11-11 13:05:32 -0500377
378 switch ss[0] {
379 case fieldOpts:
khenaidoo26721882021-08-11 17:42:52 -0400380 if len(ss) < 2 {
Abhay Kumar40252eb2025-10-13 13:25:53 +0000381 return nil, fmt.Errorf("%w: Incomplete information for NFS stats: %v", ErrFileParse, ss)
khenaidoo26721882021-08-11 17:42:52 -0400382 }
khenaidoo59ce9dd2019-11-11 13:05:32 -0500383 if stats.Opts == nil {
384 stats.Opts = map[string]string{}
385 }
386 for _, opt := range strings.Split(ss[1], ",") {
387 split := strings.Split(opt, "=")
388 if len(split) == 2 {
389 stats.Opts[split[0]] = split[1]
390 } else {
391 stats.Opts[opt] = ""
392 }
393 }
394 case fieldAge:
khenaidoo26721882021-08-11 17:42:52 -0400395 if len(ss) < 2 {
Abhay Kumar40252eb2025-10-13 13:25:53 +0000396 return nil, fmt.Errorf("%w: Incomplete information for NFS stats: %v", ErrFileParse, ss)
khenaidoo26721882021-08-11 17:42:52 -0400397 }
khenaidoo59ce9dd2019-11-11 13:05:32 -0500398 // Age integer is in seconds
399 d, err := time.ParseDuration(ss[1] + "s")
400 if err != nil {
401 return nil, err
402 }
403
404 stats.Age = d
405 case fieldBytes:
khenaidoo26721882021-08-11 17:42:52 -0400406 if len(ss) < 2 {
Abhay Kumar40252eb2025-10-13 13:25:53 +0000407 return nil, fmt.Errorf("%w: Incomplete information for NFS stats: %v", ErrFileParse, ss)
khenaidoo26721882021-08-11 17:42:52 -0400408 }
khenaidoo59ce9dd2019-11-11 13:05:32 -0500409 bstats, err := parseNFSBytesStats(ss[1:])
410 if err != nil {
411 return nil, err
412 }
413
414 stats.Bytes = *bstats
415 case fieldEvents:
khenaidoo26721882021-08-11 17:42:52 -0400416 if len(ss) < 2 {
Abhay Kumar40252eb2025-10-13 13:25:53 +0000417 return nil, fmt.Errorf("%w: Incomplete information for NFS events: %v", ErrFileParse, ss)
khenaidoo26721882021-08-11 17:42:52 -0400418 }
khenaidoo59ce9dd2019-11-11 13:05:32 -0500419 estats, err := parseNFSEventsStats(ss[1:])
420 if err != nil {
421 return nil, err
422 }
423
424 stats.Events = *estats
425 case fieldTransport:
426 if len(ss) < 3 {
Abhay Kumar40252eb2025-10-13 13:25:53 +0000427 return nil, fmt.Errorf("%w: Incomplete information for NFS transport stats: %v", ErrFileParse, ss)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500428 }
429
430 tstats, err := parseNFSTransportStats(ss[1:], statVersion)
431 if err != nil {
432 return nil, err
433 }
434
Abhay Kumar40252eb2025-10-13 13:25:53 +0000435 stats.Transport = append(stats.Transport, *tstats)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500436 }
437
438 // When encountering "per-operation statistics", we must break this
439 // loop and parse them separately to ensure we can terminate parsing
440 // before reaching another device entry; hence why this 'if' statement
441 // is not just another switch case
442 if ss[0] == fieldPerOpStats {
443 break
444 }
445 }
446
447 if err := s.Err(); err != nil {
448 return nil, err
449 }
450
451 // NFS per-operation stats appear last before the next device entry
452 perOpStats, err := parseNFSOperationStats(s)
453 if err != nil {
454 return nil, err
455 }
456
457 stats.Operations = perOpStats
458
459 return stats, nil
460}
461
462// parseNFSBytesStats parses a NFSBytesStats line using an input set of
463// integer fields.
464func parseNFSBytesStats(ss []string) (*NFSBytesStats, error) {
465 if len(ss) != fieldBytesLen {
Abhay Kumar40252eb2025-10-13 13:25:53 +0000466 return nil, fmt.Errorf("%w: Invalid NFS bytes stats: %v", ErrFileParse, ss)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500467 }
468
469 ns := make([]uint64, 0, fieldBytesLen)
470 for _, s := range ss {
471 n, err := strconv.ParseUint(s, 10, 64)
472 if err != nil {
473 return nil, err
474 }
475
476 ns = append(ns, n)
477 }
478
479 return &NFSBytesStats{
480 Read: ns[0],
481 Write: ns[1],
482 DirectRead: ns[2],
483 DirectWrite: ns[3],
484 ReadTotal: ns[4],
485 WriteTotal: ns[5],
486 ReadPages: ns[6],
487 WritePages: ns[7],
488 }, nil
489}
490
491// parseNFSEventsStats parses a NFSEventsStats line using an input set of
492// integer fields.
493func parseNFSEventsStats(ss []string) (*NFSEventsStats, error) {
494 if len(ss) != fieldEventsLen {
Abhay Kumar40252eb2025-10-13 13:25:53 +0000495 return nil, fmt.Errorf("%w: invalid NFS events stats: %v", ErrFileParse, ss)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500496 }
497
498 ns := make([]uint64, 0, fieldEventsLen)
499 for _, s := range ss {
500 n, err := strconv.ParseUint(s, 10, 64)
501 if err != nil {
502 return nil, err
503 }
504
505 ns = append(ns, n)
506 }
507
508 return &NFSEventsStats{
509 InodeRevalidate: ns[0],
510 DnodeRevalidate: ns[1],
511 DataInvalidate: ns[2],
512 AttributeInvalidate: ns[3],
513 VFSOpen: ns[4],
514 VFSLookup: ns[5],
515 VFSAccess: ns[6],
516 VFSUpdatePage: ns[7],
517 VFSReadPage: ns[8],
518 VFSReadPages: ns[9],
519 VFSWritePage: ns[10],
520 VFSWritePages: ns[11],
521 VFSGetdents: ns[12],
522 VFSSetattr: ns[13],
523 VFSFlush: ns[14],
524 VFSFsync: ns[15],
525 VFSLock: ns[16],
526 VFSFileRelease: ns[17],
527 CongestionWait: ns[18],
528 Truncation: ns[19],
529 WriteExtension: ns[20],
530 SillyRename: ns[21],
531 ShortRead: ns[22],
532 ShortWrite: ns[23],
533 JukeboxDelay: ns[24],
534 PNFSRead: ns[25],
535 PNFSWrite: ns[26],
536 }, nil
537}
538
539// parseNFSOperationStats parses a slice of NFSOperationStats by scanning
540// additional information about per-operation statistics until an empty
541// line is reached.
542func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
543 const (
khenaidoo26721882021-08-11 17:42:52 -0400544 // Minimum number of expected fields in each per-operation statistics set
545 minFields = 9
khenaidoo59ce9dd2019-11-11 13:05:32 -0500546 )
547
548 var ops []NFSOperationStats
549
550 for s.Scan() {
551 ss := strings.Fields(string(s.Bytes()))
552 if len(ss) == 0 {
553 // Must break when reading a blank line after per-operation stats to
554 // enable top-level function to parse the next device entry
555 break
556 }
557
khenaidoo26721882021-08-11 17:42:52 -0400558 if len(ss) < minFields {
Abhay Kumar40252eb2025-10-13 13:25:53 +0000559 return nil, fmt.Errorf("%w: invalid NFS per-operations stats: %v", ErrFileParse, ss)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500560 }
561
562 // Skip string operation name for integers
khenaidoo26721882021-08-11 17:42:52 -0400563 ns := make([]uint64, 0, minFields-1)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500564 for _, st := range ss[1:] {
565 n, err := strconv.ParseUint(st, 10, 64)
566 if err != nil {
567 return nil, err
568 }
569
570 ns = append(ns, n)
571 }
khenaidoo26721882021-08-11 17:42:52 -0400572 opStats := NFSOperationStats{
khenaidoo59ce9dd2019-11-11 13:05:32 -0500573 Operation: strings.TrimSuffix(ss[0], ":"),
574 Requests: ns[0],
575 Transmissions: ns[1],
576 MajorTimeouts: ns[2],
577 BytesSent: ns[3],
578 BytesReceived: ns[4],
579 CumulativeQueueMilliseconds: ns[5],
580 CumulativeTotalResponseMilliseconds: ns[6],
581 CumulativeTotalRequestMilliseconds: ns[7],
khenaidoo26721882021-08-11 17:42:52 -0400582 }
583
584 if len(ns) > 8 {
585 opStats.Errors = ns[8]
586 }
587
588 ops = append(ops, opStats)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500589 }
590
591 return ops, s.Err()
592}
593
594// parseNFSTransportStats parses a NFSTransportStats line using an input set of
595// integer fields matched to a specific stats version.
596func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats, error) {
597 // Extract the protocol field. It is the only string value in the line
598 protocol := ss[0]
599 ss = ss[1:]
600
601 switch statVersion {
602 case statVersion10:
603 var expectedLength int
Abhay Kumar40252eb2025-10-13 13:25:53 +0000604 switch protocol {
605 case "tcp":
khenaidoo59ce9dd2019-11-11 13:05:32 -0500606 expectedLength = fieldTransport10TCPLen
Abhay Kumar40252eb2025-10-13 13:25:53 +0000607 case "udp":
khenaidoo59ce9dd2019-11-11 13:05:32 -0500608 expectedLength = fieldTransport10UDPLen
Abhay Kumar40252eb2025-10-13 13:25:53 +0000609 default:
610 return nil, fmt.Errorf("%w: Invalid NFS protocol \"%s\" in stats 1.0 statement: %v", ErrFileParse, protocol, ss)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500611 }
612 if len(ss) != expectedLength {
Abhay Kumar40252eb2025-10-13 13:25:53 +0000613 return nil, fmt.Errorf("%w: Invalid NFS transport stats 1.0 statement: %v", ErrFileParse, ss)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500614 }
615 case statVersion11:
616 var expectedLength int
Abhay Kumar40252eb2025-10-13 13:25:53 +0000617 switch protocol {
618 case "tcp":
khenaidoo59ce9dd2019-11-11 13:05:32 -0500619 expectedLength = fieldTransport11TCPLen
Abhay Kumar40252eb2025-10-13 13:25:53 +0000620 case "udp":
khenaidoo59ce9dd2019-11-11 13:05:32 -0500621 expectedLength = fieldTransport11UDPLen
Abhay Kumar40252eb2025-10-13 13:25:53 +0000622 case "rdma":
623 expectedLength = fieldTransport11RDMAMinLen
624 default:
625 return nil, fmt.Errorf("%w: invalid NFS protocol \"%s\" in stats 1.1 statement: %v", ErrFileParse, protocol, ss)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500626 }
Abhay Kumar40252eb2025-10-13 13:25:53 +0000627 if (len(ss) != expectedLength && (protocol == "tcp" || protocol == "udp")) ||
628 (protocol == "rdma" && len(ss) < expectedLength) {
629 return nil, fmt.Errorf("%w: invalid NFS transport stats 1.1 statement: %v, protocol: %v", ErrFileParse, ss, protocol)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500630 }
631 default:
Abhay Kumar40252eb2025-10-13 13:25:53 +0000632 return nil, fmt.Errorf("%w: Unrecognized NFS transport stats version: %q, protocol: %v", ErrFileParse, statVersion, protocol)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500633 }
634
635 // Allocate enough for v1.1 stats since zero value for v1.1 stats will be okay
636 // in a v1.0 response. Since the stat length is bigger for TCP stats, we use
637 // the TCP length here.
638 //
639 // Note: slice length must be set to length of v1.1 stats to avoid a panic when
640 // only v1.0 stats are present.
641 // See: https://github.com/prometheus/node_exporter/issues/571.
Abhay Kumar40252eb2025-10-13 13:25:53 +0000642 //
643 // Note: NFS Over RDMA slice length is fieldTransport11RDMAMaxLen
644 ns := make([]uint64, fieldTransport11RDMAMaxLen+3)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500645 for i, s := range ss {
646 n, err := strconv.ParseUint(s, 10, 64)
647 if err != nil {
648 return nil, err
649 }
650
651 ns[i] = n
652 }
653
654 // The fields differ depending on the transport protocol (TCP or UDP)
655 // From https://utcc.utoronto.ca/%7Ecks/space/blog/linux/NFSMountstatsXprt
656 //
657 // For the udp RPC transport there is no connection count, connect idle time,
658 // or idle time (fields #3, #4, and #5); all other fields are the same. So
659 // we set them to 0 here.
Abhay Kumar40252eb2025-10-13 13:25:53 +0000660 switch protocol {
661 case "udp":
khenaidoo59ce9dd2019-11-11 13:05:32 -0500662 ns = append(ns[:2], append(make([]uint64, 3), ns[2:]...)...)
Abhay Kumar40252eb2025-10-13 13:25:53 +0000663 case "tcp":
664 ns = append(ns[:fieldTransport11TCPLen], make([]uint64, fieldTransport11RDMAMaxLen-fieldTransport11TCPLen+3)...)
665 case "rdma":
666 ns = append(ns[:fieldTransport10TCPLen], append(make([]uint64, 3), ns[fieldTransport10TCPLen:]...)...)
khenaidoo59ce9dd2019-11-11 13:05:32 -0500667 }
668
669 return &NFSTransportStats{
Abhay Kumar40252eb2025-10-13 13:25:53 +0000670 // NFS xprt over tcp or udp
khenaidoo59ce9dd2019-11-11 13:05:32 -0500671 Protocol: protocol,
672 Port: ns[0],
673 Bind: ns[1],
674 Connect: ns[2],
675 ConnectIdleTime: ns[3],
676 IdleTimeSeconds: ns[4],
677 Sends: ns[5],
678 Receives: ns[6],
679 BadTransactionIDs: ns[7],
680 CumulativeActiveRequests: ns[8],
681 CumulativeBacklog: ns[9],
Abhay Kumar40252eb2025-10-13 13:25:53 +0000682
683 // NFS xprt over tcp or udp
684 // And statVersion 1.1
685 MaximumRPCSlotsUsed: ns[10],
686 CumulativeSendingQueue: ns[11],
687 CumulativePendingQueue: ns[12],
688
689 // NFS xprt over rdma
690 // And stat Version 1.1
691 ReadChunkCount: ns[13],
692 WriteChunkCount: ns[14],
693 ReplyChunkCount: ns[15],
694 TotalRdmaRequest: ns[16],
695 PullupCopyCount: ns[17],
696 HardwayRegisterCount: ns[18],
697 FailedMarshalCount: ns[19],
698 BadReplyCount: ns[20],
699 MrsRecovered: ns[21],
700 MrsOrphaned: ns[22],
701 MrsAllocated: ns[23],
702 EmptySendctxQ: ns[24],
703 TotalRdmaReply: ns[25],
704 FixupCopyCount: ns[26],
705 ReplyWaitsForSend: ns[27],
706 LocalInvNeeded: ns[28],
707 NomsgCallCount: ns[29],
708 BcallCount: ns[30],
khenaidoo59ce9dd2019-11-11 13:05:32 -0500709 }, nil
710}