Repository: elastic/go-sysinfo
Pull request #59 (2019-08-08 14:12:38)

Add vmstat interface
See elastic/beats#13175 This took me longer than expected, as I needed to figure out how this library actually works, and it's a tad different from how gosigar works. This should hopefully be fine; I've attempted to run it against the test suite and the unit tests works, but I still need to put together some "IRL" tests on a normal linux host. Also not entirely sure the test suite is working.

We also need godocs for a bunch of the types, but that's a separate PR.

fearful-symmetry: Okay, down to the actual code issues now. Fixing.
fearful-symmetry: Okay, lets try that.
fearful-symmetry: Alright, this moves it to an `init()`. We already have a lot of init functions in that library, but considering the performance implications of how often the `VMStat` thing will be called, it might be worth it.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1727d2..b74c764 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
+- Add `VMStat` interface for Linux. [#59](https://github.com/elastic/go-sysinfo/pull/59)
+
 ### Changed
 
 ### Deprecated
diff --git a/README.md b/README.md
index 3a657a4..958f11c 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,7 @@ that are implemented.
 | `Info()`         | x      | x     | x       |
 | `Memory()`       | x      | x     | x       |
 | `CPUTimer`       | x      | x     | x       |
+| `VMStat`         |        | x     |         |
 
 | `Process` Features     | Darwin | Linux | Windows |
 |------------------------|--------|-------|---------|
diff --git a/providers/linux/host_linux.go b/providers/linux/host_linux.go
index 9b09ddf..19325b0 100644
--- a/providers/linux/host_linux.go
+++ b/providers/linux/host_linux.go
@@ -71,6 +71,16 @@ func (h *host) Memory() (*types.HostMemoryInfo, error) {
 	return parseMemInfo(content)
 }
 
+// VMStat reports data from /proc/vmstat on linux.
+func (h *host) VMStat() (*types.VMStatInfo, error) {
+	content, err := ioutil.ReadFile(h.procFS.path("vmstat"))
+	if err != nil {
+		return nil, err
+	}
+
+	return parseVMStat(content)
+}
+
 func (h *host) CPUTime() (types.CPUTimes, error) {
 	stat, err := h.procFS.NewStat()
 	if err != nil {
diff --git a/providers/linux/memory_linux.go b/providers/linux/memory_linux.go
index 3035743..758caab 100644
--- a/providers/linux/memory_linux.go
+++ b/providers/linux/memory_linux.go
@@ -18,9 +18,6 @@
 package linux
 
 import (
-	"bytes"
-	"strconv"
-
 	"github.com/pkg/errors"
 
 	"github.com/elastic/go-sysinfo/types"
@@ -73,28 +70,3 @@ func parseMemInfo(content []byte) (*types.HostMemoryInfo, error) {
 
 	return memInfo, nil
 }
-
-func parseBytesOrNumber(data []byte) (uint64, error) {
-	parts := bytes.Fields(data)
-
-	if len(parts) == 0 {
-		return 0, errors.New("empty value")
-	}
-
-	num, err := strconv.ParseUint(string(parts[0]), 10, 64)
-	if err != nil {
-		return 0, errors.Wrap(err, "failed to parse value")
-	}
-
-	var multiplier uint64 = 1
-	if len(parts) >= 2 {
-		switch string(parts[1]) {
-		case "kB":
-			multiplier = 1024
-		default:
-			return 0, errors.Errorf("unhandled unit %v", string(parts[1]))
-		}
-	}
-
-	return num * multiplier, nil
-}
diff --git a/providers/linux/util.go b/providers/linux/util.go
index 066fef6..0be3f6b 100644
--- a/providers/linux/util.go
+++ b/providers/linux/util.go
@@ -84,3 +84,28 @@ func decodeBitMap(s string, lookupName func(int) string) ([]string, error) {
 
 	return names, nil
 }
+
+func parseBytesOrNumber(data []byte) (uint64, error) {
+	parts := bytes.Fields(data)
+
+	if len(parts) == 0 {
+		return 0, errors.New("empty value")
+	}
+
+	num, err := strconv.ParseUint(string(parts[0]), 10, 64)
+	if err != nil {
+		return 0, errors.Wrap(err, "failed to parse value")
+	}
+
+	var multiplier uint64 = 1
+	if len(parts) >= 2 {
+		switch string(parts[1]) {
+		case "kB":
+			multiplier = 1024
+		default:
+			return 0, errors.Errorf("unhandled unit %v", string(parts[1]))
+		}
+	}
+
+	return num * multiplier, nil
+}
diff --git a/providers/linux/vmstat.go b/providers/linux/vmstat.go
new file mode 100644
index 0000000..0a22867
--- /dev/null
+++ b/providers/linux/vmstat.go
@@ -0,0 +1,70 @@
+// Licensed to Elasticsearch B.V. under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Elasticsearch B.V. licenses this file to you 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 linux
+
+import (
+	"reflect"
+
+	"github.com/pkg/errors"
+
+	"github.com/elastic/go-sysinfo/types"
+)
+
+// vmstatTagToFieldIndex contains a mapping of json struct tags to struct field indices.
+var vmstatTagToFieldIndex = make(map[string]int)
+
+func init() {
+	var vmstat types.VMStatInfo
+	val := reflect.ValueOf(vmstat)
+	typ := reflect.TypeOf(vmstat)
+
+	for i := 0; i < val.NumField(); i++ {
+		field := typ.Field(i)
+		if tag := field.Tag.Get("json"); tag != "" {
+			vmstatTagToFieldIndex[tag] = i
+		}
+	}
+}
+
+// parseVMStat parses the contents of /proc/vmstat.
+func parseVMStat(content []byte) (*types.VMStatInfo, error) {
+	var vmStat types.VMStatInfo
+	refValues := reflect.ValueOf(&vmStat).Elem()
+
+	err := parseKeyValue(content, " ", func(key, value []byte) error {
+		// turn our []byte value into an int
+		val, err := parseBytesOrNumber(value)
+		if err != nil {
+			return errors.Wrapf(err, "failed to parse %v value of %v", string(key), string(value))
+		}
+
+		idx, ok := vmstatTagToFieldIndex[string(key)]
+		if !ok {
+			return nil
+		}
+
+		sval := refValues.Field(idx)
+
+		if sval.CanSet() {
+			sval.SetUint(val)
+		}
+		return nil
+	})
+
+	return &vmStat, err
+}
diff --git a/types/host.go b/types/host.go
index 198dac1..bf5b80c 100644
--- a/types/host.go
+++ b/types/host.go
@@ -26,7 +26,12 @@ type Host interface {
 	Memory() (*HostMemoryInfo, error)
 }
 
-// HostInfo contains basic host information
+// VMStat is the interface wrapper for platforms that support /proc/vmstat.
+type VMStat interface {
+	VMStat() (*VMStatInfo, error)
+}
+
+// HostInfo contains basic host information.
 type HostInfo struct {
 	Architecture      string    `json:"architecture"`            // Hardware architecture (e.g. x86_64, arm, ppc, mips).
 	BootTime          time.Time `json:"boot_time"`               // Host boot time.
@@ -83,3 +88,181 @@ type HostMemoryInfo struct {
 	VirtualFree  uint64            `json:"virtual_free_bytes"`  // Virtual memory that is not used.
 	Metrics      map[string]uint64 `json:"raw,omitempty"`       // Other memory related metrics.
 }
+
+// VMStatInfo contains parsed info from /proc/vmstat.
+// This procfs file has expanded much over the years
+// with different kernel versions. If we don't have a field in vmstat,
+// the field in the struct will just be blank. The comments represent kernel versions.
+type VMStatInfo struct {
+	NrFreePages                uint64 `json:"nr_free_pages"`                 // (since Linux 2.6.31)
+	NrAllocBatch               uint64 `json:"nr_alloc_batch"`                // (since Linux 3.12)
+	NrInactiveAnon             uint64 `json:"nr_inactive_anon"`              // (since Linux 2.6.28)
+	NrActiveAnon               uint64 `json:"nr_active_anon"`                // (since Linux 2.6.28)
+	NrInactiveFile             uint64 `json:"nr_inactive_file"`              // (since Linux 2.6.28)
+	NrActiveFile               uint64 `json:"nr_active_file"`                // (since Linux 2.6.28)
+	NrUnevictable              uint64 `json:"nr_unevictable"`                // (since Linux 2.6.28)
+	NrMlock                    uint64 `json:"nr_mlock"`                      // (since Linux 2.6.28)
+	NrAnonPages                uint64 `json:"nr_anon_pages"`                 // (since Linux 2.6.18)
+	NrMapped                   uint64 `json:"nr_mapped"`                     // (since Linux 2.6.0)
+	NrFilePages                uint64 `json:"nr_file_pages"`                 // (since Linux 2.6.18)
+	NrDirty                    uint64 `json:"nr_dirty"`                      // (since Linux 2.6.0)
+	NrWriteback                uint64 `json:"nr_writeback"`                  // (since Linux 2.6.0)
+	NrSlabReclaimable          uint64 `json:"nr_slab_reclaimable"`           // (since Linux 2.6.19)
+	NrSlabUnreclaimable        uint64 `json:"nr_slab_unreclaimable"`         // (since Linux 2.6.19)
+	NrPageTablePages           uint64 `json:"nr_page_table_pages"`           // (since Linux 2.6.0)
+	NrKernelStack              uint64 `json:"nr_kernel_stack"`               // (since Linux 2.6.32)  Amount of memory allocated to kernel stacks.
+	NrUnstable                 uint64 `json:"nr_unstable"`                   // (since Linux 2.6.0)
+	NrBounce                   uint64 `json:"nr_bounce"`                     // (since Linux 2.6.12)
+	NrVmscanWrite              uint64 `json:"nr_vmscan_write"`               // (since Linux 2.6.19)
+	NrVmscanImmediateReclaim   uint64 `json:"nr_vmscan_immediate_reclaim"`   // (since Linux 3.2)
+	NrWritebackTemp            uint64 `json:"nr_writeback_temp"`             // (since Linux 2.6.26)
+	NrIsolatedAnon             uint64 `json:"nr_isolated_anon"`              // (since Linux 2.6.32)
+	NrIsolatedFile             uint64 `json:"nr_isolated_file"`              // (since Linux 2.6.32)
+	NrShmem                    uint64 `json:"nr_shmem"`                      // (since Linux 2.6.32) Pages used by shmem and tmpfs(5).
+	NrDirtied                  uint64 `json:"nr_dirtied"`                    // (since Linux 2.6.37)
+	NrWritten                  uint64 `json:"nr_written"`                    // (since Linux 2.6.37)
+	NrPagesScanned             uint64 `json:"nr_pages_scanned"`              // (since Linux 3.17)
+	NumaHit                    uint64 `json:"numa_hit"`                      // (since Linux 2.6.18)
+	NumaMiss                   uint64 `json:"numa_miss"`                     // (since Linux 2.6.18)
+	NumaForeign                uint64 `json:"numa_foreign"`                  // (since Linux 2.6.18)
+	NumaInterleave             uint64 `json:"numa_interleave"`               // (since Linux 2.6.18)
+	NumaLocal                  uint64 `json:"numa_local"`                    // (since Linux 2.6.18)
+	NumaOther                  uint64 `json:"numa_other"`                    // (since Linux 2.6.18)
+	WorkingsetRefault          uint64 `json:"workingset_refault"`            // (since Linux 3.15)
+	WorkingsetActivate         uint64 `json:"workingset_activate"`           // (since Linux 3.15)
+	WorkingsetNodereclaim      uint64 `json:"workingset_nodereclaim"`        // (since Linux 3.15)
+	NrAnonTransparentHugepages uint64 `json:"nr_anon_transparent_hugepages"` // (since Linux 2.6.38)
+	NrFreeCma                  uint64 `json:"nr_free_cma"`                   // (since Linux 3.7)  Number of free CMA (Contiguous Memory Allocator) pages.
+	NrDirtyThreshold           uint64 `json:"nr_dirty_threshold"`            // (since Linux 2.6.37)
+	NrDirtyBackgroundThreshold uint64 `json:"nr_dirty_background_threshold"` // (since Linux 2.6.37)
+	Pgpgin                     uint64 `json:"pgpgin"`                        // (since Linux 2.6.0)
+	Pgpgout                    uint64 `json:"pgpgout"`                       // (since Linux 2.6.0)
+	Pswpin                     uint64 `json:"pswpin"`                        // (since Linux 2.6.0)
+	Pswpout                    uint64 `json:"pswpout"`                       // (since Linux 2.6.0)
+	PgallocDma                 uint64 `json:"pgalloc_dma"`                   // (since Linux 2.6.5)
+	PgallocDma32               uint64 `json:"pgalloc_dma32"`                 // (since Linux 2.6.16)
+	PgallocNormal              uint64 `json:"pgalloc_normal"`                // (since Linux 2.6.5)
+	PgallocHigh                uint64 `json:"pgalloc_high"`                  // (since Linux 2.6.5)
+	PgallocMovable             uint64 `json:"pgalloc_movable"`               // (since Linux 2.6.23)
+	Pgfree                     uint64 `json:"pgfree"`                        // (since Linux 2.6.0)
+	Pgactivate                 uint64 `json:"pgactivate"`                    // (since Linux 2.6.0)
+	Pgdeactivate               uint64 `json:"pgdeactivate"`                  // (since Linux 2.6.0)
+	Pgfault                    uint64 `json:"pgfault"`                       // (since Linux 2.6.0)
+	Pgmajfault                 uint64 `json:"pgmajfault"`                    // (since Linux 2.6.0)
+	PgrefillDma                uint64 `json:"pgrefill_dma"`                  // (since Linux 2.6.5)
+	PgrefillDma32              uint64 `json:"pgrefill_dma32"`                // (since Linux 2.6.16)
+	PgrefillNormal             uint64 `json:"pgrefill_normal"`               // (since Linux 2.6.5)
+	PgrefillHigh               uint64 `json:"pgrefill_high"`                 // (since Linux 2.6.5)
+	PgrefillMovable            uint64 `json:"pgrefill_movable"`              // (since Linux 2.6.23)
+	PgstealKswapdDma           uint64 `json:"pgsteal_kswapd_dma"`            // (since Linux 3.4)
+	PgstealKswapdDma32         uint64 `json:"pgsteal_kswapd_dma32"`          // (since Linux 3.4)
+	PgstealKswapdNormal        uint64 `json:"pgsteal_kswapd_normal"`         // (since Linux 3.4)
+	PgstealKswapdHigh          uint64 `json:"pgsteal_kswapd_high"`           // (since Linux 3.4)
+	PgstealKswapdMovable       uint64 `json:"pgsteal_kswapd_movable"`        // (since Linux 3.4)
+	PgstealDirectDma           uint64 `json:"pgsteal_direct_dma"`
+	PgstealDirectDma32         uint64 `json:"pgsteal_direct_dma32"`   // (since Linux 3.4)
+	PgstealDirectNormal        uint64 `json:"pgsteal_direct_normal"`  // (since Linux 3.4)
+	PgstealDirectHigh          uint64 `json:"pgsteal_direct_high"`    // (since Linux 3.4)
+	PgstealDirectMovable       uint64 `json:"pgsteal_direct_movable"` // (since Linux 2.6.23)
+	PgscanKswapdDma            uint64 `json:"pgscan_kswapd_dma"`
+	PgscanKswapdDma32          uint64 `json:"pgscan_kswapd_dma32"`  // (since Linux 2.6.16)
+	PgscanKswapdNormal         uint64 `json:"pgscan_kswapd_normal"` // (since Linux 2.6.5)
+	PgscanKswapdHigh           uint64 `json:"pgscan_kswapd_high"`
+	PgscanKswapdMovable        uint64 `json:"pgscan_kswapd_movable"` // (since Linux 2.6.23)
+	PgscanDirectDma            uint64 `json:"pgscan_direct_dma"`     //
+	PgscanDirectDma32          uint64 `json:"pgscan_direct_dma32"`   // (since Linux 2.6.16)
+	PgscanDirectNormal         uint64 `json:"pgscan_direct_normal"`
+	PgscanDirectHigh           uint64 `json:"pgscan_direct_high"`
+	PgscanDirectMovable        uint64 `json:"pgscan_direct_movable"`         // (since Linux 2.6.23)
+	PgscanDirectThrottle       uint64 `json:"pgscan_direct_throttle"`        // (since Linux 3.6)
+	ZoneReclaimFailed          uint64 `json:"zone_reclaim_failed"`           // (since linux 2.6.31)
+	Pginodesteal               uint64 `json:"pginodesteal"`                  // (since linux 2.6.0)
+	SlabsScanned               uint64 `json:"slabs_scanned"`                 // (since linux 2.6.5)
+	KswapdInodesteal           uint64 `json:"kswapd_inodesteal"`             // (since linux 2.6.0)
+	KswapdLowWmarkHitQuickly   uint64 `json:"kswapd_low_wmark_hit_quickly"`  // (since 2.6.33)
+	KswapdHighWmarkHitQuickly  uint64 `json:"kswapd_high_wmark_hit_quickly"` // (since 2.6.33)
+	Pageoutrun                 uint64 `json:"pageoutrun"`                    // (since Linux 2.6.0)
+	Allocstall                 uint64 `json:"allocstall"`                    // (since Linux 2.6.0)
+	Pgrotated                  uint64 `json:"pgrotated"`                     // (since Linux 2.6.0)
+	DropPagecache              uint64 `json:"drop_pagecache"`                // (since Linux 3.15)
+	DropSlab                   uint64 `json:"drop_slab"`                     // (since Linux 3.15)
+	NumaPteUpdates             uint64 `json:"numa_pte_updates"`              // (since Linux 3.8)
+	NumaHugePteUpdates         uint64 `json:"numa_huge_pte_updates"`         // (since Linux 3.13)
+	NumaHintFaults             uint64 `json:"numa_hint_faults"`              // (since Linux 3.8)
+	NumaHintFaultsLocal        uint64 `json:"numa_hint_faults_local"`        // (since Linux 3.8)
+	NumaPagesMigrated          uint64 `json:"numa_pages_migrated"`           // (since Linux 3.8)
+	PgmigrateSuccess           uint64 `json:"pgmigrate_success"`             // (since Linux 3.8)
+	PgmigrateFail              uint64 `json:"pgmigrate_fail"`                // (since Linux 3.8)
+	CompactMigrateScanned      uint64 `json:"compact_migrate_scanned"`       // (since Linux 3.8)
+	CompactFreeScanned         uint64 `json:"compact_free_scanned"`          // (since Linux 3.8)
+	CompactIsolated            uint64 `json:"compact_isolated"`              // (since Linux 3.8)
+	CompactStall               uint64 `json:"compact_stall"`                 // (since Linux 2.6.35) See the kernel source file Documentation/admin-guide/mm/transhuge.rst.
+	CompactFail                uint64 `json:"compact_fail"`                  // (since Linux 2.6.35) See the kernel source file Documentation/admin-guide/mm/transhuge.rst.
+	CompactSuccess             uint64 `json:"compact_success"`               // (since Linux 2.6.35) See the kernel source file Documentation/admin-guide/mm/transhuge.rst.
+	HtlbBuddyAllocSuccess      uint64 `json:"htlb_buddy_alloc_success"`      // (since Linux 2.6.26)
+	HtlbBuddyAllocFail         uint64 `json:"htlb_buddy_alloc_fail"`         // (since Linux 2.6.26)
+	UnevictablePgsCulled       uint64 `json:"unevictable_pgs_culled"`        // (since Linux 2.6.28)
+	UnevictablePgsScanned      uint64 `json:"unevictable_pgs_scanned"`       // (since Linux 2.6.28)
+	UnevictablePgsRescued      uint64 `json:"unevictable_pgs_rescued"`       // (since Linux 2.6.28)
+	UnevictablePgsMlocked      uint64 `json:"unevictable_pgs_mlocked"`       // (since Linux 2.6.28)
+	UnevictablePgsMunlocked    uint64 `json:"unevictable_pgs_munlocked"`     // (since Linux 2.6.28)
+	UnevictablePgsCleared      uint64 `json:"unevictable_pgs_cleared"`       // (since Linux 2.6.28)
+	UnevictablePgsStranded     uint64 `json:"unevictable_pgs_stranded"`      // (since Linux 2.6.28)
+	ThpFaultAlloc              uint64 `json:"thp_fault_alloc"`               // (since Linux 2.6.39) See the kernel source file Documentation/admin-guide/mm/transhuge.rst.
+	ThpFaultFallback           uint64 `json:"thp_fault_fallback"`            // (since Linux 2.6.39) See the kernel source file Documentation/admin-guide/mm/transhuge.rst.
+	ThpCollapseAlloc           uint64 `json:"thp_collapse_alloc"`            // (since Linux 2.6.39) See the kernel source file Documentation/admin-guide/mm/transhuge.rst.
+	ThpCollapseAllocFailed     uint64 `json:"thp_collapse_alloc_failed"`     // (since Linux 2.6.39) See the kernel source file Documentation/admin-guide/mm/transhuge.rst.
+	ThpSplit                   uint64 `json:"thp_split"`                     // (since Linux 2.6.39) See the kernel source file Documentation/admin-guide/mm/transhuge.rst.
+	ThpZeroPageAlloc           uint64 `json:"thp_zero_page_alloc"`           // (since Linux 3.8) See the kernel source file Documentation/admin-guide/mm/transhuge.rst.
+	ThpZeroPageAllocFailed     uint64 `json:"thp_zero_page_alloc_failed"`    // (since Linux 3.8) See the kernel source file Documentation/admin-guide/mm/transhuge.rst.
+	BalloonInflate             uint64 `json:"balloon_inflate"`               // (since Linux 3.18)
+	BalloonDeflate             uint64 `json:"balloon_deflate"`               // (since Linux 3.18)
+	BalloonMigrate             uint64 `json:"balloon_migrate"`               // (since Linux 3.18)
+	NrTlbRemoteFlush           uint64 `json:"nr_tlb_remote_flush"`           // (since Linux 3.12)
+	NrTlbRemoteFlushReceived   uint64 `json:"nr_tlb_remote_flush_received"`  // (since Linux 3.12)
+	NrTlbLocalFlushAll         uint64 `json:"nr_tlb_local_flush_all"`        // (since Linux 3.12)
+	NrTlbLocalFlushOne         uint64 `json:"nr_tlb_local_flush_one"`        // (since Linux 3.12)
+	VmacacheFindCalls          uint64 `json:"vmacache_find_calls"`           // (since Linux 3.16)
+	VmacacheFindHits           uint64 `json:"vmacache_find_hits"`            // (since Linux 3.16)
+	VmacacheFullFlushes        uint64 `json:"vmacache_full_flushes"`         // (since Linux 3.19)
+	// the following fields are not documented in `man 5 proc` as of 4.15
+	NrZoneInactiveAnon          uint64 `json:"nr_zone_inactive_anon"`
+	NrZoneActiveAnon            uint64 `json:"nr_zone_active_anon"`
+	NrZoneInactiveFile          uint64 `json:"nr_zone_inactive_file"`
+	NrZoneActiveFile            uint64 `json:"nr_zone_active_file"`
+	NrZoneUnevictable           uint64 `json:"nr_zone_unevictable"`
+	NrZoneWritePending          uint64 `json:"nr_zone_write_pending"`
+	NrZspages                   uint64 `json:"nr_zspages"`
+	NrShmemHugepages            uint64 `json:"nr_shmem_hugepages"`
+	NrShmemPmdmapped            uint64 `json:"nr_shmem_pmdmapped"`
+	AllocstallDma               uint64 `json:"allocstall_dma"`
+	AllocstallDma32             uint64 `json:"allocstall_dma32"`
+	AllocstallNormal            uint64 `json:"allocstall_normal"`
+	AllocstallMovable           uint64 `json:"allocstall_movable"`
+	PgskipDma                   uint64 `json:"pgskip_dma"`
+	PgskipDma32                 uint64 `json:"pgskip_dma32"`
+	PgskipNormal                uint64 `json:"pgskip_normal"`
+	PgskipMovable               uint64 `json:"pgskip_movable"`
+	Pglazyfree                  uint64 `json:"pglazyfree"`
+	Pglazyfreed                 uint64 `json:"pglazyfreed"`
+	Pgrefill                    uint64 `json:"pgrefill"`
+	PgstealKswapd               uint64 `json:"pgsteal_kswapd"`
+	PgstealDirect               uint64 `json:"pgsteal_direct"`
+	PgscanKswapd                uint64 `json:"pgscan_kswapd"`
+	PgscanDirect                uint64 `json:"pgscan_direct"`
+	OomKill                     uint64 `json:"oom_kill"`
+	CompactDaemonWake           uint64 `json:"compact_daemon_wake"`
+	CompactDaemonMigrateScanned uint64 `json:"compact_daemon_migrate_scanned"`
+	CompactDaemonFreeScanned    uint64 `json:"compact_daemon_free_scanned"`
+	ThpFileAlloc                uint64 `json:"thp_file_alloc"`
+	ThpFileMapped               uint64 `json:"thp_file_mapped"`
+	ThpSplitPage                uint64 `json:"thp_split_page"`
+	ThpSplitPageFailed          uint64 `json:"thp_split_page_failed"`
+	ThpDeferredSplitPage        uint64 `json:"thp_deferred_split_page"`
+	ThpSplitPmd                 uint64 `json:"thp_split_pmd"`
+	ThpSplitPud                 uint64 `json:"thp_split_pud"`
+	ThpSwpout                   uint64 `json:"thp_swpout"`
+	ThpSwpoutFallback           uint64 `json:"thp_swpout_fallback"`
+	SwapRa                      uint64 `json:"swap_ra"`
+	SwapRaHit                   uint64 `json:"swap_ra_hit"`
+}


Test patch:
diff --git a/providers/linux/host_linux_test.go b/providers/linux/host_linux_test.go
index 98ad62c..17bb4ad 100644
--- a/providers/linux/host_linux_test.go
+++ b/providers/linux/host_linux_test.go
@@ -24,6 +24,7 @@ import (
 	"github.com/stretchr/testify/assert"
 
 	"github.com/elastic/go-sysinfo/internal/registry"
+	"github.com/elastic/go-sysinfo/types"
 )
 
 var _ registry.HostProvider = linuxSystem{}
@@ -53,3 +54,20 @@ func TestHostMemoryInfo(t *testing.T) {
 	assert.NotContains(t, m.Metrics, "MemTotal")
 	assert.Contains(t, m.Metrics, "Slab")
 }
+
+func TestHostVMStat(t *testing.T) {
+	host, err := newLinuxSystem("testdata/ubuntu1710").Host()
+	if err != nil {
+		t.Fatal(err)
+	}
+	s, err := host.(types.VMStat).VMStat()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	data, err := json.MarshalIndent(s, "", "  ")
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Log(string(data))
+}
diff --git a/providers/linux/testdata/ubuntu1710/proc/vmstat b/providers/linux/testdata/ubuntu1710/proc/vmstat
new file mode 100644
index 0000000..7ff1d85
--- /dev/null
+++ b/providers/linux/testdata/ubuntu1710/proc/vmstat
@@ -0,0 +1,133 @@
+nr_free_pages 50545
+nr_zone_inactive_anon 66
+nr_zone_active_anon 26799
+nr_zone_inactive_file 31849
+nr_zone_active_file 94164
+nr_zone_unevictable 0
+nr_zone_write_pending 7
+nr_mlock 0
+nr_page_table_pages 1225
+nr_kernel_stack 2496
+nr_bounce 0
+nr_zspages 0
+nr_free_cma 0
+numa_hit 44470329
+numa_miss 0
+numa_foreign 0
+numa_interleave 16296
+numa_local 44470329
+numa_other 0
+nr_inactive_anon 66
+nr_active_anon 26799
+nr_inactive_file 31849
+nr_active_file 94164
+nr_unevictable 0
+nr_slab_reclaimable 31763
+nr_slab_unreclaimable 10329
+nr_isolated_anon 0
+nr_isolated_file 0
+workingset_refault 302914
+workingset_activate 108959
+workingset_nodereclaim 6422
+nr_anon_pages 26218
+nr_mapped 8641
+nr_file_pages 126182
+nr_dirty 7
+nr_writeback 0
+nr_writeback_temp 0
+nr_shmem 169
+nr_shmem_hugepages 0
+nr_shmem_pmdmapped 0
+nr_anon_transparent_hugepages 0
+nr_unstable 0
+nr_vmscan_write 35
+nr_vmscan_immediate_reclaim 9832
+nr_dirtied 7188920
+nr_written 6479005
+nr_dirty_threshold 31736
+nr_dirty_background_threshold 15848
+pgpgin 17010697
+pgpgout 27734292
+pswpin 0
+pswpout 0
+pgalloc_dma 241378
+pgalloc_dma32 45788683
+pgalloc_normal 0
+pgalloc_movable 0
+allocstall_dma 0
+allocstall_dma32 0
+allocstall_normal 5
+allocstall_movable 8
+pgskip_dma 0
+pgskip_dma32 0
+pgskip_normal 0
+pgskip_movable 0
+pgfree 46085578
+pgactivate 2475069
+pgdeactivate 636658
+pglazyfree 9426
+pgfault 46777498
+pgmajfault 19204
+pglazyfreed 0
+pgrefill 707817
+pgsteal_kswapd 3798890
+pgsteal_direct 1466
+pgscan_kswapd 3868525
+pgscan_direct 1483
+pgscan_direct_throttle 0
+zone_reclaim_failed 0
+pginodesteal 1710
+slabs_scanned 8348560
+kswapd_inodesteal 3142001
+kswapd_low_wmark_hit_quickly 541
+kswapd_high_wmark_hit_quickly 332
+pageoutrun 1492
+pgrotated 29725
+drop_pagecache 0
+drop_slab 0
+oom_kill 0
+numa_pte_updates 0
+numa_huge_pte_updates 0
+numa_hint_faults 0
+numa_hint_faults_local 0
+numa_pages_migrated 0
+pgmigrate_success 4539
+pgmigrate_fail 156
+compact_migrate_scanned 9331
+compact_free_scanned 136266
+compact_isolated 9407
+compact_stall 2
+compact_fail 0
+compact_success 2
+compact_daemon_wake 21
+compact_daemon_migrate_scanned 8311
+compact_daemon_free_scanned 107086
+htlb_buddy_alloc_success 0
+htlb_buddy_alloc_fail 0
+unevictable_pgs_culled 19
+unevictable_pgs_scanned 0
+unevictable_pgs_rescued 304
+unevictable_pgs_mlocked 304
+unevictable_pgs_munlocked 304
+unevictable_pgs_cleared 0
+unevictable_pgs_stranded 0
+thp_fault_alloc 2
+thp_fault_fallback 0
+thp_collapse_alloc 2
+thp_collapse_alloc_failed 0
+thp_file_alloc 0
+thp_file_mapped 0
+thp_split_page 0
+thp_split_page_failed 0
+thp_deferred_split_page 4
+thp_split_pmd 1
+thp_split_pud 0
+thp_zero_page_alloc 0
+thp_zero_page_alloc_failed 0
+thp_swpout 0
+thp_swpout_fallback 0
+balloon_inflate 0
+balloon_deflate 0
+balloon_migrate 0
+swap_ra 0
+swap_ra_hit 0
\ No newline at end of file
diff --git a/providers/linux/vmstat_test.go b/providers/linux/vmstat_test.go
new file mode 100644
index 0000000..1e44cb9
--- /dev/null
+++ b/providers/linux/vmstat_test.go
@@ -0,0 +1,171 @@
+// Licensed to Elasticsearch B.V. under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Elasticsearch B.V. licenses this file to you 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 linux
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+var rawInput = `
+nr_free_pages 50545
+nr_zone_inactive_anon 66
+nr_zone_active_anon 26799
+nr_zone_inactive_file 31849
+nr_zone_active_file 94164
+nr_zone_unevictable 0
+nr_zone_write_pending 7
+nr_mlock 0
+nr_page_table_pages 1225
+nr_kernel_stack 2496
+nr_bounce 0
+nr_zspages 0
+nr_free_cma 0
+numa_hit 44470329
+numa_miss 0
+numa_foreign 0
+numa_interleave 16296
+numa_local 44470329
+numa_other 0
+nr_inactive_anon 66
+nr_active_anon 26799
+nr_inactive_file 31849
+nr_active_file 94164
+nr_unevictable 0
+nr_slab_reclaimable 31763
+nr_slab_unreclaimable 10329
+nr_isolated_anon 0
+nr_isolated_file 0
+workingset_refault 302914
+workingset_activate 108959
+workingset_nodereclaim 6422
+nr_anon_pages 26218
+nr_mapped 8641
+nr_file_pages 126182
+nr_dirty 7
+nr_writeback 0
+nr_writeback_temp 0
+nr_shmem 169
+nr_shmem_hugepages 0
+nr_shmem_pmdmapped 0
+nr_anon_transparent_hugepages 0
+nr_unstable 0
+nr_vmscan_write 35
+nr_vmscan_immediate_reclaim 9832
+nr_dirtied 7188920
+nr_written 6479005
+nr_dirty_threshold 31736
+nr_dirty_background_threshold 15848
+pgpgin 17010697
+pgpgout 27734292
+pswpin 0
+pswpout 0
+pgalloc_dma 241378
+pgalloc_dma32 45788683
+pgalloc_normal 0
+pgalloc_movable 0
+allocstall_dma 0
+allocstall_dma32 0
+allocstall_normal 5
+allocstall_movable 8
+pgskip_dma 0
+pgskip_dma32 0
+pgskip_normal 0
+pgskip_movable 0
+pgfree 46085578
+pgactivate 2475069
+pgdeactivate 636658
+pglazyfree 9426
+pgfault 46777498
+pgmajfault 19204
+pglazyfreed 0
+pgrefill 707817
+pgsteal_kswapd 3798890
+pgsteal_direct 1466
+pgscan_kswapd 3868525
+pgscan_direct 1483
+pgscan_direct_throttle 0
+zone_reclaim_failed 0
+pginodesteal 1710
+slabs_scanned 8348560
+kswapd_inodesteal 3142001
+kswapd_low_wmark_hit_quickly 541
+kswapd_high_wmark_hit_quickly 332
+pageoutrun 1492
+pgrotated 29725
+drop_pagecache 0
+drop_slab 0
+oom_kill 0
+numa_pte_updates 0
+numa_huge_pte_updates 0
+numa_hint_faults 0
+numa_hint_faults_local 0
+numa_pages_migrated 0
+pgmigrate_success 4539
+pgmigrate_fail 156
+compact_migrate_scanned 9331
+compact_free_scanned 136266
+compact_isolated 9407
+compact_stall 2
+compact_fail 0
+compact_success 2
+compact_daemon_wake 21
+compact_daemon_migrate_scanned 8311
+compact_daemon_free_scanned 107086
+htlb_buddy_alloc_success 0
+htlb_buddy_alloc_fail 0
+unevictable_pgs_culled 19
+unevictable_pgs_scanned 0
+unevictable_pgs_rescued 304
+unevictable_pgs_mlocked 304
+unevictable_pgs_munlocked 304
+unevictable_pgs_cleared 0
+unevictable_pgs_stranded 0
+thp_fault_alloc 2
+thp_fault_fallback 0
+thp_collapse_alloc 2
+thp_collapse_alloc_failed 0
+thp_file_alloc 0
+thp_file_mapped 0
+thp_split_page 0
+thp_split_page_failed 0
+thp_deferred_split_page 4
+thp_split_pmd 1
+thp_split_pud 0
+thp_zero_page_alloc 0
+thp_zero_page_alloc_failed 0
+thp_swpout 0
+thp_swpout_fallback 0
+balloon_inflate 0
+balloon_deflate 0
+balloon_migrate 0
+swap_ra 0
+swap_ra_hit 0
+`
+
+func TestVmStatParse(t *testing.T) {
+	data, err := parseVMStat([]byte(rawInput))
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Check a few values
+	assert.Equal(t, uint64(8348560), data.SlabsScanned)
+	assert.Equal(t, uint64(0), data.SwapRa)
+	assert.Equal(t, uint64(108959), data.WorkingsetActivate)
+}
diff --git a/system_test.go b/system_test.go
index 62b4ab2..5daabb0 100644
--- a/system_test.go
+++ b/system_test.go
@@ -290,5 +290,4 @@ func TestProcesses(t *testing.T) {
 			info.PID, info.Name, info.Exe, info.Args, info.PPID, info.CWD,
 			info.StartTime)
 	}
-
 }