Repository: prometheus/procfs (go)

Issue:
Support for parsing opts line in /proc/self/mountstats
Currently procfs does not support parsing of the ```opts:``` line in proc/self/mountstats. Having these values would give users access to more info related to their mounts. This would also help with resolving an issue with node_exporter (https://github.com/prometheus/node_exporter/issues/993) where different versions of nfs would not be recognized thus causing only a single form of metrics to show.

Pull request description:
mountstats: parse nfs mount options
Adds a map called 'Opts' to the stats which contains
the key value pairs from the 'opts' line
in /proc/<pid>/mountstats.

Fixes #102

This also changes functionality introduced in PR #130.  Instead of using a specific struct field, `MountAddress`, the mountaddr option is available via `Opts["mountaddr"]`.

Patch:
diff --git a/Makefile b/Makefile
index 314d1ba..616a0d2 100644
--- a/Makefile
+++ b/Makefile
@@ -14,6 +14,7 @@
 include Makefile.common
 
 %/.unpacked: %.ttar
+	@echo ">> extracting fixtures"
 	./ttar -C $(dir $*) -x -f $*.ttar
 	touch $@
 
diff --git a/Makefile.common b/Makefile.common
index 4f18ea5..c7f9ea6 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -69,7 +69,7 @@ else
 	GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)
 endif
 
-PROMU_VERSION ?= 0.3.0
+PROMU_VERSION ?= 0.4.0
 PROMU_URL     := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
 
 GOLANGCI_LINT :=
diff --git a/fixtures.ttar b/fixtures.ttar
index f7f84ef..97157bf 100644
--- a/fixtures.ttar
+++ b/fixtures.ttar
@@ -81,7 +81,7 @@ device sysfs mounted on /sys with fstype sysfs
 device proc mounted on /proc with fstype proc
 device /dev/sda1 mounted on / with fstype ext4
 device 192.168.1.1:/srv/test mounted on /mnt/nfs/test with fstype nfs4 statvers=1.1
-	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
+	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,mountaddr=192.168.1.1,clientaddr=192.168.1.5,local_lock=none
 	age:	13968
 	caps:	caps=0xfff7,wtmult=512,dtsize=32768,bsize=0,namlen=255
 	nfsv4:	bm0=0xfdffafff,bm1=0xf9be3e,bm2=0x0,acl=0x0,pnfs=not configured
@@ -120,6 +120,63 @@ SymlinkTo: net:[4026531993]
 Path: fixtures/proc/26231/root
 SymlinkTo: /
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/26231/status
+Lines: 53
+
+Name:	prometheus
+Umask:	0022
+State:	S (sleeping)
+Tgid:	1
+Ngid:	0
+Pid:	1
+PPid:	0
+TracerPid:	0
+Uid:	0	0	0	0
+Gid:	0	0	0	0
+FDSize:	128
+Groups:
+NStgid:	1
+NSpid:	1
+NSpgid:	1
+NSsid:	1
+VmPeak:	   58472 kB
+VmSize:	   58440 kB
+VmLck:	       0 kB
+VmPin:	       0 kB
+VmHWM:	    8028 kB
+VmRSS:	    6716 kB
+RssAnon:	    2092 kB
+RssFile:	    4624 kB
+RssShmem:	       0 kB
+VmData:	    2580 kB
+VmStk:	     136 kB
+VmExe:	     948 kB
+VmLib:	    6816 kB
+VmPTE:	     128 kB
+VmPMD:	      12 kB
+VmSwap:	     660 kB
+HugetlbPages:	       0 kB
+Threads:	1
+SigQ:	8/63965
+SigPnd:	0000000000000000
+ShdPnd:	0000000000000000
+SigBlk:	7be3c0fe28014a03
+SigIgn:	0000000000001000
+SigCgt:	00000001800004ec
+CapInh:	0000000000000000
+CapPrm:	0000003fffffffff
+CapEff:	0000003fffffffff
+CapBnd:	0000003fffffffff
+CapAmb:	0000000000000000
+Seccomp:	0
+Cpus_allowed:	ff
+Cpus_allowed_list:	0-7
+Mems_allowed:	00000000,00000001
+Mems_allowed_list:	0
+voluntary_ctxt_switches:	4742839
+nonvoluntary_ctxt_switches:	1727500
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/26231/stat
 Lines: 1
 26231 (vim) R 5392 7446 5392 34835 7446 4218880 32533 309516 26 82 1677 44 158 99 20 0 1 0 82375 56274944 1981 18446744073709551615 4194304 6294284 140736914091744 140736914087944 139965136429984 0 0 12288 1870679807 0 0 0 17 0 0 0 31 0 0 8391624 8481048 16420864 140736914093252 140736914093279 140736914093279 140736914096107 0
@@ -160,23 +217,23 @@ SymlinkTo: ../../symlinktargets/xyz
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/26232/limits
 Lines: 17
-Limit                     Soft Limit           Hard Limit           Units     
-Max cpu time              unlimited            unlimited            seconds   
-Max file size             unlimited            unlimited            bytes     
-Max data size             unlimited            unlimited            bytes     
-Max stack size            8388608              unlimited            bytes     
-Max core file size        0                    unlimited            bytes     
-Max resident set          unlimited            unlimited            bytes     
-Max processes             29436                29436                processes 
-Max open files            1024                 4096                 files     
-Max locked memory         65536                65536                bytes     
-Max address space         unlimited            unlimited            bytes     
-Max file locks            unlimited            unlimited            locks     
-Max pending signals       29436                29436                signals   
-Max msgqueue size         819200               819200               bytes     
-Max nice priority         0                    0                    
-Max realtime priority     0                    0                    
-Max realtime timeout      unlimited            unlimited            us        
+Limit                     Soft Limit           Hard Limit           Units
+Max cpu time              unlimited            unlimited            seconds
+Max file size             unlimited            unlimited            bytes
+Max data size             unlimited            unlimited            bytes
+Max stack size            8388608              unlimited            bytes
+Max core file size        0                    unlimited            bytes
+Max resident set          unlimited            unlimited            bytes
+Max processes             29436                29436                processes
+Max open files            1024                 4096                 files
+Max locked memory         65536                65536                bytes
+Max address space         unlimited            unlimited            bytes
+Max file locks            unlimited            unlimited            locks
+Max pending signals       29436                29436                signals
+Max msgqueue size         819200               819200               bytes
+Max nice priority         0                    0
+Max realtime priority     0                    0
+Max realtime timeout      unlimited            unlimited            us
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/26232/root
@@ -206,9 +263,9 @@ Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/buddyinfo
 Lines: 3
-Node 0, zone      DMA      1      0      1      0      2      1      1      0      1      1      3 
-Node 0, zone    DMA32    759    572    791    475    194     45     12      0      0      0      0 
-Node 0, zone   Normal   4381   1093    185   1530    567    102      4      0      0      0      0 
+Node 0, zone      DMA      1      0      1      0      2      1      1      0      1      1      3
+Node 0, zone    DMA32    759    572    791    475    194     45     12      0      0      0      0
+Node 0, zone   Normal   4381   1093    185   1530    567    102      4      0      0      0      0
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/diskstats
@@ -302,13 +359,13 @@ Lines: 26
 Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10]
 md3 : active raid6 sda1[8] sdh1[7] sdg1[6] sdf1[5] sde1[11] sdd1[3] sdc1[10] sdb1[9]
       5853468288 blocks super 1.2 level 6, 64k chunk, algorithm 2 [8/8] [UUUUUUUU]
-      
+
 md127 : active raid1 sdi2[0] sdj2[1]
       312319552 blocks [2/2] [UU]
-      
+
 md0 : active raid1 sdk[2](S) sdi1[0] sdj1[1]
       248896 blocks [2/2] [UU]
-      
+
 md4 : inactive raid1 sda3[0] sdb3[1]
       4883648 blocks [2/2] [UU]
 
diff --git a/mountstats.go b/mountstats.go
index fc385af..7e8f044 100644
--- a/mountstats.go
+++ b/mountstats.go
@@ -69,8 +69,8 @@ type MountStats interface {
 type MountStatsNFS struct {
 	// The version of statistics provided.
 	StatVersion string
-	// The optional mountaddr of the NFS mount.
-	MountAddress string
+	// The mount options of the NFS mount.
+	Opts map[string]string
 	// The age of the NFS mount.
 	Age time.Duration
 	// Statistics related to byte counters for various operations.
@@ -342,10 +342,15 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
 
 		switch ss[0] {
 		case fieldOpts:
+			if stats.Opts == nil {
+				stats.Opts = map[string]string{}
+			}
 			for _, opt := range strings.Split(ss[1], ",") {
 				split := strings.Split(opt, "=")
-				if len(split) == 2 && split[0] == "mountaddr" {
-					stats.MountAddress = split[1]
+				if len(split) == 2 {
+					stats.Opts[split[0]] = split[1]
+				} else {
+					stats.Opts[opt] = ""
 				}
 			}
 		case fieldAge:
diff --git a/net_dev.go b/net_dev.go
index 0063594..8249c98 100644
--- a/net_dev.go
+++ b/net_dev.go
@@ -75,7 +75,7 @@ func newNetDev(file string) (NetDev, error) {
 	}
 	defer f.Close()
 
-	nd := NetDev{}
+	netDev := NetDev{}
 	s := bufio.NewScanner(f)
 	for n := 0; s.Scan(); n++ {
 		// Skip the 2 header lines.
@@ -83,20 +83,20 @@ func newNetDev(file string) (NetDev, error) {
 			continue
 		}
 
-		line, err := nd.parseLine(s.Text())
+		line, err := netDev.parseLine(s.Text())
 		if err != nil {
-			return nd, err
+			return netDev, err
 		}
 
-		nd[line.Name] = *line
+		netDev[line.Name] = *line
 	}
 
-	return nd, s.Err()
+	return netDev, s.Err()
 }
 
 // parseLine parses a single line from the /proc/net/dev file. Header lines
 // must be filtered prior to calling this method.
-func (nd NetDev) parseLine(rawLine string) (*NetDevLine, error) {
+func (netDev NetDev) parseLine(rawLine string) (*NetDevLine, error) {
 	parts := strings.SplitN(rawLine, ":", 2)
 	if len(parts) != 2 {
 		return nil, errors.New("invalid net/dev line, missing colon")
@@ -185,11 +185,11 @@ func (nd NetDev) parseLine(rawLine string) (*NetDevLine, error) {
 
 // Total aggregates the values across interfaces and returns a new NetDevLine.
 // The Name field will be a sorted comma separated list of interface names.
-func (nd NetDev) Total() NetDevLine {
+func (netDev NetDev) Total() NetDevLine {
 	total := NetDevLine{}
 
-	names := make([]string, 0, len(nd))
-	for _, ifc := range nd {
+	names := make([]string, 0, len(netDev))
+	for _, ifc := range netDev {
 		names = append(names, ifc.Name)
 		total.RxBytes += ifc.RxBytes
 		total.RxPackets += ifc.RxPackets
diff --git a/proc_status.go b/proc_status.go
new file mode 100644
index 0000000..6b4b61f
--- /dev/null
+++ b/proc_status.go
@@ -0,0 +1,162 @@
+// 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 (
+	"bytes"
+	"io/ioutil"
+	"os"
+	"strconv"
+	"strings"
+)
+
+// ProcStat provides status information about the process,
+// read from /proc/[pid]/stat.
+type ProcStatus struct {
+	// The process ID.
+	PID int
+	// The process name.
+	Name string
+
+	// Peak virtual memory size.
+	VmPeak uint64
+	// Virtual memory size.
+	VmSize uint64
+	// Locked memory size.
+	VmLck uint64
+	// Pinned memory size.
+	VmPin uint64
+	// Peak resident set size.
+	VmHWM uint64
+	// Resident set size (sum of RssAnnon RssFile and RssShmem).
+	VmRSS uint64
+	// Size of resident anonymous memory.
+	RssAnon uint64
+	// Size of resident file mappings.
+	RssFile uint64
+	// Size of resident shared memory.
+	RssShmem uint64
+	// Size of data segments.
+	VmData uint64
+	// Size of stack segments.
+	VmStk uint64
+	// Size of text segments.
+	VmExe uint64
+	// Shared library code size.
+	VmLib uint64
+	// Page table entries size.
+	VmPTE uint64
+	// Size of second-level page tables.
+	VmPMD uint64
+	// Swapped-out virtual memory size by anonymous private.
+	VmSwap uint64
+	// Size of hugetlb memory portions
+	HugetlbPages uint64
+
+	// Number of voluntary context switches.
+	VoluntaryCtxtSwitches uint64
+	// Number of involuntary context switches.
+	NonVoluntaryCtxtSwitches uint64
+}
+
+// NewStatus returns the current status information of the process.
+func (p Proc) NewStatus() (ProcStatus, error) {
+	f, err := os.Open(p.path("status"))
+	if err != nil {
+		return ProcStatus{}, err
+	}
+	defer f.Close()
+
+	data, err := ioutil.ReadAll(f)
+	if err != nil {
+		return ProcStatus{}, err
+	}
+
+	s := ProcStatus{PID: p.PID}
+
+	lines := strings.Split(string(data), "\n")
+	for _, line := range lines {
+		if !bytes.Contains([]byte(line), []byte(":")) {
+			continue
+		}
+
+		kv := strings.SplitN(line, ":", 2)
+
+		// removes spaces
+		k := string(strings.TrimSpace(kv[0]))
+		v := string(strings.TrimSpace(kv[1]))
+		// removes "kB"
+		v = string(bytes.Trim([]byte(v), " kB"))
+
+		// value to int when possible
+		// we can skip error check here, 'cause vKBytes is not used when value is a string
+		vKBytes, _ := strconv.ParseUint(v, 10, 64)
+		// convert kB to B
+		vBytes := vKBytes * 1024
+
+		s.fillStatus(k, v, vKBytes, vBytes)
+	}
+
+	return s, nil
+}
+
+func (s *ProcStatus) fillStatus(k string, vString string, vUint uint64, vUintBytes uint64) {
+	switch k {
+	case "Name":
+		s.Name = vString
+	case "VmPeak":
+		s.VmPeak = vUintBytes
+	case "VmSize":
+		s.VmSize = vUintBytes
+	case "VmLck":
+		s.VmLck = vUintBytes
+	case "VmPin":
+		s.VmPin = vUintBytes
+	case "VmHWM":
+		s.VmHWM = vUintBytes
+	case "VmRSS":
+		s.VmRSS = vUintBytes
+	case "RssAnon":
+		s.RssAnon = vUintBytes
+	case "RssFile":
+		s.RssFile = vUintBytes
+	case "RssShmem":
+		s.RssShmem = vUintBytes
+	case "VmData":
+		s.VmData = vUintBytes
+	case "VmStk":
+		s.VmStk = vUintBytes
+	case "VmExe":
+		s.VmExe = vUintBytes
+	case "VmLib":
+		s.VmLib = vUintBytes
+	case "VmPTE":
+		s.VmPTE = vUintBytes
+	case "VmPMD":
+		s.VmPMD = vUintBytes
+	case "VmSwap":
+		s.VmSwap = vUintBytes
+	case "HugetlbPages":
+		s.HugetlbPages = vUintBytes
+	case "voluntary_ctxt_switches":
+		s.VoluntaryCtxtSwitches = vUint
+	case "nonvoluntary_ctxt_switches":
+		s.NonVoluntaryCtxtSwitches = vUint
+	}
+}
+
+// TotalCtxtSwitches returns the total context switch.
+func (s ProcStatus) TotalCtxtSwitches() uint64 {
+	return s.VoluntaryCtxtSwitches + s.NonVoluntaryCtxtSwitches
+}
diff --git a/ttar b/ttar
index b0171a1..19ef02b 100755
--- a/ttar
+++ b/ttar
@@ -86,8 +86,10 @@ Usage:   $bname [-C <DIR>] -c -f <ARCHIVE> <FILE...> (create archive)
          $bname [-C <DIR>] -x -f <ARCHIVE>           (extract archive)
 
 Options:
-         -C <DIR>                                    (change directory)
-         -v                                          (verbose)
+         -C <DIR>           (change directory)
+         -v                 (verbose)
+         --recursive-unlink (recursively delete existing directory if path
+                             collides with file or directory to extract)
 
 Example: Change to sysfs directory, create ttar file from fixtures directory
          $bname -C sysfs -c -f sysfs/fixtures.ttar fixtures/
@@ -111,8 +113,9 @@ function set_cmd {
 }
 
 unset VERBOSE
+unset RECURSIVE_UNLINK
 
-while getopts :cf:htxvC: opt; do
+while getopts :cf:-:htxvC: opt; do
     case $opt in
         c)
             set_cmd "create"
@@ -136,6 +139,18 @@ while getopts :cf:htxvC: opt; do
         C)
             CDIR=$OPTARG
             ;;
+        -)
+            case $OPTARG in
+                recursive-unlink)
+                    RECURSIVE_UNLINK="yes"
+                    ;;
+                *)
+                    echo -e "Error: invalid option -$OPTARG"
+                    echo
+                    usage 1
+                    ;;
+            esac
+            ;;
         *)
             echo >&2 "ERROR: invalid option -$OPTARG"
             echo
@@ -212,16 +227,16 @@ function extract {
         local eof_without_newline
         if [ "$size" -gt 0 ]; then
             if [[ "$line" =~ [^\\]EOF ]]; then
-                # An EOF not preceeded by a backslash indicates that the line
+                # An EOF not preceded by a backslash indicates that the line
                 # does not end with a newline
                 eof_without_newline=1
             else
                 eof_without_newline=0
             fi
             # Replace NULLBYTE with null byte if at beginning of line
-            # Replace NULLBYTE with null byte unless preceeded by backslash
+            # Replace NULLBYTE with null byte unless preceded by backslash
             # Remove one backslash in front of NULLBYTE (if any)
-            # Remove EOF unless preceeded by backslash
+            # Remove EOF unless preceded by backslash
             # Remove one backslash in front of EOF
             if [ $USE_PYTHON -eq 1 ]; then
                 echo -n "$line" | python -c "$PYTHON_EXTRACT_FILTER" >> "$path"
@@ -245,7 +260,16 @@ function extract {
         fi
         if [[ $line =~ ^Path:\ (.*)$ ]]; then
             path=${BASH_REMATCH[1]}
-            if [ -e "$path" ] || [ -L "$path" ]; then
+            if [ -L "$path" ]; then
+                rm "$path"
+            elif [ -d "$path" ]; then
+                if [ "${RECURSIVE_UNLINK:-}" == "yes" ]; then
+                    rm -r "$path"
+                else
+                    # Safe because symlinks to directories are dealt with above
+                    rmdir "$path"
+                fi
+            elif [ -e "$path" ]; then
                 rm "$path"
             fi
         elif [[ $line =~ ^Lines:\ (.*)$ ]]; then
@@ -338,8 +362,8 @@ function _create {
             else
                 < "$file" \
                     sed 's/EOF/\\EOF/g;
-                         s/NULLBYTE/\\NULLBYTE/g;
-                         s/\x0/NULLBYTE/g;
+                            s/NULLBYTE/\\NULLBYTE/g;
+                            s/\x0/NULLBYTE/g;
                     '
             fi
             if [[ "$eof_without_newline" -eq 1 ]]; then


Test patch:
diff --git a/mountstats_test.go b/mountstats_test.go
index 727af07..b25ca8f 100644
--- a/mountstats_test.go
+++ b/mountstats_test.go
@@ -227,8 +227,8 @@ func TestMountStats(t *testing.T) {
 				Mount:  "/mnt/nfs",
 				Type:   "nfs",
 				Stats: &MountStatsNFS{
-					StatVersion:  "1.1",
-					MountAddress: "192.168.1.1",
+					StatVersion: "1.1",
+					Opts:        map[string]string{"rw": "", "vers": "3", "mountaddr": "192.168.1.1", "proto": "udp"},
 				},
 			}},
 		},
@@ -282,7 +282,14 @@ func TestMountStats(t *testing.T) {
 					Type:   "nfs4",
 					Stats: &MountStatsNFS{
 						StatVersion: "1.1",
-						Age:         13968 * time.Second,
+						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,
@@ -376,7 +383,7 @@ func mountsStr(mounts []*Mount) string {
 			continue
 		}
 
-		out += fmt.Sprintf("\n\t- mountaddr: %s", stats.MountAddress)
+		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)
diff --git a/net_dev_test.go b/net_dev_test.go
index 363c194..1c829bb 100644
--- a/net_dev_test.go
+++ b/net_dev_test.go
@@ -37,7 +37,7 @@ func TestNewNetDev(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	nd, err := fs.NewNetDev()
+	netDev, err := fs.NewNetDev()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -49,10 +49,10 @@ func TestNewNetDev(t *testing.T) {
 		"eth0":        {Name: "eth0", RxBytes: 874354587, RxPackets: 1036395, TxBytes: 563352563, TxPackets: 732147},
 	}
 
-	if want, have := len(lines), len(nd); want != have {
+	if want, have := len(lines), len(netDev); want != have {
 		t.Errorf("want %d parsed net/dev lines, have %d", want, have)
 	}
-	for _, line := range nd {
+	for _, line := range netDev {
 		if want, have := lines[line.Name], line; want != have {
 			t.Errorf("%s: want %v, have %v", line.Name, want, have)
 		}
@@ -65,7 +65,7 @@ func TestProcNewNetDev(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	nd, err := p.NewNetDev()
+	netDev, err := p.NewNetDev()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -75,10 +75,10 @@ func TestProcNewNetDev(t *testing.T) {
 		"eth0": {Name: "eth0", RxBytes: 438, RxPackets: 5, TxBytes: 648, TxPackets: 8},
 	}
 
-	if want, have := len(lines), len(nd); want != have {
+	if want, have := len(lines), len(netDev); want != have {
 		t.Errorf("want %d parsed net/dev lines, have %d", want, have)
 	}
-	for _, line := range nd {
+	for _, line := range netDev {
 		if want, have := lines[line.Name], line; want != have {
 			t.Errorf("%s: want %v, have %v", line.Name, want, have)
 		}
diff --git a/proc_status_test.go b/proc_status_test.go
new file mode 100644
index 0000000..fdf2ac4
--- /dev/null
+++ b/proc_status_test.go
@@ -0,0 +1,76 @@
+// 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 (
+	"testing"
+)
+
+func TestProcStatus(t *testing.T) {
+	p, err := getProcFixtures(t).NewProc(26231)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	s, err := p.NewStatus()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for _, test := range []struct {
+		name string
+		want int
+		have int
+	}{
+		{name: "pid", want: 26231, have: s.PID},
+		{name: "VmPeak", want: 58472 * 1024, have: int(s.VmPeak)},
+		{name: "VmSize", want: 58440 * 1024, have: int(s.VmSize)},
+		{name: "VmLck", want: 0 * 1024, have: int(s.VmLck)},
+		{name: "VmPin", want: 0 * 1024, have: int(s.VmPin)},
+		{name: "VmHWM", want: 8028 * 1024, have: int(s.VmHWM)},
+		{name: "VmRSS", want: 6716 * 1024, have: int(s.VmRSS)},
+		{name: "RssAnon", want: 2092 * 1024, have: int(s.RssAnon)},
+		{name: "RssFile", want: 4624 * 1024, have: int(s.RssFile)},
+		{name: "RssShmem", want: 0 * 1024, have: int(s.RssShmem)},
+		{name: "VmData", want: 2580 * 1024, have: int(s.VmData)},
+		{name: "VmStk", want: 136 * 1024, have: int(s.VmStk)},
+		{name: "VmExe", want: 948 * 1024, have: int(s.VmExe)},
+		{name: "VmLib", want: 6816 * 1024, have: int(s.VmLib)},
+		{name: "VmPTE", want: 128 * 1024, have: int(s.VmPTE)},
+		{name: "VmPMD", want: 12 * 1024, have: int(s.VmPMD)},
+		{name: "VmSwap", want: 660 * 1024, have: int(s.VmSwap)},
+		{name: "HugetlbPages", want: 0 * 1024, have: int(s.HugetlbPages)},
+		{name: "VoluntaryCtxtSwitches", want: 4742839, have: int(s.VoluntaryCtxtSwitches)},
+		{name: "NonVoluntaryCtxtSwitches", want: 1727500, have: int(s.NonVoluntaryCtxtSwitches)},
+		{name: "TotalCtxtSwitches", want: 4742839 + 1727500, have: int(s.TotalCtxtSwitches())},
+	} {
+		if test.want != test.have {
+			t.Errorf("want %s %d, have %d", test.name, test.want, test.have)
+		}
+	}
+}
+
+func TestProcStatusName(t *testing.T) {
+	p, err := getProcFixtures(t).NewProc(26231)
+	if err != nil {
+		t.Fatal(err)
+	}
+	s, err := p.NewStatus()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if want, have := "prometheus", s.Name; want != have {
+		t.Errorf("want name %s, have %s", want, have)
+	}
+}