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
14package procfs
15
16import (
17	"fmt"
18	"reflect"
19	"strings"
20	"testing"
21	"time"
22)
23
24func TestMountStats(t *testing.T) {
25	tests := []struct {
26		name    string
27		s       string
28		mounts  []*Mount
29		invalid bool
30	}{
31		{
32			name: "no devices",
33			s:    `hello`,
34		},
35		{
36			name:    "device has too few fields",
37			s:       `device foo`,
38			invalid: true,
39		},
40		{
41			name:    "device incorrect format",
42			s:       `device rootfs BAD on / with fstype rootfs`,
43			invalid: true,
44		},
45		{
46			name:    "device incorrect format",
47			s:       `device rootfs mounted BAD / with fstype rootfs`,
48			invalid: true,
49		},
50		{
51			name:    "device incorrect format",
52			s:       `device rootfs mounted on / BAD fstype rootfs`,
53			invalid: true,
54		},
55		{
56			name:    "device incorrect format",
57			s:       `device rootfs mounted on / with BAD rootfs`,
58			invalid: true,
59		},
60		{
61			name:    "device rootfs cannot have stats",
62			s:       `device rootfs mounted on / with fstype rootfs stats`,
63			invalid: true,
64		},
65		{
66			name:    "NFSv4 device with too little info",
67			s:       "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nhello",
68			invalid: true,
69		},
70		{
71			name:    "NFSv4 device with bad bytes",
72			s:       "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nbytes: 0",
73			invalid: true,
74		},
75		{
76			name:    "NFSv4 device with bad events",
77			s:       "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nevents: 0",
78			invalid: true,
79		},
80		{
81			name:    "NFSv4 device with bad per-op stats",
82			s:       "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nper-op statistics\nFOO 0",
83			invalid: true,
84		},
85		{
86			name:    "NFSv4 device with bad transport stats",
87			s:       "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nxprt: tcp",
88			invalid: true,
89		},
90		{
91			name:    "NFSv4 device with bad transport version",
92			s:       "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=foo\nxprt: tcp 0",
93			invalid: true,
94		},
95		{
96			name:    "NFSv4 device with bad transport stats version 1.0",
97			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",
98			invalid: true,
99		},
100		{
101			name:    "NFSv4 device with bad transport stats version 1.1",
102			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",
103			invalid: true,
104		},
105		{
106			name:    "NFSv3 device with bad transport protocol",
107			s:       "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nxprt: tcpx 0 0 0 0 0 0 0 0 0 0",
108			invalid: true,
109		},
110		{
111			name: "NFSv3 device using TCP with transport stats version 1.0 OK",
112			s:    "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs statvers=1.0\nxprt: tcp 1 2 3 4 5 6 7 8 9 10",
113			mounts: []*Mount{{
114				Device: "192.168.1.1:/srv",
115				Mount:  "/mnt/nfs",
116				Type:   "nfs",
117				Stats: &MountStatsNFS{
118					StatVersion: "1.0",
119					Transport: NFSTransportStats{
120						Protocol:                 "tcp",
121						Port:                     1,
122						Bind:                     2,
123						Connect:                  3,
124						ConnectIdleTime:          4,
125						IdleTimeSeconds:          5,
126						Sends:                    6,
127						Receives:                 7,
128						BadTransactionIDs:        8,
129						CumulativeActiveRequests: 9,
130						CumulativeBacklog:        10,
131						MaximumRPCSlotsUsed:      0, // these three are not
132						CumulativeSendingQueue:   0, // present in statvers=1.0
133						CumulativePendingQueue:   0, //
134					},
135				},
136			}},
137		},
138		{
139			name: "NFSv3 device using UDP with transport stats version 1.0 OK",
140			s:    "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs statvers=1.0\nxprt: udp 1 2 3 4 5 6 7",
141			mounts: []*Mount{{
142				Device: "192.168.1.1:/srv",
143				Mount:  "/mnt/nfs",
144				Type:   "nfs",
145				Stats: &MountStatsNFS{
146					StatVersion: "1.0",
147					Transport: NFSTransportStats{
148						Protocol:                 "udp",
149						Port:                     1,
150						Bind:                     2,
151						Connect:                  0,
152						ConnectIdleTime:          0,
153						IdleTimeSeconds:          0,
154						Sends:                    3,
155						Receives:                 4,
156						BadTransactionIDs:        5,
157						CumulativeActiveRequests: 6,
158						CumulativeBacklog:        7,
159						MaximumRPCSlotsUsed:      0, // these three are not
160						CumulativeSendingQueue:   0, // present in statvers=1.0
161						CumulativePendingQueue:   0, //
162					},
163				},
164			}},
165		},
166		{
167			name: "NFSv3 device using TCP with transport stats version 1.1 OK",
168			s:    "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs statvers=1.1\nxprt: tcp 1 2 3 4 5 6 7 8 9 10 11 12 13",
169			mounts: []*Mount{{
170				Device: "192.168.1.1:/srv",
171				Mount:  "/mnt/nfs",
172				Type:   "nfs",
173				Stats: &MountStatsNFS{
174					StatVersion: "1.1",
175					Transport: NFSTransportStats{
176						Protocol:                 "tcp",
177						Port:                     1,
178						Bind:                     2,
179						Connect:                  3,
180						ConnectIdleTime:          4,
181						IdleTimeSeconds:          5,
182						Sends:                    6,
183						Receives:                 7,
184						BadTransactionIDs:        8,
185						CumulativeActiveRequests: 9,
186						CumulativeBacklog:        10,
187						MaximumRPCSlotsUsed:      11,
188						CumulativeSendingQueue:   12,
189						CumulativePendingQueue:   13,
190					},
191				},
192			}},
193		},
194		{
195			name: "NFSv3 device using UDP with transport stats version 1.1 OK",
196			s:    "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs statvers=1.1\nxprt: udp 1 2 3 4 5 6 7 8 9 10",
197			mounts: []*Mount{{
198				Device: "192.168.1.1:/srv",
199				Mount:  "/mnt/nfs",
200				Type:   "nfs",
201				Stats: &MountStatsNFS{
202					StatVersion: "1.1",
203					Transport: NFSTransportStats{
204						Protocol:                 "udp",
205						Port:                     1,
206						Bind:                     2,
207						Connect:                  0, // these three are not
208						ConnectIdleTime:          0, // present for UDP
209						IdleTimeSeconds:          0, //
210						Sends:                    3,
211						Receives:                 4,
212						BadTransactionIDs:        5,
213						CumulativeActiveRequests: 6,
214						CumulativeBacklog:        7,
215						MaximumRPCSlotsUsed:      8,
216						CumulativeSendingQueue:   9,
217						CumulativePendingQueue:   10,
218					},
219				},
220			}},
221		},
222		{
223			name: "NFSv3 device with mountaddr OK",
224			s:    "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs statvers=1.1\nopts: rw,vers=3,mountaddr=192.168.1.1,proto=udp\n",
225			mounts: []*Mount{{
226				Device: "192.168.1.1:/srv",
227				Mount:  "/mnt/nfs",
228				Type:   "nfs",
229				Stats: &MountStatsNFS{
230					StatVersion: "1.1",
231					Opts:        map[string]string{"rw": "", "vers": "3", "mountaddr": "192.168.1.1", "proto": "udp"},
232				},
233			}},
234		},
235		{
236			name: "device rootfs OK",
237			s:    `device rootfs mounted on / with fstype rootfs`,
238			mounts: []*Mount{{
239				Device: "rootfs",
240				Mount:  "/",
241				Type:   "rootfs",
242			}},
243		},
244		{
245			name: "NFSv3 device with minimal stats OK",
246			s:    `device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs statvers=1.1`,
247			mounts: []*Mount{{
248				Device: "192.168.1.1:/srv",
249				Mount:  "/mnt/nfs",
250				Type:   "nfs",
251				Stats: &MountStatsNFS{
252					StatVersion: "1.1",
253				},
254			}},
255		},
256		{
257			name: "fixtures/proc OK",
258			mounts: []*Mount{
259				{
260					Device: "rootfs",
261					Mount:  "/",
262					Type:   "rootfs",
263				},
264				{
265					Device: "sysfs",
266					Mount:  "/sys",
267					Type:   "sysfs",
268				},
269				{
270					Device: "proc",
271					Mount:  "/proc",
272					Type:   "proc",
273				},
274				{
275					Device: "/dev/sda1",
276					Mount:  "/",
277					Type:   "ext4",
278				},
279				{
280					Device: "192.168.1.1:/srv/test",
281					Mount:  "/mnt/nfs/test",
282					Type:   "nfs4",
283					Stats: &MountStatsNFS{
284						StatVersion: "1.1",
285						Opts: map[string]string{"rw": "", "vers": "4.0",
286							"rsize": "1048576", "wsize": "1048576", "namlen": "255", "acregmin": "3",
287							"acregmax": "60", "acdirmin": "30", "acdirmax": "60", "hard": "",
288							"proto": "tcp", "port": "0", "timeo": "600", "retrans": "2",
289							"sec": "sys", "mountaddr": "192.168.1.1", "clientaddr": "192.168.1.5",
290							"local_lock": "none",
291						},
292						Age: 13968 * time.Second,
293						Bytes: NFSBytesStats{
294							Read:      1207640230,
295							ReadTotal: 1210214218,
296							ReadPages: 295483,
297						},
298						Events: NFSEventsStats{
299							InodeRevalidate: 52,
300							DnodeRevalidate: 226,
301							VFSOpen:         1,
302							VFSLookup:       13,
303							VFSAccess:       398,
304							VFSReadPages:    331,
305							VFSWritePages:   47,
306							VFSFlush:        77,
307							VFSFileRelease:  77,
308						},
309						Operations: []NFSOperationStats{
310							{
311								Operation: "NULL",
312							},
313							{
314								Operation:                           "READ",
315								Requests:                            1298,
316								Transmissions:                       1298,
317								BytesSent:                           207680,
318								BytesReceived:                       1210292152,
319								CumulativeQueueMilliseconds:         6,
320								CumulativeTotalResponseMilliseconds: 79386,
321								CumulativeTotalRequestMilliseconds:  79407,
322							},
323							{
324								Operation: "WRITE",
325							},
326							{
327								Operation:                           "ACCESS",
328								Requests:                            2927395007,
329								Transmissions:                       2927394995,
330								BytesSent:                           526931094212,
331								BytesReceived:                       362996810236,
332								CumulativeQueueMilliseconds:         18446743919241604546,
333								CumulativeTotalResponseMilliseconds: 1667369447,
334								CumulativeTotalRequestMilliseconds:  1953587717,
335							},
336						},
337						Transport: NFSTransportStats{
338							Protocol:                 "tcp",
339							Port:                     832,
340							Connect:                  1,
341							IdleTimeSeconds:          11,
342							Sends:                    6428,
343							Receives:                 6428,
344							CumulativeActiveRequests: 12154,
345							MaximumRPCSlotsUsed:      24,
346							CumulativeSendingQueue:   26,
347							CumulativePendingQueue:   5726,
348						},
349					},
350				},
351			},
352		},
353	}
354
355	for i, tt := range tests {
356		t.Logf("[%02d] test %q", i, tt.name)
357
358		var mounts []*Mount
359		var err error
360
361		if tt.s != "" {
362			mounts, err = parseMountStats(strings.NewReader(tt.s))
363		} else {
364			proc, e := getProcFixtures(t).Proc(26231)
365			if e != nil {
366				t.Fatalf("failed to create proc: %v", err)
367			}
368
369			mounts, err = proc.MountStats()
370		}
371
372		if tt.invalid && err == nil {
373			t.Error("expected an error, but none occurred")
374		}
375		if !tt.invalid && err != nil {
376			t.Errorf("unexpected error: %v", err)
377		}
378
379		if want, have := tt.mounts, mounts; !reflect.DeepEqual(want, have) {
380			t.Errorf("mounts:\nwant:\n%v\nhave:\n%v", mountsStr(want), mountsStr(have))
381		}
382	}
383}
384
385func mountsStr(mounts []*Mount) string {
386	var out string
387	for i, m := range mounts {
388		out += fmt.Sprintf("[%d] %q on %q (%q)", i, m.Device, m.Mount, m.Type)
389
390		stats, ok := m.Stats.(*MountStatsNFS)
391		if !ok {
392			out += "\n"
393			continue
394		}
395
396		out += fmt.Sprintf("\n\t- opts: %s", stats.Opts)
397		out += fmt.Sprintf("\n\t- v%s, age: %s", stats.StatVersion, stats.Age)
398		out += fmt.Sprintf("\n\t- bytes: %v", stats.Bytes)
399		out += fmt.Sprintf("\n\t- events: %v", stats.Events)
400		out += fmt.Sprintf("\n\t- transport: %v", stats.Transport)
401		out += fmt.Sprintf("\n\t- per-operation stats:")
402
403		for _, o := range stats.Operations {
404			out += fmt.Sprintf("\n\t\t- %v", o)
405		}
406
407		out += "\n"
408	}
409
410	return out
411}
412
413func TestMountStatsExtendedOperationStats(t *testing.T) {
414	r := strings.NewReader(extendedOpsExampleMountstats)
415	_, err := parseMountStats(r)
416	if err != nil {
417		t.Errorf("failed to parse mount stats with extended per-op statistics: %v", err)
418	}
419}
420
421const (
422	extendedOpsExampleMountstats = `
423device fs.example.com:/volume4/apps/home-automation/node-red-data mounted on /var/lib/kubelet/pods/1c2215a7-0d92-4df5-83ce-a807bcc2f8c8/volumes/kubernetes.io~nfs/home-automation--node-red-data--pv0001 with fstype nfs4 statvers=1.1
424	opts:   rw,vers=4.1,rsize=131072,wsize=131072,namlen=255,acregmin=3,acregmax=60,acdirmin=30,acdirmax=60,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=192.168.1.191,local_lock=none
425	age:    83520
426	impl_id:        name='',domain='',date='0,0'
427	caps:   caps=0x3fff7,wtmult=512,dtsize=32768,bsize=0,namlen=255
428	nfsv4:  bm0=0xfdffafff,bm1=0xf9be3e,bm2=0x800,acl=0x0,sessions,pnfs=not configured,lease_time=90,lease_expired=0
429	sec:    flavor=1,pseudoflavor=1
430	events: 52472 472680 16671 57552 2104 9565 749555 9568641 168 24103 1 267134 3350 20097 116581 18214 43757 111141 0 28 9563845 34 0 0 0 0 0
431	bytes:  2021340783 39056395530 0 0 1788561151 39087991255 442605 9557343
432	RPC iostats version: 1.1  p/v: 100003/4 (nfs)
433	xprt:   tcp 940 0 2 0 1 938505 938504 0 12756069 0 32 254729 10823602
434	per-op statistics
435			NULL: 1 1 0 44 24 0 0 0 0
436			READ: 34096 34096 0 7103096 1792122744 2272 464840 467945 0
437			WRITE: 322308 322308 0 39161277084 56725504 401718334 10139998 411864389 0
438			COMMIT: 12541 12541 0 2709896 1304264 342 7179 7819 0
439			OPEN: 12637 12637 0 3923256 4659940 871 57185 58251 394
440	OPEN_CONFIRM: 0 0 0 0 0 0 0 0 0
441		OPEN_NOATTR: 98741 98741 0 25656212 31630800 3366 77710 82693 0
442	OPEN_DOWNGRADE: 0 0 0 0 0 0 0 0 0
443			CLOSE: 87075 87075 0 18778608 15308496 2026 49131 52399 116
444			SETATTR: 24576 24576 0 5825876 6522260 643 34384 35650 0
445			FSINFO: 1 1 0 168 152 0 0 0 0
446			RENEW: 0 0 0 0 0 0 0 0 0
447		SETCLIENTID: 0 0 0 0 0 0 0 0 0
448	SETCLIENTID_CONFIRM: 0 0 0 0 0 0 0 0 0
449			LOCK: 22512 22512 0 5417628 2521312 1088 17407 18794 2
450			LOCKT: 0 0 0 0 0 0 0 0 0
451			LOCKU: 21247 21247 0 4589352 2379664 315 8409 9003 0
452			ACCESS: 1466 1466 0 298160 246288 22 1394 1492 0
453			GETATTR: 52480 52480 0 10015464 12694076 2930 30069 34502 0
454			LOOKUP: 11727 11727 0 2518200 2886376 272 16935 17662 3546
455		LOOKUP_ROOT: 0 0 0 0 0 0 0 0 0
456			REMOVE: 833 833 0 172236 95268 15 4566 4617 68
457			RENAME: 11431 11431 0 3150708 1737512 211 52649 53091 0
458			LINK: 1 1 0 288 292 0 0 0 0
459			SYMLINK: 0 0 0 0 0 0 0 0 0
460			CREATE: 77 77 0 18292 23496 0 363 371 11
461		PATHCONF: 1 1 0 164 116 0 0 0 0
462			STATFS: 7420 7420 0 1394960 1187200 144 4672 4975 0
463		READLINK: 4 4 0 704 488 0 1 1 0
464			READDIR: 1353 1353 0 304024 2902928 11 4326 4411 0
465		SERVER_CAPS: 9 9 0 1548 1476 0 3 3 0
466		DELEGRETURN: 232 232 0 48896 37120 811 300 1115 0
467			GETACL: 0 0 0 0 0 0 0 0 0
468			SETACL: 0 0 0 0 0 0 0 0 0
469	FS_LOCATIONS: 0 0 0 0 0 0 0 0 0
470	RELEASE_LOCKOWNER: 0 0 0 0 0 0 0 0 0
471			SECINFO: 0 0 0 0 0 0 0 0 0
472	FSID_PRESENT: 0 0 0 0 0 0 0 0 0
473		EXCHANGE_ID: 2 2 0 464 200 0 0 0 0
474	CREATE_SESSION: 1 1 0 192 124 0 0 0 0
475	DESTROY_SESSION: 0 0 0 0 0 0 0 0 0
476		SEQUENCE: 0 0 0 0 0 0 0 0 0
477	GET_LEASE_TIME: 0 0 0 0 0 0 0 0 0
478	RECLAIM_COMPLETE: 1 1 0 124 88 0 81 81 0
479		LAYOUTGET: 0 0 0 0 0 0 0 0 0
480	GETDEVICEINFO: 0 0 0 0 0 0 0 0 0
481	LAYOUTCOMMIT: 0 0 0 0 0 0 0 0 0
482	LAYOUTRETURN: 0 0 0 0 0 0 0 0 0
483	SECINFO_NO_NAME: 0 0 0 0 0 0 0 0 0
484	TEST_STATEID: 0 0 0 0 0 0 0 0 0
485	FREE_STATEID: 10413 10413 0 1416168 916344 147 3518 3871 10413
486	GETDEVICELIST: 0 0 0 0 0 0 0 0 0
487	BIND_CONN_TO_SESSION: 0 0 0 0 0 0 0 0 0
488	DESTROY_CLIENTID: 0 0 0 0 0 0 0 0 0
489			SEEK: 0 0 0 0 0 0 0 0 0
490		ALLOCATE: 0 0 0 0 0 0 0 0 0
491		DEALLOCATE: 0 0 0 0 0 0 0 0 0
492		LAYOUTSTATS: 0 0 0 0 0 0 0 0 0
493			CLONE: 0 0 0 0 0 0 0 0 0
494			COPY: 0 0 0 0 0 0 0 0 0
495	OFFLOAD_CANCEL: 0 0 0 0 0 0 0 0 0
496			LOOKUPP: 0 0 0 0 0 0 0 0 0
497		LAYOUTERROR: 0 0 0 0 0 0 0 0 0
498`
499)
500