Repository: prometheus/procfs
Pull request #199 (2019-08-03 10:44:59)

rebase Initial support for parsing /proc/net/arp #105 from @skottler
Rebase and slightly refactor abandoned PR https://github.com/prometheus/procfs/pull/105

Signed-off-by: Christoph Maser <christoph.maser+github@gmail.com>

/cc prometheus/node_exporter#540 where this was originally discussed
/cc prometheus/node_exporter#535


Patch:
diff --git a/arp.go b/arp.go
new file mode 100644
index 0000000..916c918
--- /dev/null
+++ b/arp.go
@@ -0,0 +1,85 @@
+// Copyright 2019 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"
+	"io/ioutil"
+	"net"
+	"strings"
+)
+
+// ARPEntry contains a single row of the columnar data represented in
+// /proc/net/arp.
+type ARPEntry struct {
+	// IP address
+	IPAddr net.IP
+	// MAC address
+	HWAddr net.HardwareAddr
+	// Name of the device
+	Device string
+}
+
+// GatherARPEntries retrieves all the ARP entries, parse the relevant columns,
+// and then return a slice of ARPEntry's.
+func (fs FS) GatherARPEntries() ([]ARPEntry, error) {
+	data, err := ioutil.ReadFile(fs.proc.Path("net/arp"))
+	if err != nil {
+		return nil, fmt.Errorf("error reading arp %s: %s", fs.proc.Path("net/arp"), err)
+	}
+
+	return parseARPEntries(data)
+}
+
+func parseARPEntries(data []byte) ([]ARPEntry, error) {
+	lines := strings.Split(string(data), "\n")
+	entries := make([]ARPEntry, 0)
+	var err error
+	const (
+		expectedDataWidth   = 6
+		expectedHeaderWidth = 9
+	)
+	for _, line := range lines {
+		columns := strings.Fields(line)
+		width := len(columns)
+
+		if width == expectedHeaderWidth || width == 0 {
+			continue
+		} else if width == expectedDataWidth {
+			entry, err := parseARPEntry(columns)
+			if err != nil {
+				return []ARPEntry{}, fmt.Errorf("failed to parse ARP entry: %s", err)
+			}
+			entries = append(entries, entry)
+		} else {
+			return []ARPEntry{}, fmt.Errorf("%d columns were detected, but %d were expected", width, expectedDataWidth)
+		}
+
+	}
+
+	return entries, err
+}
+
+func parseARPEntry(columns []string) (ARPEntry, error) {
+	ip := net.ParseIP(columns[0])
+	mac := net.HardwareAddr(columns[3])
+
+	entry := ARPEntry{
+		IPAddr: ip,
+		HWAddr: mac,
+		Device: columns[5],
+	}
+
+	return entry, nil
+}
diff --git a/fixtures.ttar b/fixtures.ttar
index 3d23c33..8c4ad64 100644
--- a/fixtures.ttar
+++ b/fixtures.ttar
@@ -1457,6 +1457,12 @@ Mode: 644
 Directory: fixtures/proc/net
 Mode: 755
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/net/arp
+Lines: 2
+IP address       HW type     Flags       HW address            Mask     Device
+192.168.224.1    0x1         0x2         00:50:56:c0:00:08     *        ens33
+Mode: 664
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/net/dev
 Lines: 6
 Inter-|   Receive                                                |  Transmit
diff --git a/sysfs/net_class.go b/sysfs/net_class.go
index 909f7fb..d5c0d6a 100644
--- a/sysfs/net_class.go
+++ b/sysfs/net_class.go
@@ -18,11 +18,7 @@ package sysfs
 import (
 	"fmt"
 	"io/ioutil"
-	"os"
 	"path/filepath"
-	"reflect"
-	"strconv"
-	"strings"
 
 	"github.com/prometheus/procfs/internal/util"
 )
@@ -33,32 +29,32 @@ const netclassPath = "class/net"
 // for single interface (iface).
 type NetClassIface struct {
 	Name             string // Interface name
-	AddrAssignType   *int64 `fileName:"addr_assign_type"`   // /sys/class/net/<iface>/addr_assign_type
-	AddrLen          *int64 `fileName:"addr_len"`           // /sys/class/net/<iface>/addr_len
-	Address          string `fileName:"address"`            // /sys/class/net/<iface>/address
-	Broadcast        string `fileName:"broadcast"`          // /sys/class/net/<iface>/broadcast
-	Carrier          *int64 `fileName:"carrier"`            // /sys/class/net/<iface>/carrier
-	CarrierChanges   *int64 `fileName:"carrier_changes"`    // /sys/class/net/<iface>/carrier_changes
-	CarrierUpCount   *int64 `fileName:"carrier_up_count"`   // /sys/class/net/<iface>/carrier_up_count
-	CarrierDownCount *int64 `fileName:"carrier_down_count"` // /sys/class/net/<iface>/carrier_down_count
-	DevID            *int64 `fileName:"dev_id"`             // /sys/class/net/<iface>/dev_id
-	Dormant          *int64 `fileName:"dormant"`            // /sys/class/net/<iface>/dormant
-	Duplex           string `fileName:"duplex"`             // /sys/class/net/<iface>/duplex
-	Flags            *int64 `fileName:"flags"`              // /sys/class/net/<iface>/flags
-	IfAlias          string `fileName:"ifalias"`            // /sys/class/net/<iface>/ifalias
-	IfIndex          *int64 `fileName:"ifindex"`            // /sys/class/net/<iface>/ifindex
-	IfLink           *int64 `fileName:"iflink"`             // /sys/class/net/<iface>/iflink
-	LinkMode         *int64 `fileName:"link_mode"`          // /sys/class/net/<iface>/link_mode
-	MTU              *int64 `fileName:"mtu"`                // /sys/class/net/<iface>/mtu
-	NameAssignType   *int64 `fileName:"name_assign_type"`   // /sys/class/net/<iface>/name_assign_type
-	NetDevGroup      *int64 `fileName:"netdev_group"`       // /sys/class/net/<iface>/netdev_group
-	OperState        string `fileName:"operstate"`          // /sys/class/net/<iface>/operstate
-	PhysPortID       string `fileName:"phys_port_id"`       // /sys/class/net/<iface>/phys_port_id
-	PhysPortName     string `fileName:"phys_port_name"`     // /sys/class/net/<iface>/phys_port_name
-	PhysSwitchID     string `fileName:"phys_switch_id"`     // /sys/class/net/<iface>/phys_switch_id
-	Speed            *int64 `fileName:"speed"`              // /sys/class/net/<iface>/speed
-	TxQueueLen       *int64 `fileName:"tx_queue_len"`       // /sys/class/net/<iface>/tx_queue_len
-	Type             *int64 `fileName:"type"`               // /sys/class/net/<iface>/type
+	AddrAssignType   *int64 // /sys/class/net/<iface>/addr_assign_type
+	AddrLen          *int64 // /sys/class/net/<iface>/addr_len
+	Address          string // /sys/class/net/<iface>/address
+	Broadcast        string // /sys/class/net/<iface>/broadcast
+	Carrier          *int64 // /sys/class/net/<iface>/carrier
+	CarrierChanges   *int64 // /sys/class/net/<iface>/carrier_changes
+	CarrierUpCount   *int64 // /sys/class/net/<iface>/carrier_up_count
+	CarrierDownCount *int64 // /sys/class/net/<iface>/carrier_down_count
+	DevID            *int64 // /sys/class/net/<iface>/dev_id
+	Dormant          *int64 // /sys/class/net/<iface>/dormant
+	Duplex           string // /sys/class/net/<iface>/duplex
+	Flags            *int64 // /sys/class/net/<iface>/flags
+	IfAlias          string // /sys/class/net/<iface>/ifalias
+	IfIndex          *int64 // /sys/class/net/<iface>/ifindex
+	IfLink           *int64 // /sys/class/net/<iface>/iflink
+	LinkMode         *int64 // /sys/class/net/<iface>/link_mode
+	MTU              *int64 // /sys/class/net/<iface>/mtu
+	NameAssignType   *int64 // /sys/class/net/<iface>/name_assign_type
+	NetDevGroup      *int64 // /sys/class/net/<iface>/netdev_group
+	OperState        string // /sys/class/net/<iface>/operstate
+	PhysPortID       string // /sys/class/net/<iface>/phys_port_id
+	PhysPortName     string // /sys/class/net/<iface>/phys_port_name
+	PhysSwitchID     string // /sys/class/net/<iface>/phys_switch_id
+	Speed            *int64 // /sys/class/net/<iface>/speed
+	TxQueueLen       *int64 // /sys/class/net/<iface>/tx_queue_len
+	Type             *int64 // /sys/class/net/<iface>/type
 }
 
 // NetClass is collection of info for every interface (iface) in /sys/class/net. The map keys
@@ -109,54 +105,77 @@ func (fs FS) NetClass() (NetClass, error) {
 // directory and gets their contents.
 func (nc NetClass) parseNetClassIface(devicePath string) (*NetClassIface, error) {
 	interfaceClass := NetClassIface{}
-	interfaceElem := reflect.ValueOf(&interfaceClass).Elem()
-	interfaceType := reflect.TypeOf(interfaceClass)
 
-	//start from 1 - skip the Name field
-	for i := 1; i < interfaceElem.NumField(); i++ {
-		fieldType := interfaceType.Field(i)
-		fieldValue := interfaceElem.Field(i)
+	files, err := ioutil.ReadDir(devicePath)
+	if err != nil {
+		return nil, err
+	}
 
-		if fieldType.Tag.Get("fileName") == "" {
-			panic(fmt.Errorf("field %s does not have a filename tag", fieldType.Name))
+	for _, f := range files {
+		if f.IsDir() {
+			continue
 		}
-
-		value, err := util.SysReadFile(devicePath + "/" + fieldType.Tag.Get("fileName"))
-
+		name := filepath.Join(devicePath, f.Name())
+		value, err := util.SysReadFile(name)
 		if err != nil {
-			if os.IsNotExist(err) || err.Error() == "operation not supported" || err.Error() == "invalid argument" {
-				continue
-			}
-			return nil, fmt.Errorf("could not access file %s: %s", fieldType.Tag.Get("fileName"), err)
+			return nil, fmt.Errorf("failed to read file %q: %v", name, err)
 		}
-
-		switch fieldValue.Kind() {
-		case reflect.String:
-			fieldValue.SetString(value)
-		case reflect.Ptr:
-			var int64ptr *int64
-			switch fieldValue.Type() {
-			case reflect.TypeOf(int64ptr):
-				var intValue int64
-				if strings.HasPrefix(value, "0x") {
-					intValue, err = strconv.ParseInt(value[2:], 16, 64)
-					if err != nil {
-						return nil, fmt.Errorf("expected hex value for %s, got: %s", fieldType.Name, value)
-					}
-				} else {
-					intValue, err = strconv.ParseInt(value, 10, 64)
-					if err != nil {
-						return nil, fmt.Errorf("expected Uint64 value for %s, got: %s", fieldType.Name, value)
-					}
-				}
-				fieldValue.Set(reflect.ValueOf(&intValue))
-			default:
-				return nil, fmt.Errorf("unhandled pointer type %q", fieldValue.Type())
-			}
-		default:
-			return nil, fmt.Errorf("unhandled type %q", fieldValue.Kind())
+		vp := util.NewValueParser(value)
+		switch f.Name() {
+		case "addr_assign_type":
+			interfaceClass.AddrAssignType = vp.PInt64()
+		case "addr_len":
+			interfaceClass.AddrLen = vp.PInt64()
+		case "address":
+			interfaceClass.Address = value
+		case "broadcast":
+			interfaceClass.Broadcast = value
+		case "carrier":
+			interfaceClass.Carrier = vp.PInt64()
+		case "carrier_changes":
+			interfaceClass.CarrierChanges = vp.PInt64()
+		case "carrier_up_count":
+			interfaceClass.CarrierUpCount = vp.PInt64()
+		case "carrier_down_count":
+			interfaceClass.CarrierDownCount = vp.PInt64()
+		case "dev_id":
+			interfaceClass.DevID = vp.PInt64()
+		case "dormant":
+			interfaceClass.Dormant = vp.PInt64()
+		case "duplex":
+			interfaceClass.Duplex = value
+		case "flags":
+			interfaceClass.Flags = vp.PInt64()
+		case "ifalias":
+			interfaceClass.IfAlias = value
+		case "ifindex":
+			interfaceClass.IfIndex = vp.PInt64()
+		case "iflink":
+			interfaceClass.IfLink = vp.PInt64()
+		case "link_mode":
+			interfaceClass.LinkMode = vp.PInt64()
+		case "mtu":
+			interfaceClass.MTU = vp.PInt64()
+		case "name_assign_type":
+			interfaceClass.NameAssignType = vp.PInt64()
+		case "netdev_group":
+			interfaceClass.NetDevGroup = vp.PInt64()
+		case "operstate":
+			interfaceClass.OperState = value
+		case "phys_port_id":
+			interfaceClass.PhysPortID = value
+		case "phys_port_name":
+			interfaceClass.PhysPortName = value
+		case "phys_switch_id":
+			interfaceClass.PhysSwitchID = value
+		case "speed":
+			interfaceClass.Speed = vp.PInt64()
+		case "tx_queue_len":
+			interfaceClass.TxQueueLen = vp.PInt64()
+		case "type":
+			interfaceClass.Type = vp.PInt64()
 		}
 	}
-
 	return &interfaceClass, nil
+
 }
diff --git a/xfs/parse.go b/xfs/parse.go
index b3d8634..9781446 100644
--- a/xfs/parse.go
+++ b/xfs/parse.go
@@ -35,23 +35,25 @@ func ParseStats(r io.Reader) (*Stats, error) {
 		fieldTrans       = "trans"
 		fieldIg          = "ig"
 		fieldLog         = "log"
+		fieldPushAil     = "push_ail"
+		fieldXstrat      = "xstrat"
 		fieldRw          = "rw"
 		fieldAttr        = "attr"
 		fieldIcluster    = "icluster"
 		fieldVnodes      = "vnodes"
 		fieldBuf         = "buf"
 		fieldXpc         = "xpc"
-
+		fieldAbtb2       = "abtb2"
+		fieldAbtc2       = "abtc2"
+		fieldBmbt2       = "bmbt2"
+		fieldIbt2        = "ibt2"
+		//fieldFibt2 = "fibt2"
+		fieldQm    = "qm"
+		fieldDebug = "debug"
 		// Unimplemented at this time due to lack of documentation.
-		// fieldPushAil = "push_ail"
-		// fieldXstrat  = "xstrat"
-		// fieldAbtb2   = "abtb2"
-		// fieldAbtc2   = "abtc2"
-		// fieldBmbt2   = "bmbt2"
-		// fieldIbt2    = "ibt2"
-		// fieldFibt2   = "fibt2"
-		// fieldQm      = "qm"
-		// fieldDebug   = "debug"
+		//fieldRmapbt = "rmapbt"
+		//fieldRefcntbt = "refcntbt"
+
 	)
 
 	var xfss Stats
@@ -115,6 +117,23 @@ func ParseStats(r io.Reader) (*Stats, error) {
 			xfss.Vnode, err = vnodeStats(us)
 		case fieldBuf:
 			xfss.Buffer, err = bufferStats(us)
+		case fieldPushAil:
+			xfss.PushAil, err = pushAilStats(us)
+		case fieldXstrat:
+			xfss.Xstrat, err = xStratStats(us)
+		case fieldAbtb2:
+			xfss.BtreeAllocBlocks2, err = btreeAllocBlocks2Stats(us)
+		case fieldAbtc2:
+			xfss.BtreeAllocContig2, err = btreeAllocContig2Stats(us)
+		case fieldBmbt2:
+			xfss.BtreeBlockMap2, err = btreeBlockMap2Stats(us)
+		case fieldIbt2:
+			xfss.BtreeInode2, err = btreeInode2Stats(us)
+		//case fieldFibt2:
+		case fieldQm:
+			xfss.QuotaManager, err = quotaManagerStats(us)
+		case fieldDebug:
+			xfss.Debug, err = debugStats(us)
 		}
 		if err != nil {
 			return nil, err
@@ -228,7 +247,39 @@ func logOperationStats(us []uint32) (LogOperationStats, error) {
 	}, nil
 }
 
-// ReadWriteStats builds a ReadWriteStats from a slice of uint32s.
+// push_ail
+func pushAilStats(us []uint32) (PushAilStats, error) {
+	if l := len(us); l != 10 {
+		return PushAilStats{}, fmt.Errorf("incorrect number of values for XFS push ail stats: %d", l)
+	}
+
+	return PushAilStats{
+		TryLogspace:   us[0],
+		SleepLogspace: us[1],
+		Pushes:        us[2],
+		Success:       us[3],
+		PushBuf:       us[4],
+		Pinned:        us[5],
+		Locked:        us[6],
+		Flushing:      us[7],
+		Restarts:      us[8],
+		Flush:         us[9],
+	}, nil
+}
+
+// xstrat
+func xStratStats(us []uint32) (XstratStats, error) {
+	if l := len(us); l != 2 {
+		return XstratStats{}, fmt.Errorf("incorrect number of values for XFS  xstrat stats: %d", l)
+	}
+
+	return XstratStats{
+		Quick: us[0],
+		Split: us[1],
+	}, nil
+}
+
+// rw
 func readWriteStats(us []uint32) (ReadWriteStats, error) {
 	if l := len(us); l != 2 {
 		return ReadWriteStats{}, fmt.Errorf("incorrect number of values for XFS read write stats: %d", l)
@@ -328,3 +379,130 @@ func extendedPrecisionStats(us []uint64) (ExtendedPrecisionStats, error) {
 		ReadBytes:  us[2],
 	}, nil
 }
+
+func quotaManagerStats(us []uint32) (QuotaManagerStats, error) {
+	if l := len(us); l != 8 {
+		return QuotaManagerStats{}, fmt.Errorf("incorrect number of values for XFS quota stats: %d", l)
+	}
+
+	return QuotaManagerStats{
+		Reclaims:      us[0],
+		ReclaimMisses: us[1],
+		DquoteDups:    us[2],
+		CacheMisses:   us[3],
+		CacheHits:     us[4],
+		Wants:         us[5],
+		ShakeReclaims: us[6],
+		InactReclaims: us[7],
+	}, nil
+}
+
+func debugStats(us []uint32) (DebugStats, error) {
+	if l := len(us); l != 1 {
+		return DebugStats{}, fmt.Errorf("incorrect number of values for XFS debug stats: %d", l)
+	}
+
+	return DebugStats{
+		Enabled: us[0],
+	}, nil
+}
+
+// abtb2
+func btreeAllocBlocks2Stats(us []uint32) (BtreeAllocBlocks2Stats, error) {
+	if l := len(us); l != 15 {
+		return BtreeAllocBlocks2Stats{}, fmt.Errorf("incorrect number of values for abtb2 stats: %d", 1)
+	}
+
+	return BtreeAllocBlocks2Stats{
+		Lookup:    us[0],
+		Compare:   us[1],
+		Insrec:    us[2],
+		Delrec:    us[3],
+		NewRoot:   us[4],
+		KillRoot:  us[5],
+		Increment: us[6],
+		Decrement: us[7],
+		Lshift:    us[8],
+		Rshift:    us[9],
+		Split:     us[10],
+		Join:      us[11],
+		Alloc:     us[12],
+		Free:      us[13],
+		Moves:     us[14],
+	}, nil
+}
+
+// abtc2
+func btreeAllocContig2Stats(us []uint32) (BtreeAllocContig2Stats, error) {
+	if l := len(us); l != 15 {
+		return BtreeAllocContig2Stats{}, fmt.Errorf("incorrect number of values for abtc2 stats: %d", 1)
+	}
+
+	return BtreeAllocContig2Stats{
+		Lookup:    us[0],
+		Compare:   us[1],
+		Insrec:    us[2],
+		Delrec:    us[3],
+		NewRoot:   us[4],
+		KillRoot:  us[5],
+		Increment: us[6],
+		Decrement: us[7],
+		Lshift:    us[8],
+		Rshift:    us[9],
+		Split:     us[10],
+		Join:      us[11],
+		Alloc:     us[12],
+		Free:      us[13],
+		Moves:     us[14],
+	}, nil
+}
+
+// bmbt2
+func btreeBlockMap2Stats(us []uint32) (BtreeBlockMap2Stats, error) {
+	if l := len(us); l != 15 {
+		return BtreeBlockMap2Stats{}, fmt.Errorf("incorrect number of values for bmbt2 stats: %d", 1)
+	}
+
+	return BtreeBlockMap2Stats{
+		Lookup:    us[0],
+		Compare:   us[1],
+		Insrec:    us[2],
+		Delrec:    us[3],
+		NewRoot:   us[4],
+		KillRoot:  us[5],
+		Increment: us[6],
+		Decrement: us[7],
+		Lshift:    us[8],
+		Rshift:    us[9],
+		Split:     us[10],
+		Join:      us[11],
+		Alloc:     us[12],
+		Free:      us[13],
+		Moves:     us[14],
+	}, nil
+}
+
+// ibt2
+func btreeInode2Stats(us []uint32) (BtreeInode2Stats, error) {
+	if l := len(us); l != 15 {
+		return BtreeInode2Stats{}, fmt.Errorf("incorrect number of values for ibt2 stats: %d", 1)
+	}
+
+	return BtreeInode2Stats{
+		Lookup:    us[0],
+		Compare:   us[1],
+		Insrec:    us[2],
+		Delrec:    us[3],
+		NewRoot:   us[4],
+		KillRoot:  us[5],
+		Increment: us[6],
+		Decrement: us[7],
+		Lshift:    us[8],
+		Rshift:    us[9],
+		Split:     us[10],
+		Join:      us[11],
+		Alloc:     us[12],
+		Free:      us[13],
+		Moves:     us[14],
+	}, nil
+}
diff --git a/xfs/xfs.go b/xfs/xfs.go
index 3aad661..018a0b8 100644
--- a/xfs/xfs.go
+++ b/xfs/xfs.go
@@ -49,6 +49,14 @@ type Stats struct {
 	Vnode              VnodeStats
 	Buffer             BufferStats
 	ExtendedPrecision  ExtendedPrecisionStats
+	Xstrat             XstratStats            // xstrat
+	PushAil            PushAilStats           // push_ail
+	Debug              DebugStats             // debug
+	QuotaManager       QuotaManagerStats      // qm
+	BtreeAllocBlocks2  BtreeAllocBlocks2Stats // abtb2
+	BtreeAllocContig2  BtreeAllocContig2Stats // abtc2
+	BtreeBlockMap2     BtreeBlockMap2Stats    // bmbt2
+	BtreeInode2        BtreeInode2Stats       // ibt2
 }
 
 // ExtentAllocationStats contains statistics regarding XFS extent allocations.
@@ -170,6 +178,119 @@ type ExtendedPrecisionStats struct {
 	ReadBytes  uint64
 }
 
+// PushAilStats contains statistics on tail-pushing operations.
+type PushAilStats struct {
+	TryLogspace   uint32
+	SleepLogspace uint32
+	Pushes        uint32
+	Success       uint32
+	PushBuf       uint32
+	Pinned        uint32
+	Locked        uint32
+	Flushing      uint32
+	Restarts      uint32
+	Flush         uint32
+}
+
+// QuotaManagerStats contain statistics regarding quota processing.
+type QuotaManagerStats struct {
+	Reclaims      uint32
+	ReclaimMisses uint32
+	DquoteDups    uint32
+	CacheMisses   uint32
+	CacheHits     uint32
+	Wants         uint32
+	ShakeReclaims uint32
+	InactReclaims uint32
+}
+
+// XstratStats contains statistics regarding bytes processed by the XFS daemon.
+type XstratStats struct {
+	Quick uint32
+	Split uint32
+}
+
+// DebugStats indicate if XFS debugging is enabled.
+type DebugStats struct {
+	Enabled uint32
+}
+
+// BtreeAllocBlocks2Stats contains statistics on B-Tree v2 allocations.
+type BtreeAllocBlocks2Stats struct {
+	Lookup    uint32
+	Compare   uint32
+	Insrec    uint32
+	Delrec    uint32
+	NewRoot   uint32
+	KillRoot  uint32
+	Increment uint32
+	Decrement uint32
+	Lshift    uint32
+	Rshift    uint32
+	Split     uint32
+	Join      uint32
+	Alloc     uint32
+	Free      uint32
+	Moves     uint32
+}
+
+// BtreeAllocContig2Stats contain statistics on B-tree v2 free-space-by-size record operations.
+type BtreeAllocContig2Stats struct {
+	Lookup    uint32
+	Compare   uint32
+	Insrec    uint32
+	Delrec    uint32
+	NewRoot   uint32
+	KillRoot  uint32
+	Increment uint32
+	Decrement uint32
+	Lshift    uint32
+	Rshift    uint32
+	Split     uint32
+	Join      uint32
+	Alloc     uint32
+	Free      uint32
+	Moves     uint32
+}
+
+// BtreeBlockMap2Stats contain statistics on B-tree v2 block map operations.
+type BtreeBlockMap2Stats struct {
+	Lookup    uint32
+	Compare   uint32
+	Insrec    uint32
+	Delrec    uint32
+	NewRoot   uint32
+	KillRoot  uint32
+	Increment uint32
+	Decrement uint32
+	Lshift    uint32
+	Rshift    uint32
+	Split     uint32
+	Join      uint32
+	Alloc     uint32
+	Free      uint32
+	Moves     uint32
+}
+
+// BtreeInode2Stats contain statistics on B-tree v2 inode allocations.
+type BtreeInode2Stats struct {
+	Lookup    uint32
+	Compare   uint32
+	Insrec    uint32
+	Delrec    uint32
+	NewRoot   uint32
+	KillRoot  uint32
+	Increment uint32
+	Decrement uint32
+	Lshift    uint32
+	Rshift    uint32
+	Split     uint32
+	Join      uint32
+	Alloc     uint32
+	Free      uint32
+	Moves     uint32
+}
+
 // FS represents the pseudo-filesystems proc and sys, which provides an interface to
 // kernel data structures.
 type FS struct {


Test patch:
diff --git a/arp_test.go b/arp_test.go
new file mode 100644
index 0000000..272567b
--- /dev/null
+++ b/arp_test.go
@@ -0,0 +1,43 @@
+// Copyright 2019 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 (
+	"net"
+	"testing"
+)
+
+func TestARP(t *testing.T) {
+	fs, err := NewFS(procTestFixtures)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	arpFile, err := fs.GatherARPEntries()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if want, got := "192.168.224.1", arpFile[0].IPAddr.String(); want != got {
+		t.Errorf("want 192.168.224.1, got %s", got)
+	}
+
+	if want, got := net.HardwareAddr("00:50:56:c0:00:08").String(), arpFile[0].HWAddr.String(); want != got {
+		t.Errorf("want 00:50:56:c0:00:08, got %s", got)
+	}
+
+	if want, got := "ens33", arpFile[0].Device; want != got {
+		t.Errorf("want ens33, got %s", got)
+	}
+}
diff --git a/xfs/parse_test.go b/xfs/parse_test.go
index fcbe9ef..1774665 100644
--- a/xfs/parse_test.go
+++ b/xfs/parse_test.go
@@ -311,6 +311,191 @@ func TestParseStats(t *testing.T) {
 				},
 			},
 		},
+		{
+			name:    "xstrat bad",
+			s:       "xstrat 1",
+			invalid: true,
+		},
+		{
+			name: "xstrat OK",
+			s:    "xstrat 1 2",
+			stats: &xfs.Stats{
+				Xstrat: xfs.XstratStats{
+					Quick: 1,
+					Split: 2,
+				},
+			},
+		},
+		{
+			name:    "push_ail bad",
+			s:       "push_ail 1 2 3 4 5",
+			invalid: true,
+		},
+		{
+			name: "push_ail OK",
+			s:    "push_ail 1 2 3 4 5 6 7 8 9 10",
+			stats: &xfs.Stats{
+				PushAil: xfs.PushAilStats{
+					TryLogspace:   1,
+					SleepLogspace: 2,
+					Pushes:        3,
+					Success:       4,
+					PushBuf:       5,
+					Pinned:        6,
+					Locked:        7,
+					Flushing:      8,
+					Restarts:      9,
+					Flush:         10,
+				},
+			},
+		},
+		{
+			name:    "debug bad",
+			s:       "debug 1 2",
+			invalid: true,
+		},
+		{
+			name: "debug  OK",
+			s:    "debug 1",
+			stats: &xfs.Stats{
+				Debug: xfs.DebugStats{
+					Enabled: 1,
+				},
+			},
+		},
+		{
+			name:    "qm bad",
+			s:       "qm 1 2 3 4 5 6 7",
+			invalid: true,
+		},
+		{
+			name: "qm OK",
+			s:    "qm 1 2 3 4 5 6 7 8",
+			stats: &xfs.Stats{
+				QuotaManager: xfs.QuotaManagerStats{
+					Reclaims:      1,
+					ReclaimMisses: 2,
+					DquoteDups:    3,
+					CacheMisses:   4,
+					CacheHits:     5,
+					Wants:         6,
+					ShakeReclaims: 7,
+					InactReclaims: 8,
+				},
+			},
+		},
+		{
+			name:    "abtb2 bad",
+			s:       "abtb2 1 2 3 4 5 6",
+			invalid: true,
+		},
+		{
+			name: "abtb2 OK",
+			s:    "abtb2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15",
+			stats: &xfs.Stats{
+				BtreeAllocBlocks2: xfs.BtreeAllocBlocks2Stats{
+					Lookup:    1,
+					Compare:   2,
+					Insrec:    3,
+					Delrec:    4,
+					NewRoot:   5,
+					KillRoot:  6,
+					Increment: 7,
+					Decrement: 8,
+					Lshift:    9,
+					Rshift:    10,
+					Split:     11,
+					Join:      12,
+					Alloc:     13,
+					Free:      14,
+					Moves:     15,
+				},
+			},
+		},
+		{
+			name:    "abtc2 bad",
+			s:       "abtc2 1 2 3 4 5 6",
+			invalid: true,
+		},
+		{
+			name: "abtc2 OK",
+			s:    "abtc2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15",
+			stats: &xfs.Stats{
+				BtreeAllocContig2: xfs.BtreeAllocContig2Stats{
+					Lookup:    1,
+					Compare:   2,
+					Insrec:    3,
+					Delrec:    4,
+					NewRoot:   5,
+					KillRoot:  6,
+					Increment: 7,
+					Decrement: 8,
+					Lshift:    9,
+					Rshift:    10,
+					Split:     11,
+					Join:      12,
+					Alloc:     13,
+					Free:      14,
+					Moves:     15,
+				},
+			},
+		},
+		{
+			name:    "bmbt2 bad",
+			s:       "bmbt2 1 2 3 4 5 6",
+			invalid: true,
+		},
+		{
+			name: "bmbt2 OK",
+			s:    "bmbt2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15",
+			stats: &xfs.Stats{
+				BtreeBlockMap2: xfs.BtreeBlockMap2Stats{
+					Lookup:    1,
+					Compare:   2,
+					Insrec:    3,
+					Delrec:    4,
+					NewRoot:   5,
+					KillRoot:  6,
+					Increment: 7,
+					Decrement: 8,
+					Lshift:    9,
+					Rshift:    10,
+					Split:     11,
+					Join:      12,
+					Alloc:     13,
+					Free:      14,
+					Moves:     15,
+				},
+			},
+		},
+		{
+			name:    "ibt2 bad",
+			s:       "ibt2 1 2 3 4 5 6",
+			invalid: true,
+		},
+		{
+			name: "ibt2 OK",
+			s:    "ibt2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15",
+			stats: &xfs.Stats{
+				BtreeInode2: xfs.BtreeInode2Stats{
+					Lookup:    1,
+					Compare:   2,
+					Insrec:    3,
+					Delrec:    4,
+					NewRoot:   5,
+					KillRoot:  6,
+					Increment: 7,
+					Decrement: 8,
+					Lshift:    9,
+					Rshift:    10,
+					Split:     11,
+					Join:      12,
+					Alloc:     13,
+					Free:      14,
+					Moves:     15,
+				},
+			},
+		},
 		{
 			name: "fixtures OK",
 			fs:   true,
@@ -410,6 +595,106 @@ func TestParseStats(t *testing.T) {
 					WriteBytes: 92823103,
 					ReadBytes:  86219234,
 				},
+				PushAil: xfs.PushAilStats{
+					TryLogspace:   945014,
+					SleepLogspace: 0,
+					Pushes:        134260,
+					Success:       15483,
+					PushBuf:       0,
+					Pinned:        3940,
+					Locked:        464,
+					Flushing:      159985,
+					Restarts:      0,
+					Flush:         40,
+				},
+				Xstrat: xfs.XstratStats{
+					Quick: 92447,
+					Split: 0,
+				},
+				Debug: xfs.DebugStats{
+					Enabled: 0,
+				},
+				QuotaManager: xfs.QuotaManagerStats{
+					Reclaims:      0,
+					ReclaimMisses: 0,
+					DquoteDups:    0,
+					CacheMisses:   0,
+					CacheHits:     0,
+					Wants:         0,
+					ShakeReclaims: 0,
+					InactReclaims: 0,
+				},
+				BtreeAllocBlocks2: xfs.BtreeAllocBlocks2Stats{
+					Lookup:    184941,
+					Compare:   1277345,
+					Insrec:    13257,
+					Delrec:    13278,
+					NewRoot:   0,
+					KillRoot:  0,
+					Increment: 0,
+					Decrement: 0,
+					Lshift:    0,
+					Rshift:    0,
+					Split:     0,
+					Join:      0,
+					Alloc:     0,
+					Free:      0,
+					Moves:     2746147,
+				},
+
+				BtreeAllocContig2: xfs.BtreeAllocContig2Stats{
+					Lookup:    345295,
+					Compare:   2416764,
+					Insrec:    172637,
+					Delrec:    172658,
+					NewRoot:   0,
+					KillRoot:  0,
+					Increment: 0,
+					Decrement: 0,
+					Lshift:    0,
+					Rshift:    0,
+					Split:     0,
+					Join:      0,
+					Alloc:     0,
+					Free:      0,
+					Moves:     21406023,
+				},
+
+				BtreeBlockMap2: xfs.BtreeBlockMap2Stats{
+					Lookup:    0,
+					Compare:   0,
+					Insrec:    0,
+					Delrec:    0,
+					NewRoot:   0,
+					KillRoot:  0,
+					Increment: 0,
+					Decrement: 0,
+					Lshift:    0,
+					Rshift:    0,
+					Split:     0,
+					Join:      0,
+					Alloc:     0,
+					Free:      0,
+					Moves:     0,
+				},
+
+				BtreeInode2: xfs.BtreeInode2Stats{
+					Lookup:    343004,
+					Compare:   1358467,
+					Insrec:    0,
+					Delrec:    0,
+					NewRoot:   0,
+					KillRoot:  0,
+					Increment: 0,
+					Decrement: 0,
+					Lshift:    0,
+					Rshift:    0,
+					Split:     0,
+					Join:      0,
+					Alloc:     0,
+					Free:      0,
+					Moves:     0,
+				},
 			},
 		},
 	}