Codebase list golang-procfs / 9052b62
Enable parsing of '/proc/[pid]/mountstats' Matt Layher 7 years ago
5 changed file(s) with 836 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
1313 * Ji-Hoon, Seol <jihoon.seol@gmail.com>
1414 * Jonas Große Sundrup <cherti@letopolis.de>
1515 * Julius Volz <julius.volz@gmail.com>
16 * Matt Layher <mdlayher@gmail.com>
1617 * Matthias Rampke <mr@soundcloud.com>
1718 * Nicky Gerritsen <nicky@streamone.nl>
1819 * Rémi Audebert <contact@halfr.net>
0 device rootfs mounted on / with fstype rootfs
1 device sysfs mounted on /sys with fstype sysfs
2 device proc mounted on /proc with fstype proc
3 device /dev/sda1 mounted on / with fstype ext4
4 device 192.168.1.1:/srv/test mounted on /mnt/nfs/test with fstype nfs4 statvers=1.1
5 opts: rw,vers=4.0,rsize=1048576,wsize=1048576,namlen=255,acregmin=3,acregmax=60,acdirmin=30,acdirmax=60,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=192.168.1.5,local_lock=none
6 age: 13968
7 caps: caps=0xfff7,wtmult=512,dtsize=32768,bsize=0,namlen=255
8 nfsv4: bm0=0xfdffafff,bm1=0xf9be3e,bm2=0x0,acl=0x0,pnfs=not configured
9 sec: flavor=1,pseudoflavor=1
10 events: 52 226 0 0 1 13 398 0 0 331 0 47 0 0 77 0 0 77 0 0 0 0 0 0 0 0 0
11 bytes: 1207640230 0 0 0 1210214218 0 295483 0
12 RPC iostats version: 1.0 p/v: 100003/4 (nfs)
13 xprt: tcp 832 0 1 0 11 6428 6428 0 12154 0 24 26 5726
14 per-op statistics
15 NULL: 0 0 0 0 0 0 0 0
16 READ: 1298 1298 0 207680 1210292152 6 79386 79407
17 WRITE: 0 0 0 0 0 0 0 0
18
0 package procfs
1
2 // While implementing parsing of /proc/[pid]/mountstats, this blog was used
3 // heavily as a reference:
4 // https://utcc.utoronto.ca/~cks/space/blog/linux/NFSMountstatsIndex
5 //
6 // Special thanks to Chris Siebenmann for all of his posts explaining the
7 // various statistics available for NFS.
8
9 import (
10 "bufio"
11 "fmt"
12 "io"
13 "strconv"
14 "strings"
15 "time"
16 )
17
18 // Constants shared between multiple functions.
19 const (
20 deviceEntryLen = 8
21
22 fieldBytesLen = 8
23 fieldEventsLen = 27
24
25 statVersion10 = "1.0"
26 statVersion11 = "1.1"
27
28 fieldTransport10Len = 10
29 fieldTransport11Len = 13
30 )
31
32 // A Mount is a device mount parsed from /proc/[pid]/mountstats.
33 type Mount struct {
34 // Name of the device.
35 Device string
36 // The mount point of the device.
37 Mount string
38 // The filesystem type used by the device.
39 Type string
40 // If available additional statistics related to this Mount.
41 // Use a type assertion to determine if additional statistics are available.
42 Stats MountStats
43 }
44
45 // A MountStats is a type which contains detailed statistics for a specific
46 // type of Mount.
47 type MountStats interface {
48 mountStats()
49 }
50
51 // A MountStatsNFS is a MountStats implementation for NFSv3 and v4 mounts.
52 type MountStatsNFS struct {
53 // The version of statistics provided.
54 StatVersion string
55 // The age of the NFS mount.
56 Age time.Duration
57 // Statistics related to byte counters for various operations.
58 Bytes NFSBytesStats
59 // Statistics related to various NFS event occurrences.
60 Events NFSEventsStats
61 // Statistics broken down by filesystem operation.
62 Operations []NFSOperationStats
63 // Statistics about the NFS RPC transport.
64 Transport NFSTransportStats
65 }
66
67 // mountStats implements MountStats.
68 func (m MountStatsNFS) mountStats() {}
69
70 // A NFSBytesStats contains statistics about the number of bytes read and written
71 // by an NFS client to and from an NFS server.
72 type NFSBytesStats struct {
73 // Number of bytes read using the read() syscall.
74 Read int
75 // Number of bytes written using the write() syscall.
76 Write int
77 // Number of bytes read using the read() syscall in O_DIRECT mode.
78 DirectRead int
79 // Number of bytes written using the write() syscall in O_DIRECT mode.
80 DirectWrite int
81 // Number of bytes read from the NFS server, in total.
82 ReadTotal int
83 // Number of bytes written to the NFS server, in total.
84 WriteTotal int
85 // Number of pages read directly via mmap()'d files.
86 ReadPages int
87 // Number of pages written directly via mmap()'d files.
88 WritePages int
89 }
90
91 // A NFSEventsStats contains statistics about NFS event occurrences.
92 type NFSEventsStats struct {
93 // Number of times cached inode attributes are re-validated from the server.
94 InodeRevalidate int
95 // Number of times cached dentry nodes are re-validated from the server.
96 DnodeRevalidate int
97 // Number of times an inode cache is cleared.
98 DataInvalidate int
99 // Number of times cached inode attributes are invalidated.
100 AttributeInvalidate int
101 // Number of times files or directories have been open()'d.
102 VFSOpen int
103 // Number of times a directory lookup has occurred.
104 VFSLookup int
105 // Number of times permissions have been checked.
106 VFSAccess int
107 // Number of updates (and potential writes) to pages.
108 VFSUpdatePage int
109 // Number of pages read directly via mmap()'d files.
110 VFSReadPage int
111 // Number of times a group of pages have been read.
112 VFSReadPages int
113 // Number of pages written directly via mmap()'d files.
114 VFSWritePage int
115 // Number of times a group of pages have been written.
116 VFSWritePages int
117 // Number of times directory entries have been read with getdents().
118 VFSGetdents int
119 // Number of times attributes have been set on inodes.
120 VFSSetattr int
121 // Number of pending writes that have been forcefully flushed to the server.
122 VFSFlush int
123 // Number of times fsync() has been called on directories and files.
124 VFSFsync int
125 // Number of times locking has been attemped on a file.
126 VFSLock int
127 // Number of times files have been closed and released.
128 VFSFileRelease int
129 // Unknown. Possibly unused.
130 CongestionWait int
131 // Number of times files have been truncated.
132 Truncation int
133 // Number of times a file has been grown due to writes beyond its existing end.
134 WriteExtension int
135 // Number of times a file was removed while still open by another process.
136 SillyRename int
137 // Number of times the NFS server gave less data than expected while reading.
138 ShortRead int
139 // Number of times the NFS server wrote less data than expected while writing.
140 ShortWrite int
141 // Number of times the NFS server indicated EJUKEBOX; retrieving data from
142 // offline storage.
143 JukeboxDelay int
144 // Number of NFS v4.1+ pNFS reads.
145 PNFSRead int
146 // Number of NFS v4.1+ pNFS writes.
147 PNFSWrite int
148 }
149
150 // A NFSOperationStats contains statistics for a single operation.
151 type NFSOperationStats struct {
152 // The name of the operation.
153 Operation string
154 // Number of requests performed for this operation.
155 Requests int
156 // Number of times an actual RPC request has been transmitted for this operation.
157 Transmissions int
158 // Number of times a request has had a major timeout.
159 MajorTimeouts int
160 // Number of bytes sent for this operation, including RPC headers and payload.
161 BytesSent int
162 // Number of bytes received for this operation, including RPC headers and payload.
163 BytesReceived int
164 // Duration all requests spent queued for transmission before they were sent.
165 CumulativeQueueTime time.Duration
166 // Duration it took to get a reply back after the request was transmitted.
167 CumulativeTotalResponseTime time.Duration
168 // Duration from when a request was enqueued to when it was completely handled.
169 CumulativeTotalRequestTime time.Duration
170 }
171
172 // A NFSTransportStats contains statistics for the NFS mount RPC requests and
173 // responses.
174 type NFSTransportStats struct {
175 // The local port used for the NFS mount.
176 Port int
177 // Number of times the client has had to establish a connection from scratch
178 // to the NFS server.
179 Bind int
180 // Number of times the client has made a TCP connection to the NFS server.
181 Connect int
182 // Duration (in jiffies, a kernel internal unit of time) the NFS mount has
183 // spent waiting for connections to the server to be established.
184 ConnectIdleTime int
185 // Duration since the NFS mount last saw any RPC traffic.
186 IdleTime time.Duration
187 // Number of RPC requests for this mount sent to the NFS server.
188 Sends int
189 // Number of RPC responses for this mount received from the NFS server.
190 Receives int
191 // Number of times the NFS server sent a response with a transaction ID
192 // unknown to this client.
193 BadTransactionIDs int
194 // A running counter, incremented on each request as the current difference
195 // ebetween sends and receives.
196 CumulativeActiveRequests int
197 // A running counter, incremented on each request by the current backlog
198 // queue size.
199 CumulativeBacklog int
200
201 // Stats below only available with stat version 1.1.
202
203 // Maximum number of simultaneously active RPC requests ever used.
204 MaximumRPCSlotsUsed int
205 // A running counter, incremented on each request as the current size of the
206 // sending queue.
207 CumulativeSendingQueue int
208 // A running counter, incremented on each request as the current size of the
209 // pending queue.
210 CumulativePendingQueue int
211 }
212
213 // parseMountStats parses a /proc/[pid]/mountstats file and returns a slice
214 // of Mount structures containing detailed information about each mount.
215 // If available, statistics for each mount are parsed as well.
216 func parseMountStats(r io.Reader) ([]*Mount, error) {
217 const (
218 device = "device"
219 statVersionPrefix = "statvers="
220
221 nfs3Type = "nfs"
222 nfs4Type = "nfs4"
223 )
224
225 var mounts []*Mount
226
227 s := bufio.NewScanner(r)
228 for s.Scan() {
229 // Only look for device entries in this function
230 ss := strings.Fields(string(s.Bytes()))
231 if len(ss) == 0 || ss[0] != device {
232 continue
233 }
234
235 m, err := parseMount(ss)
236 if err != nil {
237 return nil, err
238 }
239
240 // Does this mount also possess statistics information?
241 if len(ss) > deviceEntryLen {
242 // Only NFSv3 and v4 are supported for parsing statistics
243 if m.Type != nfs3Type && m.Type != nfs4Type {
244 return nil, fmt.Errorf("cannot parse MountStats for fstype %q", m.Type)
245 }
246
247 statVersion := strings.TrimPrefix(ss[8], statVersionPrefix)
248
249 stats, err := parseMountStatsNFS(s, statVersion)
250 if err != nil {
251 return nil, err
252 }
253
254 m.Stats = stats
255 }
256
257 mounts = append(mounts, m)
258 }
259
260 return mounts, s.Err()
261 }
262
263 // parseMount parses an entry in /proc/[pid]/mountstats in the format:
264 // device [device] mounted on [mount] with fstype [type]
265 func parseMount(ss []string) (*Mount, error) {
266 if len(ss) < deviceEntryLen {
267 return nil, fmt.Errorf("invalid device entry: %v", ss)
268 }
269
270 // Check for specific words appearing at specific indices to ensure
271 // the format is consistent with what we expect
272 format := []struct {
273 i int
274 s string
275 }{
276 {i: 0, s: "device"},
277 {i: 2, s: "mounted"},
278 {i: 3, s: "on"},
279 {i: 5, s: "with"},
280 {i: 6, s: "fstype"},
281 }
282
283 for _, f := range format {
284 if ss[f.i] != f.s {
285 return nil, fmt.Errorf("invalid device entry: %v", ss)
286 }
287 }
288
289 return &Mount{
290 Device: ss[1],
291 Mount: ss[4],
292 Type: ss[7],
293 }, nil
294 }
295
296 // parseMountStatsNFS parses a MountStatsNFS by scanning additional information
297 // related to NFS statistics.
298 func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, error) {
299 // Field indicators for parsing specific types of data
300 const (
301 fieldAge = "age:"
302 fieldBytes = "bytes:"
303 fieldEvents = "events:"
304 fieldPerOpStats = "per-op"
305 fieldTransport = "xprt:"
306 )
307
308 stats := &MountStatsNFS{
309 StatVersion: statVersion,
310 }
311
312 for s.Scan() {
313 ss := strings.Fields(string(s.Bytes()))
314 if len(ss) == 0 {
315 break
316 }
317 if len(ss) < 2 {
318 return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
319 }
320
321 switch ss[0] {
322 case fieldAge:
323 // Age integer is in seconds
324 d, err := time.ParseDuration(ss[1] + "s")
325 if err != nil {
326 return nil, err
327 }
328
329 stats.Age = d
330 case fieldBytes:
331 bstats, err := parseNFSBytesStats(ss[1:])
332 if err != nil {
333 return nil, err
334 }
335
336 stats.Bytes = *bstats
337 case fieldEvents:
338 estats, err := parseNFSEventsStats(ss[1:])
339 if err != nil {
340 return nil, err
341 }
342
343 stats.Events = *estats
344 case fieldTransport:
345 if len(ss) < 3 {
346 return nil, fmt.Errorf("not enough information for NFS transport stats: %v", ss)
347 }
348
349 tstats, err := parseNFSTransportStats(ss[2:], statVersion)
350 if err != nil {
351 return nil, err
352 }
353
354 stats.Transport = *tstats
355 }
356
357 // When encountering "per-operation statistics", we must break this
358 // loop and parse them seperately to ensure we can terminate parsing
359 // before reaching another device entry; hence why this 'if' statement
360 // is not just another switch case
361 if ss[0] == fieldPerOpStats {
362 break
363 }
364 }
365
366 if err := s.Err(); err != nil {
367 return nil, err
368 }
369
370 // NFS per-operation stats appear last before the next device entry
371 perOpStats, err := parseNFSOperationStats(s)
372 if err != nil {
373 return nil, err
374 }
375
376 stats.Operations = perOpStats
377
378 return stats, nil
379 }
380
381 // parseNFSBytesStats parses a NFSBytesStats line using an input set of
382 // integer fields.
383 func parseNFSBytesStats(ss []string) (*NFSBytesStats, error) {
384 if len(ss) != fieldBytesLen {
385 return nil, fmt.Errorf("invalid NFS bytes stats: %v", ss)
386 }
387
388 ns := make([]int, 0, fieldBytesLen)
389 for _, s := range ss {
390 n, err := strconv.Atoi(s)
391 if err != nil {
392 return nil, err
393 }
394
395 ns = append(ns, n)
396 }
397
398 return &NFSBytesStats{
399 Read: ns[0],
400 Write: ns[1],
401 DirectRead: ns[2],
402 DirectWrite: ns[3],
403 ReadTotal: ns[4],
404 WriteTotal: ns[5],
405 ReadPages: ns[6],
406 WritePages: ns[7],
407 }, nil
408 }
409
410 // parseNFSEventsStats parses a NFSEventsStats line using an input set of
411 // integer fields.
412 func parseNFSEventsStats(ss []string) (*NFSEventsStats, error) {
413 if len(ss) != fieldEventsLen {
414 return nil, fmt.Errorf("invalid NFS events stats: %v", ss)
415 }
416
417 ns := make([]int, 0, fieldEventsLen)
418 for _, s := range ss {
419 n, err := strconv.Atoi(s)
420 if err != nil {
421 return nil, err
422 }
423
424 ns = append(ns, n)
425 }
426
427 return &NFSEventsStats{
428 InodeRevalidate: ns[0],
429 DnodeRevalidate: ns[1],
430 DataInvalidate: ns[2],
431 AttributeInvalidate: ns[3],
432 VFSOpen: ns[4],
433 VFSLookup: ns[5],
434 VFSAccess: ns[6],
435 VFSUpdatePage: ns[7],
436 VFSReadPage: ns[8],
437 VFSReadPages: ns[9],
438 VFSWritePage: ns[10],
439 VFSWritePages: ns[11],
440 VFSGetdents: ns[12],
441 VFSSetattr: ns[13],
442 VFSFlush: ns[14],
443 VFSFsync: ns[15],
444 VFSLock: ns[16],
445 VFSFileRelease: ns[17],
446 CongestionWait: ns[18],
447 Truncation: ns[19],
448 WriteExtension: ns[20],
449 SillyRename: ns[21],
450 ShortRead: ns[22],
451 ShortWrite: ns[23],
452 JukeboxDelay: ns[24],
453 PNFSRead: ns[25],
454 PNFSWrite: ns[26],
455 }, nil
456 }
457
458 // parseNFSOperationStats parses a slice of NFSOperationStats by scanning
459 // additional information about per-operation statistics until an empty
460 // line is reached.
461 func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
462 const (
463 // Number of expected fields in each per-operation statistics set
464 numFields = 9
465 )
466
467 var ops []NFSOperationStats
468
469 for s.Scan() {
470 ss := strings.Fields(string(s.Bytes()))
471 if len(ss) == 0 {
472 // Must break when reading a blank line after per-operation stats to
473 // enable top-level function to parse the next device entry
474 break
475 }
476
477 if len(ss) != numFields {
478 return nil, fmt.Errorf("invalid NFS per-operations stats: %v", ss)
479 }
480
481 // Skip string operation name for integers
482 ns := make([]int, 0, numFields-1)
483 for _, st := range ss[1:] {
484 n, err := strconv.Atoi(st)
485 if err != nil {
486 return nil, err
487 }
488
489 ns = append(ns, n)
490 }
491
492 ops = append(ops, NFSOperationStats{
493 Operation: strings.TrimSuffix(ss[0], ":"),
494 Requests: ns[0],
495 Transmissions: ns[1],
496 MajorTimeouts: ns[2],
497 BytesSent: ns[3],
498 BytesReceived: ns[4],
499 CumulativeQueueTime: time.Duration(ns[5]) * time.Millisecond,
500 CumulativeTotalResponseTime: time.Duration(ns[6]) * time.Millisecond,
501 CumulativeTotalRequestTime: time.Duration(ns[7]) * time.Millisecond,
502 })
503 }
504
505 return ops, s.Err()
506 }
507
508 // parseNFSTransportStats parses a NFSTransportStats line using an input set of
509 // integer fields matched to a specific stats version.
510 func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats, error) {
511 switch statVersion {
512 case statVersion10:
513 if len(ss) != fieldTransport10Len {
514 return nil, fmt.Errorf("invalid NFS transport stats 1.0 statement: %v", ss)
515 }
516 case statVersion11:
517 if len(ss) != fieldTransport11Len {
518 return nil, fmt.Errorf("invalid NFS transport stats 1.1 statement: %v", ss)
519 }
520 default:
521 return nil, fmt.Errorf("unrecognized NFS transport stats version: %q", statVersion)
522 }
523
524 // Allocate enough for v1.1 stats since zero value for v1.1 stats will be okay
525 // in a v1.0 response
526 ns := make([]int, 0, fieldTransport11Len)
527 for _, s := range ss {
528 n, err := strconv.Atoi(s)
529 if err != nil {
530 return nil, err
531 }
532
533 ns = append(ns, n)
534 }
535
536 return &NFSTransportStats{
537 Port: ns[0],
538 Bind: ns[1],
539 Connect: ns[2],
540 ConnectIdleTime: ns[3],
541 IdleTime: time.Duration(ns[4]) * time.Second,
542 Sends: ns[5],
543 Receives: ns[6],
544 BadTransactionIDs: ns[7],
545 CumulativeActiveRequests: ns[8],
546 CumulativeBacklog: ns[9],
547 MaximumRPCSlotsUsed: ns[10],
548 CumulativeSendingQueue: ns[11],
549 CumulativePendingQueue: ns[12],
550 }, nil
551 }
0 package procfs
1
2 import (
3 "fmt"
4 "reflect"
5 "strings"
6 "testing"
7 "time"
8 )
9
10 func TestMountStats(t *testing.T) {
11 tests := []struct {
12 name string
13 s string
14 fs bool
15 mounts []*Mount
16 invalid bool
17 }{
18 {
19 name: "no devices",
20 s: `hello`,
21 },
22 {
23 name: "device has too few fields",
24 s: `device foo`,
25 invalid: true,
26 },
27 {
28 name: "device incorrect format",
29 s: `device rootfs BAD on / with fstype rootfs`,
30 invalid: true,
31 },
32 {
33 name: "device incorrect format",
34 s: `device rootfs mounted BAD / with fstype rootfs`,
35 invalid: true,
36 },
37 {
38 name: "device incorrect format",
39 s: `device rootfs mounted on / BAD fstype rootfs`,
40 invalid: true,
41 },
42 {
43 name: "device incorrect format",
44 s: `device rootfs mounted on / with BAD rootfs`,
45 invalid: true,
46 },
47 {
48 name: "device rootfs cannot have stats",
49 s: `device rootfs mounted on / with fstype rootfs stats`,
50 invalid: true,
51 },
52 {
53 name: "NFSv4 device with too little info",
54 s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nhello",
55 invalid: true,
56 },
57 {
58 name: "NFSv4 device with bad bytes",
59 s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nbytes: 0",
60 invalid: true,
61 },
62 {
63 name: "NFSv4 device with bad events",
64 s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nevents: 0",
65 invalid: true,
66 },
67 {
68 name: "NFSv4 device with bad per-op stats",
69 s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nper-op statistics\nFOO 0",
70 invalid: true,
71 },
72 {
73 name: "NFSv4 device with bad transport stats",
74 s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nxprt: tcp",
75 invalid: true,
76 },
77 {
78 name: "NFSv4 device with bad transport version",
79 s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=foo\nxprt: tcp 0",
80 invalid: true,
81 },
82 {
83 name: "NFSv4 device with bad transport stats version 1.0",
84 s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.0\nxprt: tcp 0 0 0 0 0 0 0 0 0 0 0 0 0",
85 invalid: true,
86 },
87 {
88 name: "NFSv4 device with bad transport stats version 1.1",
89 s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nxprt: tcp 0 0 0 0 0 0 0 0 0 0",
90 invalid: true,
91 },
92 {
93 name: "device rootfs OK",
94 s: `device rootfs mounted on / with fstype rootfs`,
95 mounts: []*Mount{{
96 Device: "rootfs",
97 Mount: "/",
98 Type: "rootfs",
99 }},
100 },
101 {
102 name: "NFSv3 device with minimal stats OK",
103 s: `device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs statvers=1.1`,
104 mounts: []*Mount{{
105 Device: "192.168.1.1:/srv",
106 Mount: "/mnt/nfs",
107 Type: "nfs",
108 Stats: &MountStatsNFS{
109 StatVersion: "1.1",
110 },
111 }},
112 },
113 {
114 name: "fixtures OK",
115 fs: true,
116 mounts: []*Mount{
117 {
118 Device: "rootfs",
119 Mount: "/",
120 Type: "rootfs",
121 },
122 {
123 Device: "sysfs",
124 Mount: "/sys",
125 Type: "sysfs",
126 },
127 {
128 Device: "proc",
129 Mount: "/proc",
130 Type: "proc",
131 },
132 {
133 Device: "/dev/sda1",
134 Mount: "/",
135 Type: "ext4",
136 },
137 {
138 Device: "192.168.1.1:/srv/test",
139 Mount: "/mnt/nfs/test",
140 Type: "nfs4",
141 Stats: &MountStatsNFS{
142 StatVersion: "1.1",
143 Age: 13968 * time.Second,
144 Bytes: NFSBytesStats{
145 Read: 1207640230,
146 ReadTotal: 1210214218,
147 ReadPages: 295483,
148 },
149 Events: NFSEventsStats{
150 InodeRevalidate: 52,
151 DnodeRevalidate: 226,
152 VFSOpen: 1,
153 VFSLookup: 13,
154 VFSAccess: 398,
155 VFSReadPages: 331,
156 VFSWritePages: 47,
157 VFSFlush: 77,
158 VFSFileRelease: 77,
159 },
160 Operations: []NFSOperationStats{
161 {
162 Operation: "NULL",
163 },
164 {
165 Operation: "READ",
166 Requests: 1298,
167 Transmissions: 1298,
168 BytesSent: 207680,
169 BytesReceived: 1210292152,
170 CumulativeQueueTime: 6 * time.Millisecond,
171 CumulativeTotalResponseTime: 79386 * time.Millisecond,
172 CumulativeTotalRequestTime: 79407 * time.Millisecond,
173 },
174 {
175 Operation: "WRITE",
176 },
177 },
178 Transport: NFSTransportStats{
179 Port: 832,
180 Connect: 1,
181 IdleTime: 11 * time.Second,
182 Sends: 6428,
183 Receives: 6428,
184 CumulativeActiveRequests: 12154,
185 MaximumRPCSlotsUsed: 24,
186 CumulativeSendingQueue: 26,
187 CumulativePendingQueue: 5726,
188 },
189 },
190 },
191 },
192 },
193 }
194
195 for i, tt := range tests {
196 t.Logf("[%02d] test %q", i, tt.name)
197
198 var mounts []*Mount
199 var err error
200
201 if tt.s != "" {
202 mounts, err = parseMountStats(strings.NewReader(tt.s))
203 }
204 if tt.fs {
205 proc, err := FS("fixtures").NewProc(26231)
206 if err != nil {
207 t.Fatalf("failed to create proc: %v", err)
208 }
209
210 mounts, err = proc.MountStats()
211 }
212
213 if tt.invalid && err == nil {
214 t.Error("expected an error, but none occurred")
215 }
216 if !tt.invalid && err != nil {
217 t.Errorf("unexpected error: %v", err)
218 }
219
220 if want, have := tt.mounts, mounts; !reflect.DeepEqual(want, have) {
221 t.Errorf("mounts:\nwant:\n%v\nhave:\n%v", mountsStr(want), mountsStr(have))
222 }
223 }
224 }
225
226 func mountsStr(mounts []*Mount) string {
227 var out string
228 for i, m := range mounts {
229 out += fmt.Sprintf("[%d] %q on %q (%q)", i, m.Device, m.Mount, m.Type)
230
231 stats, ok := m.Stats.(*MountStatsNFS)
232 if !ok {
233 out += "\n"
234 continue
235 }
236
237 out += fmt.Sprintf("\n\t- v%s, age: %s", stats.StatVersion, stats.Age)
238 out += fmt.Sprintf("\n\t- bytes: %v", stats.Bytes)
239 out += fmt.Sprintf("\n\t- events: %v", stats.Events)
240 out += fmt.Sprintf("\n\t- transport: %v", stats.Transport)
241 out += fmt.Sprintf("\n\t- per-operation stats:")
242
243 for _, o := range stats.Operations {
244 out += fmt.Sprintf("\n\t\t- %v", o)
245 }
246
247 out += "\n"
248 }
249
250 return out
251 }
191191 return len(fds), nil
192192 }
193193
194 // MountStats retrieves statistics and configuration for mount points in a
195 // process's namespace.
196 func (p Proc) MountStats() ([]*Mount, error) {
197 f, err := os.Open(p.path("mountstats"))
198 if err != nil {
199 return nil, err
200 }
201 defer f.Close()
202
203 return parseMountStats(f)
204 }
205
194206 func (p Proc) fileDescriptors() ([]string, error) {
195207 d, err := os.Open(p.path("fd"))
196208 if err != nil {