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\nopts:", 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: "NFS4.1 device with multiline impl_id OK", 258 s: "device 192.168.0.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nopts: 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.0.2,fsc,local_lock=none\nage: 1234567\nimpl_id: name='FreeBSD 11.2-STABLE #0 r325575+c9231c7d6bd(HEAD): Mon Nov 18 22:46:47 UTC 2019\nuser@host:/path/to/something'\n,domain='something.org',date='1293840000,0'", 259 mounts: []*Mount{{ 260 Device: "192.168.0.1:/srv", 261 Mount: "/mnt/nfs", 262 Type: "nfs4", 263 Stats: &MountStatsNFS{ 264 StatVersion: "1.1", 265 Opts: map[string]string{"rw": "", "vers": "4.1", 266 "rsize": "131072", "wsize": "131072", "namlen": "255", "acregmin": "3", 267 "acregmax": "60", "acdirmin": "30", "acdirmax": "60", "fsc": "", "hard": "", 268 "proto": "tcp", "timeo": "600", "retrans": "2", 269 "sec": "sys", "clientaddr": "192.168.0.2", 270 "local_lock": "none", 271 }, 272 Age: 1234567 * time.Second, 273 }, 274 }}, 275 }, 276 { 277 name: "fixtures/proc OK", 278 mounts: []*Mount{ 279 { 280 Device: "rootfs", 281 Mount: "/", 282 Type: "rootfs", 283 }, 284 { 285 Device: "sysfs", 286 Mount: "/sys", 287 Type: "sysfs", 288 }, 289 { 290 Device: "proc", 291 Mount: "/proc", 292 Type: "proc", 293 }, 294 { 295 Device: "/dev/sda1", 296 Mount: "/", 297 Type: "ext4", 298 }, 299 { 300 Device: "192.168.1.1:/srv/test", 301 Mount: "/mnt/nfs/test", 302 Type: "nfs4", 303 Stats: &MountStatsNFS{ 304 StatVersion: "1.1", 305 Opts: map[string]string{"rw": "", "vers": "4.0", 306 "rsize": "1048576", "wsize": "1048576", "namlen": "255", "acregmin": "3", 307 "acregmax": "60", "acdirmin": "30", "acdirmax": "60", "hard": "", 308 "proto": "tcp", "port": "0", "timeo": "600", "retrans": "2", 309 "sec": "sys", "mountaddr": "192.168.1.1", "clientaddr": "192.168.1.5", 310 "local_lock": "none", 311 }, 312 Age: 13968 * time.Second, 313 Bytes: NFSBytesStats{ 314 Read: 1207640230, 315 ReadTotal: 1210214218, 316 ReadPages: 295483, 317 }, 318 Events: NFSEventsStats{ 319 InodeRevalidate: 52, 320 DnodeRevalidate: 226, 321 VFSOpen: 1, 322 VFSLookup: 13, 323 VFSAccess: 398, 324 VFSReadPages: 331, 325 VFSWritePages: 47, 326 VFSFlush: 77, 327 VFSFileRelease: 77, 328 }, 329 Operations: []NFSOperationStats{ 330 { 331 Operation: "NULL", 332 }, 333 { 334 Operation: "READ", 335 Requests: 1298, 336 Transmissions: 1298, 337 BytesSent: 207680, 338 BytesReceived: 1210292152, 339 CumulativeQueueMilliseconds: 6, 340 CumulativeTotalResponseMilliseconds: 79386, 341 CumulativeTotalRequestMilliseconds: 79407, 342 }, 343 { 344 Operation: "WRITE", 345 }, 346 { 347 Operation: "ACCESS", 348 Requests: 2927395007, 349 Transmissions: 2927394995, 350 BytesSent: 526931094212, 351 BytesReceived: 362996810236, 352 CumulativeQueueMilliseconds: 18446743919241604546, 353 CumulativeTotalResponseMilliseconds: 1667369447, 354 CumulativeTotalRequestMilliseconds: 1953587717, 355 }, 356 }, 357 Transport: NFSTransportStats{ 358 Protocol: "tcp", 359 Port: 832, 360 Connect: 1, 361 IdleTimeSeconds: 11, 362 Sends: 6428, 363 Receives: 6428, 364 CumulativeActiveRequests: 12154, 365 MaximumRPCSlotsUsed: 24, 366 CumulativeSendingQueue: 26, 367 CumulativePendingQueue: 5726, 368 }, 369 }, 370 }, 371 }, 372 }, 373 } 374 375 for i, tt := range tests { 376 t.Logf("[%02d] test %q", i, tt.name) 377 378 var mounts []*Mount 379 var err error 380 381 if tt.s != "" { 382 mounts, err = parseMountStats(strings.NewReader(tt.s)) 383 } else { 384 proc, e := getProcFixtures(t).Proc(26231) 385 if e != nil { 386 t.Fatalf("failed to create proc: %v", err) 387 } 388 389 mounts, err = proc.MountStats() 390 } 391 392 if tt.invalid && err == nil { 393 t.Error("expected an error, but none occurred") 394 } 395 if !tt.invalid && err != nil { 396 t.Errorf("unexpected error: %w", err) 397 } 398 399 if want, have := tt.mounts, mounts; !reflect.DeepEqual(want, have) { 400 t.Errorf("mounts:\nwant:\n%v\nhave:\n%v", mountsStr(want), mountsStr(have)) 401 } 402 } 403} 404 405func mountsStr(mounts []*Mount) string { 406 var out string 407 for i, m := range mounts { 408 out += fmt.Sprintf("[%d] %q on %q (%q)", i, m.Device, m.Mount, m.Type) 409 410 stats, ok := m.Stats.(*MountStatsNFS) 411 if !ok { 412 out += "\n" 413 continue 414 } 415 416 out += fmt.Sprintf("\n\t- opts: %s", stats.Opts) 417 out += fmt.Sprintf("\n\t- v%s, age: %s", stats.StatVersion, stats.Age) 418 out += fmt.Sprintf("\n\t- bytes: %v", stats.Bytes) 419 out += fmt.Sprintf("\n\t- events: %v", stats.Events) 420 out += fmt.Sprintf("\n\t- transport: %v", stats.Transport) 421 out += fmt.Sprintf("\n\t- per-operation stats:") 422 423 for _, o := range stats.Operations { 424 out += fmt.Sprintf("\n\t\t- %v", o) 425 } 426 427 out += "\n" 428 } 429 430 return out 431} 432 433func TestMountStatsExtendedOperationStats(t *testing.T) { 434 r := strings.NewReader(extendedOpsExampleMountstats) 435 _, err := parseMountStats(r) 436 if err != nil { 437 t.Errorf("failed to parse mount stats with extended per-op statistics: %w", err) 438 } 439} 440 441const ( 442 extendedOpsExampleMountstats = ` 443device 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 444 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 445 age: 83520 446 impl_id: name='',domain='',date='0,0' 447 caps: caps=0x3fff7,wtmult=512,dtsize=32768,bsize=0,namlen=255 448 nfsv4: bm0=0xfdffafff,bm1=0xf9be3e,bm2=0x800,acl=0x0,sessions,pnfs=not configured,lease_time=90,lease_expired=0 449 sec: flavor=1,pseudoflavor=1 450 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 451 bytes: 2021340783 39056395530 0 0 1788561151 39087991255 442605 9557343 452 RPC iostats version: 1.1 p/v: 100003/4 (nfs) 453 xprt: tcp 940 0 2 0 1 938505 938504 0 12756069 0 32 254729 10823602 454 per-op statistics 455 NULL: 1 1 0 44 24 0 0 0 0 456 READ: 34096 34096 0 7103096 1792122744 2272 464840 467945 0 457 WRITE: 322308 322308 0 39161277084 56725504 401718334 10139998 411864389 0 458 COMMIT: 12541 12541 0 2709896 1304264 342 7179 7819 0 459 OPEN: 12637 12637 0 3923256 4659940 871 57185 58251 394 460 OPEN_CONFIRM: 0 0 0 0 0 0 0 0 0 461 OPEN_NOATTR: 98741 98741 0 25656212 31630800 3366 77710 82693 0 462 OPEN_DOWNGRADE: 0 0 0 0 0 0 0 0 0 463 CLOSE: 87075 87075 0 18778608 15308496 2026 49131 52399 116 464 SETATTR: 24576 24576 0 5825876 6522260 643 34384 35650 0 465 FSINFO: 1 1 0 168 152 0 0 0 0 466 RENEW: 0 0 0 0 0 0 0 0 0 467 SETCLIENTID: 0 0 0 0 0 0 0 0 0 468 SETCLIENTID_CONFIRM: 0 0 0 0 0 0 0 0 0 469 LOCK: 22512 22512 0 5417628 2521312 1088 17407 18794 2 470 LOCKT: 0 0 0 0 0 0 0 0 0 471 LOCKU: 21247 21247 0 4589352 2379664 315 8409 9003 0 472 ACCESS: 1466 1466 0 298160 246288 22 1394 1492 0 473 GETATTR: 52480 52480 0 10015464 12694076 2930 30069 34502 0 474 LOOKUP: 11727 11727 0 2518200 2886376 272 16935 17662 3546 475 LOOKUP_ROOT: 0 0 0 0 0 0 0 0 0 476 REMOVE: 833 833 0 172236 95268 15 4566 4617 68 477 RENAME: 11431 11431 0 3150708 1737512 211 52649 53091 0 478 LINK: 1 1 0 288 292 0 0 0 0 479 SYMLINK: 0 0 0 0 0 0 0 0 0 480 CREATE: 77 77 0 18292 23496 0 363 371 11 481 PATHCONF: 1 1 0 164 116 0 0 0 0 482 STATFS: 7420 7420 0 1394960 1187200 144 4672 4975 0 483 READLINK: 4 4 0 704 488 0 1 1 0 484 READDIR: 1353 1353 0 304024 2902928 11 4326 4411 0 485 SERVER_CAPS: 9 9 0 1548 1476 0 3 3 0 486 DELEGRETURN: 232 232 0 48896 37120 811 300 1115 0 487 GETACL: 0 0 0 0 0 0 0 0 0 488 SETACL: 0 0 0 0 0 0 0 0 0 489 FS_LOCATIONS: 0 0 0 0 0 0 0 0 0 490 RELEASE_LOCKOWNER: 0 0 0 0 0 0 0 0 0 491 SECINFO: 0 0 0 0 0 0 0 0 0 492 FSID_PRESENT: 0 0 0 0 0 0 0 0 0 493 EXCHANGE_ID: 2 2 0 464 200 0 0 0 0 494 CREATE_SESSION: 1 1 0 192 124 0 0 0 0 495 DESTROY_SESSION: 0 0 0 0 0 0 0 0 0 496 SEQUENCE: 0 0 0 0 0 0 0 0 0 497 GET_LEASE_TIME: 0 0 0 0 0 0 0 0 0 498 RECLAIM_COMPLETE: 1 1 0 124 88 0 81 81 0 499 LAYOUTGET: 0 0 0 0 0 0 0 0 0 500 GETDEVICEINFO: 0 0 0 0 0 0 0 0 0 501 LAYOUTCOMMIT: 0 0 0 0 0 0 0 0 0 502 LAYOUTRETURN: 0 0 0 0 0 0 0 0 0 503 SECINFO_NO_NAME: 0 0 0 0 0 0 0 0 0 504 TEST_STATEID: 0 0 0 0 0 0 0 0 0 505 FREE_STATEID: 10413 10413 0 1416168 916344 147 3518 3871 10413 506 GETDEVICELIST: 0 0 0 0 0 0 0 0 0 507 BIND_CONN_TO_SESSION: 0 0 0 0 0 0 0 0 0 508 DESTROY_CLIENTID: 0 0 0 0 0 0 0 0 0 509 SEEK: 0 0 0 0 0 0 0 0 0 510 ALLOCATE: 0 0 0 0 0 0 0 0 0 511 DEALLOCATE: 0 0 0 0 0 0 0 0 0 512 LAYOUTSTATS: 0 0 0 0 0 0 0 0 0 513 CLONE: 0 0 0 0 0 0 0 0 0 514 COPY: 0 0 0 0 0 0 0 0 0 515 OFFLOAD_CANCEL: 0 0 0 0 0 0 0 0 0 516 LOOKUPP: 0 0 0 0 0 0 0 0 0 517 LAYOUTERROR: 0 0 0 0 0 0 0 0 0 518` 519) 520