// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package procfs
import (
"fmt"
"reflect"
"strings"
"testing"
"time"
)
func TestMountStats(t *testing.T) {
tests := []struct {
name string
s string
mounts []*Mount
invalid bool
}{
{
name: "no devices",
s: `hello`,
},
{
name: "device has too few fields",
s: `device foo`,
invalid: true,
},
{
name: "device incorrect format",
s: `device rootfs BAD on / with fstype rootfs`,
invalid: true,
},
{
name: "device incorrect format",
s: `device rootfs mounted BAD / with fstype rootfs`,
invalid: true,
},
{
name: "device incorrect format",
s: `device rootfs mounted on / BAD fstype rootfs`,
invalid: true,
},
{
name: "device incorrect format",
s: `device rootfs mounted on / with BAD rootfs`,
invalid: true,
},
{
name: "device rootfs cannot have stats",
s: `device rootfs mounted on / with fstype rootfs stats`,
invalid: true,
},
{
name: "NFSv4 device with too little info",
s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nopts:",
invalid: true,
},
{
name: "NFSv4 device with bad bytes",
s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nbytes: 0",
invalid: true,
},
{
name: "NFSv4 device with bad events",
s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nevents: 0",
invalid: true,
},
{
name: "NFSv4 device with bad per-op stats",
s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nper-op statistics\nFOO 0",
invalid: true,
},
{
name: "NFSv4 device with bad transport stats",
s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nxprt: tcp",
invalid: true,
},
{
name: "NFSv4 device with bad transport version",
s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=foo\nxprt: tcp 0",
invalid: true,
},
{
name: "NFSv4 device with bad transport stats version 1.0",
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",
invalid: true,
},
{
name: "NFSv4 device with bad transport stats version 1.1",
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",
invalid: true,
},
{
name: "NFSv3 device with bad transport protocol",
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",
invalid: true,
},
{
name: "NFSv3 device using TCP with transport stats version 1.0 OK",
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",
mounts: []*Mount{{
Device: "192.168.1.1:/srv",
Mount: "/mnt/nfs",
Type: "nfs",
Stats: &MountStatsNFS{
StatVersion: "1.0",
Transport: NFSTransportStats{
Protocol: "tcp",
Port: 1,
Bind: 2,
Connect: 3,
ConnectIdleTime: 4,
IdleTimeSeconds: 5,
Sends: 6,
Receives: 7,
BadTransactionIDs: 8,
CumulativeActiveRequests: 9,
CumulativeBacklog: 10,
MaximumRPCSlotsUsed: 0, // these three are not
CumulativeSendingQueue: 0, // present in statvers=1.0
CumulativePendingQueue: 0, //
},
},
}},
},
{
name: "NFSv3 device using UDP with transport stats version 1.0 OK",
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",
mounts: []*Mount{{
Device: "192.168.1.1:/srv",
Mount: "/mnt/nfs",
Type: "nfs",
Stats: &MountStatsNFS{
StatVersion: "1.0",
Transport: NFSTransportStats{
Protocol: "udp",
Port: 1,
Bind: 2,
Connect: 0,
ConnectIdleTime: 0,
IdleTimeSeconds: 0,
Sends: 3,
Receives: 4,
BadTransactionIDs: 5,
CumulativeActiveRequests: 6,
CumulativeBacklog: 7,
MaximumRPCSlotsUsed: 0, // these three are not
CumulativeSendingQueue: 0, // present in statvers=1.0
CumulativePendingQueue: 0, //
},
},
}},
},
{
name: "NFSv3 device using TCP with transport stats version 1.1 OK",
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",
mounts: []*Mount{{
Device: "192.168.1.1:/srv",
Mount: "/mnt/nfs",
Type: "nfs",
Stats: &MountStatsNFS{
StatVersion: "1.1",
Transport: NFSTransportStats{
Protocol: "tcp",
Port: 1,
Bind: 2,
Connect: 3,
ConnectIdleTime: 4,
IdleTimeSeconds: 5,
Sends: 6,
Receives: 7,
BadTransactionIDs: 8,
CumulativeActiveRequests: 9,
CumulativeBacklog: 10,
MaximumRPCSlotsUsed: 11,
CumulativeSendingQueue: 12,
CumulativePendingQueue: 13,
},
},
}},
},
{
name: "NFSv3 device using UDP with transport stats version 1.1 OK",
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",
mounts: []*Mount{{
Device: "192.168.1.1:/srv",
Mount: "/mnt/nfs",
Type: "nfs",
Stats: &MountStatsNFS{
StatVersion: "1.1",
Transport: NFSTransportStats{
Protocol: "udp",
Port: 1,
Bind: 2,
Connect: 0, // these three are not
ConnectIdleTime: 0, // present for UDP
IdleTimeSeconds: 0, //
Sends: 3,
Receives: 4,
BadTransactionIDs: 5,
CumulativeActiveRequests: 6,
CumulativeBacklog: 7,
MaximumRPCSlotsUsed: 8,
CumulativeSendingQueue: 9,
CumulativePendingQueue: 10,
},
},
}},
},
{
name: "NFSv3 device with mountaddr OK",
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",
mounts: []*Mount{{
Device: "192.168.1.1:/srv",
Mount: "/mnt/nfs",
Type: "nfs",
Stats: &MountStatsNFS{
StatVersion: "1.1",
Opts: map[string]string{"rw": "", "vers": "3", "mountaddr": "192.168.1.1", "proto": "udp"},
},
}},
},
{
name: "device rootfs OK",
s: `device rootfs mounted on / with fstype rootfs`,
mounts: []*Mount{{
Device: "rootfs",
Mount: "/",
Type: "rootfs",
}},
},
{
name: "NFSv3 device with minimal stats OK",
s: `device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs statvers=1.1`,
mounts: []*Mount{{
Device: "192.168.1.1:/srv",
Mount: "/mnt/nfs",
Type: "nfs",
Stats: &MountStatsNFS{
StatVersion: "1.1",
},
}},
},
{
name: "NFS4.1 device with multiline impl_id OK",
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'",
mounts: []*Mount{{
Device: "192.168.0.1:/srv",
Mount: "/mnt/nfs",
Type: "nfs4",
Stats: &MountStatsNFS{
StatVersion: "1.1",
Opts: map[string]string{"rw": "", "vers": "4.1",
"rsize": "131072", "wsize": "131072", "namlen": "255", "acregmin": "3",
"acregmax": "60", "acdirmin": "30", "acdirmax": "60", "fsc": "", "hard": "",
"proto": "tcp", "timeo": "600", "retrans": "2",
"sec": "sys", "clientaddr": "192.168.0.2",
"local_lock": "none",
},
Age: 1234567 * time.Second,
},
}},
},
{
name: "fixtures/proc OK",
mounts: []*Mount{
{
Device: "rootfs",
Mount: "/",
Type: "rootfs",
},
{
Device: "sysfs",
Mount: "/sys",
Type: "sysfs",
},
{
Device: "proc",
Mount: "/proc",
Type: "proc",
},
{
Device: "/dev/sda1",
Mount: "/",
Type: "ext4",
},
{
Device: "192.168.1.1:/srv/test",
Mount: "/mnt/nfs/test",
Type: "nfs4",
Stats: &MountStatsNFS{
StatVersion: "1.1",
Opts: map[string]string{"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", "mountaddr": "192.168.1.1", "clientaddr": "192.168.1.5",
"local_lock": "none",
},
Age: 13968 * time.Second,
Bytes: NFSBytesStats{
Read: 1207640230,
ReadTotal: 1210214218,
ReadPages: 295483,
},
Events: NFSEventsStats{
InodeRevalidate: 52,
DnodeRevalidate: 226,
VFSOpen: 1,
VFSLookup: 13,
VFSAccess: 398,
VFSReadPages: 331,
VFSWritePages: 47,
VFSFlush: 77,
VFSFileRelease: 77,
},
Operations: []NFSOperationStats{
{
Operation: "NULL",
},
{
Operation: "READ",
Requests: 1298,
Transmissions: 1298,
BytesSent: 207680,
BytesReceived: 1210292152,
CumulativeQueueMilliseconds: 6,
CumulativeTotalResponseMilliseconds: 79386,
CumulativeTotalRequestMilliseconds: 79407,
},
{
Operation: "WRITE",
},
{
Operation: "ACCESS",
Requests: 2927395007,
Transmissions: 2927394995,
BytesSent: 526931094212,
BytesReceived: 362996810236,
CumulativeQueueMilliseconds: 18446743919241604546,
CumulativeTotalResponseMilliseconds: 1667369447,
CumulativeTotalRequestMilliseconds: 1953587717,
},
},
Transport: NFSTransportStats{
Protocol: "tcp",
Port: 832,
Connect: 1,
IdleTimeSeconds: 11,
Sends: 6428,
Receives: 6428,
CumulativeActiveRequests: 12154,
MaximumRPCSlotsUsed: 24,
CumulativeSendingQueue: 26,
CumulativePendingQueue: 5726,
},
},
},
},
},
}
for i, tt := range tests {
t.Logf("[%02d] test %q", i, tt.name)
var mounts []*Mount
var err error
if tt.s != "" {
mounts, err = parseMountStats(strings.NewReader(tt.s))
} else {
proc, e := getProcFixtures(t).Proc(26231)
if e != nil {
t.Fatalf("failed to create proc: %v", err)
}
mounts, err = proc.MountStats()
}
if tt.invalid && err == nil {
t.Error("expected an error, but none occurred")
}
if !tt.invalid && err != nil {
t.Errorf("unexpected error: %v", err)
}
if want, have := tt.mounts, mounts; !reflect.DeepEqual(want, have) {
t.Errorf("mounts:\nwant:\n%v\nhave:\n%v", mountsStr(want), mountsStr(have))
}
}
}
func mountsStr(mounts []*Mount) string {
var out string
for i, m := range mounts {
out += fmt.Sprintf("[%d] %q on %q (%q)", i, m.Device, m.Mount, m.Type)
stats, ok := m.Stats.(*MountStatsNFS)
if !ok {
out += "\n"
continue
}
out += fmt.Sprintf("\n\t- opts: %s", stats.Opts)
out += fmt.Sprintf("\n\t- v%s, age: %s", stats.StatVersion, stats.Age)
out += fmt.Sprintf("\n\t- bytes: %v", stats.Bytes)
out += fmt.Sprintf("\n\t- events: %v", stats.Events)
out += fmt.Sprintf("\n\t- transport: %v", stats.Transport)
out += fmt.Sprintf("\n\t- per-operation stats:")
for _, o := range stats.Operations {
out += fmt.Sprintf("\n\t\t- %v", o)
}
out += "\n"
}
return out
}
func TestMountStatsExtendedOperationStats(t *testing.T) {
r := strings.NewReader(extendedOpsExampleMountstats)
_, err := parseMountStats(r)
if err != nil {
t.Errorf("failed to parse mount stats with extended per-op statistics: %v", err)
}
}
const (
extendedOpsExampleMountstats = `
device 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
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
age: 83520
impl_id: name='',domain='',date='0,0'
caps: caps=0x3fff7,wtmult=512,dtsize=32768,bsize=0,namlen=255
nfsv4: bm0=0xfdffafff,bm1=0xf9be3e,bm2=0x800,acl=0x0,sessions,pnfs=not configured,lease_time=90,lease_expired=0
sec: flavor=1,pseudoflavor=1
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
bytes: 2021340783 39056395530 0 0 1788561151 39087991255 442605 9557343
RPC iostats version: 1.1 p/v: 100003/4 (nfs)
xprt: tcp 940 0 2 0 1 938505 938504 0 12756069 0 32 254729 10823602
per-op statistics
NULL: 1 1 0 44 24 0 0 0 0
READ: 34096 34096 0 7103096 1792122744 2272 464840 467945 0
WRITE: 322308 322308 0 39161277084 56725504 401718334 10139998 411864389 0
COMMIT: 12541 12541 0 2709896 1304264 342 7179 7819 0
OPEN: 12637 12637 0 3923256 4659940 871 57185 58251 394
OPEN_CONFIRM: 0 0 0 0 0 0 0 0 0
OPEN_NOATTR: 98741 98741 0 25656212 31630800 3366 77710 82693 0
OPEN_DOWNGRADE: 0 0 0 0 0 0 0 0 0
CLOSE: 87075 87075 0 18778608 15308496 2026 49131 52399 116
SETATTR: 24576 24576 0 5825876 6522260 643 34384 35650 0
FSINFO: 1 1 0 168 152 0 0 0 0
RENEW: 0 0 0 0 0 0 0 0 0
SETCLIENTID: 0 0 0 0 0 0 0 0 0
SETCLIENTID_CONFIRM: 0 0 0 0 0 0 0 0 0
LOCK: 22512 22512 0 5417628 2521312 1088 17407 18794 2
LOCKT: 0 0 0 0 0 0 0 0 0
LOCKU: 21247 21247 0 4589352 2379664 315 8409 9003 0
ACCESS: 1466 1466 0 298160 246288 22 1394 1492 0
GETATTR: 52480 52480 0 10015464 12694076 2930 30069 34502 0
LOOKUP: 11727 11727 0 2518200 2886376 272 16935 17662 3546
LOOKUP_ROOT: 0 0 0 0 0 0 0 0 0
REMOVE: 833 833 0 172236 95268 15 4566 4617 68
RENAME: 11431 11431 0 3150708 1737512 211 52649 53091 0
LINK: 1 1 0 288 292 0 0 0 0
SYMLINK: 0 0 0 0 0 0 0 0 0
CREATE: 77 77 0 18292 23496 0 363 371 11
PATHCONF: 1 1 0 164 116 0 0 0 0
STATFS: 7420 7420 0 1394960 1187200 144 4672 4975 0
READLINK: 4 4 0 704 488 0 1 1 0
READDIR: 1353 1353 0 304024 2902928 11 4326 4411 0
SERVER_CAPS: 9 9 0 1548 1476 0 3 3 0
DELEGRETURN: 232 232 0 48896 37120 811 300 1115 0
GETACL: 0 0 0 0 0 0 0 0 0
SETACL: 0 0 0 0 0 0 0 0 0
FS_LOCATIONS: 0 0 0 0 0 0 0 0 0
RELEASE_LOCKOWNER: 0 0 0 0 0 0 0 0 0
SECINFO: 0 0 0 0 0 0 0 0 0
FSID_PRESENT: 0 0 0 0 0 0 0 0 0
EXCHANGE_ID: 2 2 0 464 200 0 0 0 0
CREATE_SESSION: 1 1 0 192 124 0 0 0 0
DESTROY_SESSION: 0 0 0 0 0 0 0 0 0
SEQUENCE: 0 0 0 0 0 0 0 0 0
GET_LEASE_TIME: 0 0 0 0 0 0 0 0 0
RECLAIM_COMPLETE: 1 1 0 124 88 0 81 81 0
LAYOUTGET: 0 0 0 0 0 0 0 0 0
GETDEVICEINFO: 0 0 0 0 0 0 0 0 0
LAYOUTCOMMIT: 0 0 0 0 0 0 0 0 0
LAYOUTRETURN: 0 0 0 0 0 0 0 0 0
SECINFO_NO_NAME: 0 0 0 0 0 0 0 0 0
TEST_STATEID: 0 0 0 0 0 0 0 0 0
FREE_STATEID: 10413 10413 0 1416168 916344 147 3518 3871 10413
GETDEVICELIST: 0 0 0 0 0 0 0 0 0
BIND_CONN_TO_SESSION: 0 0 0 0 0 0 0 0 0
DESTROY_CLIENTID: 0 0 0 0 0 0 0 0 0
SEEK: 0 0 0 0 0 0 0 0 0
ALLOCATE: 0 0 0 0 0 0 0 0 0
DEALLOCATE: 0 0 0 0 0 0 0 0 0
LAYOUTSTATS: 0 0 0 0 0 0 0 0 0
CLONE: 0 0 0 0 0 0 0 0 0
COPY: 0 0 0 0 0 0 0 0 0
OFFLOAD_CANCEL: 0 0 0 0 0 0 0 0 0
LOOKUPP: 0 0 0 0 0 0 0 0 0
LAYOUTERROR: 0 0 0 0 0 0 0 0 0
`
)