stats: golang vpp_if_stats_client

see README for details

Change-Id: Ida603ccaee21dabc903512699b5b355cebb70320
Signed-off-by: Koren Lev <korenlev@gmail.com>
diff --git a/extras/vpp_if_stats/README.md b/extras/vpp_if_stats/README.md
new file mode 100755
index 0000000..7185d6c
--- /dev/null
+++ b/extras/vpp_if_stats/README.md
@@ -0,0 +1,35 @@
+# VPP interface stats client
+
+This is a source code and a binary of a 'thin client' to collect, 
+aggregate and expose VPP interface stats through VPP stats socket API. 
+It also provides some information about the installed VPP version.
+
+This can be used by monitoring systems that needs to grab those details 
+through a simple executable client with no dependencies.
+
+example use case: where VPP runs in a container that can't expose the socket API to the host level
+
+
+## Prerequisites (for building)
+
+**GoVPP** library (compatible with VPP 18.10)
+vpp, vpp-api, vpp-lib
+
+## Building
+
+```bash
+go get git.fd.io/govpp.git
+go build
+``` 
+
+## Using (post-build for example on linux 64bit)
+
+```bash
+./bin/vpp_if_stats_linux_amd64
+```
+
+## Output examples
+
+[JSON schema](./response_schema.json)
+[Example](./response_example.json)
+
diff --git a/extras/vpp_if_stats/apimock.go b/extras/vpp_if_stats/apimock.go
new file mode 100755
index 0000000..7736334
--- /dev/null
+++ b/extras/vpp_if_stats/apimock.go
@@ -0,0 +1,92 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: git.fd.io/govpp.git/api (Interfaces: Channel)
+
+// Package mock_api is a generated GoMock package.
+package main
+
+import (
+	api "git.fd.io/govpp.git/api"
+	gomock "github.com/golang/mock/gomock"
+	reflect "reflect"
+	time "time"
+)
+
+// MockChannel is a mock of Channel interface
+type MockChannel struct {
+	ctrl     *gomock.Controller
+	recorder *MockChannelMockRecorder
+}
+
+// MockChannelMockRecorder is the mock recorder for MockChannel
+type MockChannelMockRecorder struct {
+	mock *MockChannel
+}
+
+// NewMockChannel creates a new mock instance
+func NewMockChannel(ctrl *gomock.Controller) *MockChannel {
+	mock := &MockChannel{ctrl: ctrl}
+	mock.recorder = &MockChannelMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockChannel) EXPECT() *MockChannelMockRecorder {
+	return m.recorder
+}
+
+// Close mocks base method
+func (m *MockChannel) Close() {
+	m.ctrl.Call(m, "Close")
+}
+
+// Close indicates an expected call of Close
+func (mr *MockChannelMockRecorder) Close() *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockChannel)(nil).Close))
+}
+
+// SendMultiRequest mocks base method
+func (m *MockChannel) SendMultiRequest(arg0 api.Message) api.MultiRequestCtx {
+	ret := m.ctrl.Call(m, "SendMultiRequest", arg0)
+	ret0, _ := ret[0].(api.MultiRequestCtx)
+	return ret0
+}
+
+// SendMultiRequest indicates an expected call of SendMultiRequest
+func (mr *MockChannelMockRecorder) SendMultiRequest(arg0 interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMultiRequest", reflect.TypeOf((*MockChannel)(nil).SendMultiRequest), arg0)
+}
+
+// SendRequest mocks base method
+func (m *MockChannel) SendRequest(arg0 api.Message) api.RequestCtx {
+	ret := m.ctrl.Call(m, "SendRequest", arg0)
+	ret0, _ := ret[0].(api.RequestCtx)
+	return ret0
+}
+
+// SendRequest indicates an expected call of SendRequest
+func (mr *MockChannelMockRecorder) SendRequest(arg0 interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRequest", reflect.TypeOf((*MockChannel)(nil).SendRequest), arg0)
+}
+
+// SetReplyTimeout mocks base method
+func (m *MockChannel) SetReplyTimeout(arg0 time.Duration) {
+	m.ctrl.Call(m, "SetReplyTimeout", arg0)
+}
+
+// SetReplyTimeout indicates an expected call of SetReplyTimeout
+func (mr *MockChannelMockRecorder) SetReplyTimeout(arg0 interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReplyTimeout", reflect.TypeOf((*MockChannel)(nil).SetReplyTimeout), arg0)
+}
+
+// SubscribeNotification mocks base method
+func (m *MockChannel) SubscribeNotification(arg0 chan api.Message, arg1 api.Message) (api.SubscriptionCtx, error) {
+	ret := m.ctrl.Call(m, "SubscribeNotification", arg0, arg1)
+	ret0, _ := ret[0].(api.SubscriptionCtx)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SubscribeNotification indicates an expected call of SubscribeNotification
+func (mr *MockChannelMockRecorder) SubscribeNotification(arg0, arg1 interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubscribeNotification", reflect.TypeOf((*MockChannel)(nil).SubscribeNotification), arg0, arg1)
+}
diff --git a/extras/vpp_if_stats/json_structs.go b/extras/vpp_if_stats/json_structs.go
new file mode 100755
index 0000000..a42b6d8
--- /dev/null
+++ b/extras/vpp_if_stats/json_structs.go
@@ -0,0 +1,93 @@
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"git.fd.io/govpp.git/examples/bin_api/vpe"
+)
+
+type jsonVppDetails struct {
+	Program        string `json:"program"`
+	Version        string `json:"version"`
+	BuildDate      string `json:"build_date"`
+	BuildDirectory string `json:"build_directory"`
+}
+
+type jsonVppInterface struct {
+	Index      uint32 `json:"if_index"`
+	Name       string `json:"if_name"`
+	Tag        string `json:"if_tag"`
+	MacAddress string `json:"if_mac"`
+	AdminState uint8  `json:"if_admin_state"`
+	LinkState  uint8  `json:"if_link_state"`
+	LinkMTU    uint16 `json:"if_link_mtu"`
+	SubDot1ad  uint8  `json:"if_sub_dot1ad"`
+	SubID      uint32 `json:"if_sub_id"`
+
+	TxBytes   uint64 `json:"if_tx_bytes"`
+	TxPackets uint64 `json:"if_tx_packets"`
+	TxErrors  uint64 `json:"if_tx_errors"`
+	RxBytes   uint64 `json:"if_rx_bytes"`
+	RxPackets uint64 `json:"if_rx_packets"`
+	RxErrors  uint64 `json:"if_rx_errors"`
+	Drops     uint64 `json:"if_drops"`
+	Punts     uint64 `json:"if_punts"`
+}
+
+type jsonVppPayload struct {
+	*jsonVppDetails `json:"vpp_details"`
+	Interfaces      []*jsonVppInterface `json:"interfaces"`
+}
+
+func bytesToString(b []byte) string {
+	return string(bytes.Split(b, []byte{0})[0])
+}
+
+func toJSONVppDetails(svReply *vpe.ShowVersionReply) *jsonVppDetails {
+	return &jsonVppDetails{
+		Program:        bytesToString(svReply.Program),
+		Version:        bytesToString(svReply.Version),
+		BuildDate:      bytesToString(svReply.BuildDate),
+		BuildDirectory: bytesToString(svReply.BuildDirectory),
+	}
+}
+
+func toJSONVppInterface(vppIf *vppInterface) *jsonVppInterface {
+	return &jsonVppInterface{
+		Index:      vppIf.SwIfIndex,
+		Name:       bytesToString(vppIf.InterfaceName),
+		Tag:        bytesToString(vppIf.Tag),
+		MacAddress: parseMacAddress(vppIf.L2Address, vppIf.L2AddressLength),
+		AdminState: vppIf.AdminUpDown,
+		LinkState:  vppIf.LinkUpDown,
+		LinkMTU:    vppIf.LinkMtu,
+		SubDot1ad:  vppIf.SubDot1ad,
+		SubID:      vppIf.SubID,
+		TxBytes:    vppIf.Stats.TxBytes,
+		TxPackets:  vppIf.Stats.TxPackets,
+		TxErrors:   vppIf.Stats.TxErrors,
+		RxBytes:    vppIf.Stats.RxBytes,
+		RxPackets:  vppIf.Stats.RxPackets,
+		RxErrors:   vppIf.Stats.RxErrors,
+		Drops:      vppIf.Stats.Drops,
+		Punts:      vppIf.Stats.Punts,
+	}
+}
+
+func toJSONVppPayload(svReply *vpe.ShowVersionReply, vppIfs []*vppInterface) *jsonVppPayload {
+	p := &jsonVppPayload{jsonVppDetails: toJSONVppDetails(svReply), Interfaces: make([]*jsonVppInterface, len(vppIfs))}
+	for index, vppIf := range vppIfs {
+		p.Interfaces[index] = toJSONVppInterface(vppIf)
+	}
+	return p
+}
+
+func dumpToJSONString(v *vppConnector) (string, error) {
+	payload := toJSONVppPayload(&v.VppDetails, v.Interfaces)
+	jsonBytes, err := json.Marshal(payload)
+	if err != nil {
+		return "", fmt.Errorf("failed to dump to json: %v", err)
+	}
+	return string(jsonBytes), nil
+}
diff --git a/extras/vpp_if_stats/response_example.json b/extras/vpp_if_stats/response_example.json
new file mode 100755
index 0000000..ed9f933
--- /dev/null
+++ b/extras/vpp_if_stats/response_example.json
@@ -0,0 +1,200 @@
+{
+  "vpp_details": {
+    "program": "vpe",
+    "version": "18.10-release",
+    "build_date": "Tue Oct 23 07:03:38 UTC 2018",
+    "build_directory": "/w/workspace/vpp-merge-1810-centos7"
+  },
+  "interfaces": [
+    {
+      "if_index": 0,
+      "if_name": "local0",
+      "if_tag": "",
+      "if_mac": "",
+      "if_admin_state": 0,
+      "if_link_state": 0,
+      "if_link_mtu": 0,
+      "if_sub_dot1ad": 0,
+      "if_sub_id": 0,
+      "if_tx_bytes": 0,
+      "if_tx_packets": 0,
+      "if_tx_errors": 0,
+      "if_rx_bytes": 0,
+      "if_rx_packets": 0,
+      "if_rx_errors": 0,
+      "if_drops": 0,
+      "if_punts": 0
+    },
+    {
+      "if_index": 1,
+      "if_name": "TenGigabitEthernet5e/0/2",
+      "if_tag": "",
+      "if_mac": "11:33:55:77:99:aa",
+      "if_admin_state": 1,
+      "if_link_state": 1,
+      "if_link_mtu": 9202,
+      "if_sub_dot1ad": 0,
+      "if_sub_id": 0,
+      "if_tx_bytes": 5024976,
+      "if_tx_packets": 40524,
+      "if_tx_errors": 0,
+      "if_rx_bytes": 200094228,
+      "if_rx_packets": 1685702,
+      "if_rx_errors": 0,
+      "if_drops": 1214356,
+      "if_punts": 0
+    },
+    {
+      "if_index": 2,
+      "if_name": "TenGigabitEthernet5e/0/3",
+      "if_tag": "",
+      "if_mac": "11:33:55:77:99:aa",
+      "if_admin_state": 1,
+      "if_link_state": 1,
+      "if_link_mtu": 9202,
+      "if_sub_dot1ad": 0,
+      "if_sub_id": 0,
+      "if_tx_bytes": 5024976,
+      "if_tx_packets": 40524,
+      "if_tx_errors": 0,
+      "if_rx_bytes": 233044788,
+      "if_rx_packets": 2257762,
+      "if_rx_errors": 0,
+      "if_drops": 1214348,
+      "if_punts": 0
+    },
+    {
+      "if_index": 3,
+      "if_name": "BondEthernet0",
+      "if_tag": "net-vpp.physnet:physnet1",
+      "if_mac": "11:33:55:77:99:bb",
+      "if_admin_state": 1,
+      "if_link_state": 1,
+      "if_link_mtu": 9216,
+      "if_sub_dot1ad": 0,
+      "if_sub_id": 0,
+      "if_tx_bytes": 0,
+      "if_tx_packets": 0,
+      "if_tx_errors": 0,
+      "if_rx_bytes": 0,
+      "if_rx_packets": 0,
+      "if_rx_errors": 0,
+      "if_drops": 1514852,
+      "if_punts": 0
+    },
+    {
+      "if_index": 4,
+      "if_name": "BondEthernet0.549",
+      "if_tag": "net-vpp.uplink:physnet1.vlan.549",
+      "if_mac": "",
+      "if_admin_state": 1,
+      "if_link_state": 1,
+      "if_link_mtu": 9216,
+      "if_sub_dot1ad": 0,
+      "if_sub_id": 549,
+      "if_tx_bytes": 0,
+      "if_tx_packets": 0,
+      "if_tx_errors": 0,
+      "if_rx_bytes": 1968,
+      "if_rx_packets": 26,
+      "if_rx_errors": 0,
+      "if_drops": 78,
+      "if_punts": 0
+    },
+    {
+      "if_index": 5,
+      "if_name": "VirtualEthernet0/0/0",
+      "if_tag": "net-vpp.port:fb9b1ce8-f643-45be-9298-ccd18f9018c8",
+      "if_mac": "dd:ff:11:33:55:77",
+      "if_admin_state": 1,
+      "if_link_state": 0,
+      "if_link_mtu": 9216,
+      "if_sub_dot1ad": 0,
+      "if_sub_id": 0,
+      "if_tx_bytes": 0,
+      "if_tx_packets": 0,
+      "if_tx_errors": 26,
+      "if_rx_bytes": 0,
+      "if_rx_packets": 0,
+      "if_rx_errors": 0,
+      "if_drops": 0,
+      "if_punts": 0
+    },
+    {
+      "if_index": 6,
+      "if_name": "BondEthernet0.529",
+      "if_tag": "net-vpp.uplink:physnet1.vlan.529",
+      "if_mac": "",
+      "if_admin_state": 1,
+      "if_link_state": 1,
+      "if_link_mtu": 9216,
+      "if_sub_dot1ad": 0,
+      "if_sub_id": 529,
+      "if_tx_bytes": 0,
+      "if_tx_packets": 0,
+      "if_tx_errors": 0,
+      "if_rx_bytes": 0,
+      "if_rx_packets": 0,
+      "if_rx_errors": 0,
+      "if_drops": 0,
+      "if_punts": 0
+    },
+    {
+      "if_index": 7,
+      "if_name": "VirtualEthernet0/0/1",
+      "if_tag": "net-vpp.port:bc726b1c-526e-4a8d-9f9a-c19b5dfe2b28",
+      "if_mac": "22:44:66:88:aa:cc",
+      "if_admin_state": 1,
+      "if_link_state": 0,
+      "if_link_mtu": 9216,
+      "if_sub_dot1ad": 0,
+      "if_sub_id": 0,
+      "if_tx_bytes": 0,
+      "if_tx_packets": 0,
+      "if_tx_errors": 0,
+      "if_rx_bytes": 0,
+      "if_rx_packets": 0,
+      "if_rx_errors": 0,
+      "if_drops": 0,
+      "if_punts": 0
+    },
+    {
+      "if_index": 8,
+      "if_name": "VirtualEthernet0/0/2",
+      "if_tag": "net-vpp.port:aaabbbcc-a86d-4eb5-a3bc-aaabbbcccddd",
+      "if_mac": "12:34:56:78:9a:bc",
+      "if_admin_state": 1,
+      "if_link_state": 0,
+      "if_link_mtu": 9216,
+      "if_sub_dot1ad": 0,
+      "if_sub_id": 0,
+      "if_tx_bytes": 0,
+      "if_tx_packets": 0,
+      "if_tx_errors": 26,
+      "if_rx_bytes": 0,
+      "if_rx_packets": 0,
+      "if_rx_errors": 0,
+      "if_drops": 0,
+      "if_punts": 0
+    },
+    {
+      "if_index": 9,
+      "if_name": "VirtualEthernet0/0/3",
+      "if_tag": "net-vpp.port:dddeeeff-838e-4995-9bd2-eeefff000111",
+      "if_mac": "fe:dc:ba:98:76:54",
+      "if_admin_state": 1,
+      "if_link_state": 0,
+      "if_link_mtu": 9216,
+      "if_sub_dot1ad": 0,
+      "if_sub_id": 0,
+      "if_tx_bytes": 0,
+      "if_tx_packets": 0,
+      "if_tx_errors": 26,
+      "if_rx_bytes": 0,
+      "if_rx_packets": 0,
+      "if_rx_errors": 0,
+      "if_drops": 0,
+      "if_punts": 0
+    }
+  ]
+}
\ No newline at end of file
diff --git a/extras/vpp_if_stats/response_schema.json b/extras/vpp_if_stats/response_schema.json
new file mode 100755
index 0000000..aa5f948
--- /dev/null
+++ b/extras/vpp_if_stats/response_schema.json
@@ -0,0 +1,253 @@
+{
+  "definitions": {},
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "$id": "http://example.com/root.json",
+  "type": "object",
+  "title": "The Root Schema",
+  "required": [
+    "vpp_details",
+    "interfaces"
+  ],
+  "properties": {
+    "vpp_details": {
+      "$id": "#/properties/vpp_details",
+      "type": "object",
+      "title": "The Vpp_details Schema",
+      "required": [
+        "program",
+        "version",
+        "build_date",
+        "build_directory"
+      ],
+      "properties": {
+        "program": {
+          "$id": "#/properties/vpp_details/properties/program",
+          "type": "string",
+          "title": "The Program Schema",
+          "default": "",
+          "examples": [
+            "vpe"
+          ],
+          "pattern": "^(.*)$"
+        },
+        "version": {
+          "$id": "#/properties/vpp_details/properties/version",
+          "type": "string",
+          "title": "The Version Schema",
+          "default": "",
+          "examples": [
+            "18.10-release"
+          ],
+          "pattern": "^(.*)$"
+        },
+        "build_date": {
+          "$id": "#/properties/vpp_details/properties/build_date",
+          "type": "string",
+          "title": "The Build_date Schema",
+          "default": "",
+          "examples": [
+            "Tue Oct 23 07:03:38 UTC 2018"
+          ],
+          "pattern": "^(.*)$"
+        },
+        "build_directory": {
+          "$id": "#/properties/vpp_details/properties/build_directory",
+          "type": "string",
+          "title": "The Build_directory Schema",
+          "default": "",
+          "examples": [
+            "/w/workspace/vpp-merge-1810-centos7"
+          ],
+          "pattern": "^(.*)$"
+        }
+      }
+    },
+    "interfaces": {
+      "$id": "#/properties/interfaces",
+      "type": "array",
+      "title": "The Interfaces Schema",
+      "items": {
+        "$id": "#/properties/interfaces/items",
+        "type": "object",
+        "title": "The Items Schema",
+        "required": [
+          "if_index",
+          "if_name",
+          "if_tag",
+          "if_mac",
+          "if_admin_state",
+          "if_link_state",
+          "if_link_mtu",
+          "if_sub_dot1ad",
+          "if_sub_id",
+          "if_tx_bytes",
+          "if_tx_packets",
+          "if_tx_errors",
+          "if_rx_bytes",
+          "if_rx_packets",
+          "if_rx_errors",
+          "if_drops",
+          "if_punts"
+        ],
+        "properties": {
+          "if_index": {
+            "$id": "#/properties/interfaces/items/properties/if_index",
+            "type": "integer",
+            "title": "The If_index Schema",
+            "default": 0,
+            "examples": [
+              0
+            ]
+          },
+          "if_name": {
+            "$id": "#/properties/interfaces/items/properties/if_name",
+            "type": "string",
+            "title": "The If_name Schema",
+            "default": "",
+            "examples": [
+              "local0"
+            ],
+            "pattern": "^(.*)$"
+          },
+          "if_tag": {
+            "$id": "#/properties/interfaces/items/properties/if_tag",
+            "type": "string",
+            "title": "The If_tag Schema",
+            "default": "",
+            "examples": [
+              ""
+            ],
+            "pattern": "^(.*)$"
+          },
+          "if_mac": {
+            "$id": "#/properties/interfaces/items/properties/if_mac",
+            "type": "string",
+            "title": "The If_mac Schema",
+            "default": "",
+            "examples": [
+              ""
+            ],
+            "pattern": "^(.*)$"
+          },
+          "if_admin_state": {
+            "$id": "#/properties/interfaces/items/properties/if_admin_state",
+            "type": "integer",
+            "title": "The If_admin_state Schema",
+            "default": 0,
+            "examples": [
+              0
+            ]
+          },
+          "if_link_state": {
+            "$id": "#/properties/interfaces/items/properties/if_link_state",
+            "type": "integer",
+            "title": "The If_link_state Schema",
+            "default": 0,
+            "examples": [
+              0
+            ]
+          },
+          "if_link_mtu": {
+            "$id": "#/properties/interfaces/items/properties/if_link_mtu",
+            "type": "integer",
+            "title": "The If_link_mtu Schema",
+            "default": 0,
+            "examples": [
+              0
+            ]
+          },
+          "if_sub_dot1ad": {
+            "$id": "#/properties/interfaces/items/properties/if_sub_dot1ad",
+            "type": "integer",
+            "title": "The If_sub_dot1ad Schema",
+            "default": 0,
+            "examples": [
+              0
+            ]
+          },
+          "if_sub_id": {
+            "$id": "#/properties/interfaces/items/properties/if_sub_id",
+            "type": "integer",
+            "title": "The If_sub_id Schema",
+            "default": 0,
+            "examples": [
+              0
+            ]
+          },
+          "if_tx_bytes": {
+            "$id": "#/properties/interfaces/items/properties/if_tx_bytes",
+            "type": "integer",
+            "title": "The If_tx_bytes Schema",
+            "default": 0,
+            "examples": [
+              0
+            ]
+          },
+          "if_tx_packets": {
+            "$id": "#/properties/interfaces/items/properties/if_tx_packets",
+            "type": "integer",
+            "title": "The If_tx_packets Schema",
+            "default": 0,
+            "examples": [
+              0
+            ]
+          },
+          "if_tx_errors": {
+            "$id": "#/properties/interfaces/items/properties/if_tx_errors",
+            "type": "integer",
+            "title": "The If_tx_errors Schema",
+            "default": 0,
+            "examples": [
+              0
+            ]
+          },
+          "if_rx_bytes": {
+            "$id": "#/properties/interfaces/items/properties/if_rx_bytes",
+            "type": "integer",
+            "title": "The If_rx_bytes Schema",
+            "default": 0,
+            "examples": [
+              0
+            ]
+          },
+          "if_rx_packets": {
+            "$id": "#/properties/interfaces/items/properties/if_rx_packets",
+            "type": "integer",
+            "title": "The If_rx_packets Schema",
+            "default": 0,
+            "examples": [
+              0
+            ]
+          },
+          "if_rx_errors": {
+            "$id": "#/properties/interfaces/items/properties/if_rx_errors",
+            "type": "integer",
+            "title": "The If_rx_errors Schema",
+            "default": 0,
+            "examples": [
+              0
+            ]
+          },
+          "if_drops": {
+            "$id": "#/properties/interfaces/items/properties/if_drops",
+            "type": "integer",
+            "title": "The If_drops Schema",
+            "default": 0,
+            "examples": [
+              0
+            ]
+          },
+          "if_punts": {
+            "$id": "#/properties/interfaces/items/properties/if_punts",
+            "type": "integer",
+            "title": "The If_punts Schema",
+            "default": 0,
+            "examples": [
+              0
+            ]
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/extras/vpp_if_stats/statsmock.go b/extras/vpp_if_stats/statsmock.go
new file mode 100755
index 0000000..72528f5
--- /dev/null
+++ b/extras/vpp_if_stats/statsmock.go
@@ -0,0 +1,92 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: git.fd.io/govpp.git/adapter (interfaces: StatsAPI)
+
+// Package mock_adapter is a generated GoMock package.
+package main
+
+import (
+	adapter "git.fd.io/govpp.git/adapter"
+	gomock "github.com/golang/mock/gomock"
+	reflect "reflect"
+)
+
+// MockStatsAPI is a mock of StatsAPI interface
+type MockStatsAPI struct {
+	ctrl     *gomock.Controller
+	recorder *MockStatsAPIMockRecorder
+}
+
+// MockStatsAPIMockRecorder is the mock recorder for MockStatsAPI
+type MockStatsAPIMockRecorder struct {
+	mock *MockStatsAPI
+}
+
+// NewMockStatsAPI creates a new mock instance
+func NewMockStatsAPI(ctrl *gomock.Controller) *MockStatsAPI {
+	mock := &MockStatsAPI{ctrl: ctrl}
+	mock.recorder = &MockStatsAPIMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockStatsAPI) EXPECT() *MockStatsAPIMockRecorder {
+	return m.recorder
+}
+
+// Connect mocks base method
+func (m *MockStatsAPI) Connect() error {
+	ret := m.ctrl.Call(m, "Connect")
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Connect indicates an expected call of Connect
+func (mr *MockStatsAPIMockRecorder) Connect() *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connect", reflect.TypeOf((*MockStatsAPI)(nil).Connect))
+}
+
+// Disconnect mocks base method
+func (m *MockStatsAPI) Disconnect() error {
+	ret := m.ctrl.Call(m, "Disconnect")
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Disconnect indicates an expected call of Disconnect
+func (mr *MockStatsAPIMockRecorder) Disconnect() *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Disconnect", reflect.TypeOf((*MockStatsAPI)(nil).Disconnect))
+}
+
+// DumpStats mocks base method
+func (m *MockStatsAPI) DumpStats(arg0 ...string) ([]*adapter.StatEntry, error) {
+	varargs := []interface{}{}
+	for _, a := range arg0 {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "DumpStats", varargs...)
+	ret0, _ := ret[0].([]*adapter.StatEntry)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// DumpStats indicates an expected call of DumpStats
+func (mr *MockStatsAPIMockRecorder) DumpStats(arg0 ...interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DumpStats", reflect.TypeOf((*MockStatsAPI)(nil).DumpStats), arg0...)
+}
+
+// ListStats mocks base method
+func (m *MockStatsAPI) ListStats(arg0 ...string) ([]string, error) {
+	varargs := []interface{}{}
+	for _, a := range arg0 {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "ListStats", varargs...)
+	ret0, _ := ret[0].([]string)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ListStats indicates an expected call of ListStats
+func (mr *MockStatsAPIMockRecorder) ListStats(arg0 ...interface{}) *gomock.Call {
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListStats", reflect.TypeOf((*MockStatsAPI)(nil).ListStats), arg0...)
+}
diff --git a/extras/vpp_if_stats/vpp_if_stats.go b/extras/vpp_if_stats/vpp_if_stats.go
new file mode 100755
index 0000000..48ce085
--- /dev/null
+++ b/extras/vpp_if_stats/vpp_if_stats.go
@@ -0,0 +1,223 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"git.fd.io/govpp.git"
+	"git.fd.io/govpp.git/adapter"
+	"git.fd.io/govpp.git/adapter/vppapiclient"
+	"git.fd.io/govpp.git/api"
+	"git.fd.io/govpp.git/core"
+	"git.fd.io/govpp.git/examples/bin_api/interfaces"
+	"git.fd.io/govpp.git/examples/bin_api/vpe"
+	"log"
+)
+
+//////////////////////////////////////
+/////////   Data structs   ///////////
+//////////////////////////////////////
+
+const defaultStatsSocketPath = "/run/vpp/stats.sock"
+const defaultShmPrefix = ""
+
+func parseMacAddress(l2Address []byte, l2AddressLength uint32) string {
+	var mac string
+	for i := uint32(0); i < l2AddressLength; i++ {
+		mac += fmt.Sprintf("%02x", l2Address[i])
+		if i < l2AddressLength-1 {
+			mac += ":"
+		}
+	}
+	return mac
+}
+
+type interfaceStats struct {
+	TxBytes   uint64
+	TxPackets uint64
+	TxErrors  uint64
+	RxBytes   uint64
+	RxPackets uint64
+	RxErrors  uint64
+	Drops     uint64
+	Punts     uint64
+}
+
+type vppInterface struct {
+	interfaces.SwInterfaceDetails
+	Stats interfaceStats
+}
+
+type vppConnector struct {
+	statsSocketPath string
+	shmPrefix       string
+
+	conn  *core.Connection
+	api   api.Channel
+	stats adapter.StatsAPI
+
+	VppDetails vpe.ShowVersionReply
+	Interfaces []*vppInterface
+}
+
+//////////////////////////////////////
+/////////   VPP workflow   ///////////
+//////////////////////////////////////
+
+func (v *vppConnector) getVppVersion() error {
+	if err := v.api.SendRequest(&vpe.ShowVersion{}).ReceiveReply(&v.VppDetails); err != nil {
+		return fmt.Errorf("failed to fetch vpp version: %v", err)
+	}
+	return nil
+}
+
+func (v *vppConnector) getInterfaces() error {
+	ifCtx := v.api.SendMultiRequest(&interfaces.SwInterfaceDump{})
+	for {
+		ifDetails := interfaces.SwInterfaceDetails{}
+		stop, err := ifCtx.ReceiveReply(&ifDetails)
+		if err != nil {
+			return fmt.Errorf("failed to fetch vpp interface: %v", err)
+		}
+		if stop {
+			break
+		}
+
+		v.Interfaces = append(v.Interfaces, &vppInterface{SwInterfaceDetails: ifDetails})
+	}
+	return nil
+}
+
+func (v *vppConnector) connect() (err error) {
+	if v.conn, err = govpp.Connect(v.shmPrefix); err != nil {
+		return fmt.Errorf("failed to connect to vpp: %v", err)
+	}
+
+	if v.api, err = v.conn.NewAPIChannel(); err != nil {
+		return fmt.Errorf("failed to create api channel: %v", err)
+	}
+
+	v.stats = vppapiclient.NewStatClient(v.statsSocketPath)
+	if err = v.stats.Connect(); err != nil {
+		return fmt.Errorf("failed to connect to Stats adapter: %v", err)
+	}
+
+	return
+}
+
+func (v *vppConnector) disconnect() {
+	if v.stats != nil {
+		v.stats.Disconnect()
+	}
+	if v.conn != nil {
+		v.conn.Disconnect()
+	}
+}
+
+func (v *vppConnector) reduceCombinedCounters(stat *adapter.StatEntry) *[]adapter.CombinedCounter {
+	counters := stat.Data.(adapter.CombinedCounterStat)
+	stats := make([]adapter.CombinedCounter, len(v.Interfaces))
+	for _, workerStats := range counters {
+		for i, interfaceStats := range workerStats {
+			stats[i].Bytes += interfaceStats.Bytes
+			stats[i].Packets += interfaceStats.Packets
+		}
+	}
+	return &stats
+}
+
+func (v *vppConnector) reduceSimpleCounters(stat *adapter.StatEntry) *[]adapter.Counter {
+	counters := stat.Data.(adapter.SimpleCounterStat)
+	stats := make([]adapter.Counter, len(v.Interfaces))
+	for _, workerStats := range counters {
+		for i, interfaceStats := range workerStats {
+			stats[i] += interfaceStats
+		}
+	}
+	return &stats
+}
+
+func (v *vppConnector) getStatsForAllInterfaces() error {
+	statsDump, err := v.stats.DumpStats("/if")
+	if err != nil {
+		return fmt.Errorf("failed to dump vpp Stats: %v", err)
+	}
+
+	stats := func(i int) *interfaceStats { return &v.Interfaces[uint32(i)].Stats }
+
+	for _, stat := range statsDump {
+		switch stat.Name {
+		case "/if/tx":
+			{
+				for i, counter := range *v.reduceCombinedCounters(stat) {
+					stats(i).TxBytes = uint64(counter.Bytes)
+					stats(i).TxPackets = uint64(counter.Packets)
+				}
+			}
+		case "/if/rx":
+			{
+				for i, counter := range *v.reduceCombinedCounters(stat) {
+					stats(i).RxBytes = uint64(counter.Bytes)
+					stats(i).RxPackets = uint64(counter.Packets)
+				}
+			}
+		case "/if/tx-error":
+			{
+				for i, counter := range *v.reduceSimpleCounters(stat) {
+					stats(i).TxErrors = uint64(counter)
+				}
+			}
+		case "/if/rx-error":
+			{
+				for i, counter := range *v.reduceSimpleCounters(stat) {
+					stats(i).RxErrors = uint64(counter)
+				}
+			}
+		case "/if/drops":
+			{
+				for i, counter := range *v.reduceSimpleCounters(stat) {
+					stats(i).Drops = uint64(counter)
+				}
+			}
+		case "/if/punt":
+			{
+				for i, counter := range *v.reduceSimpleCounters(stat) {
+					stats(i).Punts = uint64(counter)
+				}
+			}
+		}
+	}
+	return nil
+}
+
+//////////////////////////////////////
+
+func main() {
+	statsSocketPathPtr := flag.String("stats_socket_path", defaultStatsSocketPath, "Path to vpp stats socket")
+	shmPrefixPtr := flag.String("shm_prefix", defaultShmPrefix, "Shared memory prefix (advanced)")
+	flag.Parse()
+
+	vppConn := &vppConnector{statsSocketPath: *statsSocketPathPtr, shmPrefix: *shmPrefixPtr}
+	defer vppConn.disconnect()
+
+	if err := vppConn.connect(); err != nil {
+		log.Fatalln(err)
+	}
+
+	if err := vppConn.getVppVersion(); err != nil {
+		log.Fatalln(err)
+	}
+
+	if err := vppConn.getInterfaces(); err != nil {
+		log.Fatalln(err)
+	}
+
+	if err := vppConn.getStatsForAllInterfaces(); err != nil {
+		log.Fatalln(err)
+	}
+
+	jsonString, err := dumpToJSONString(vppConn)
+	if err != nil {
+		log.Fatalln(err)
+	}
+	fmt.Println(jsonString)
+}
diff --git a/extras/vpp_if_stats/vpp_if_stats_test.go b/extras/vpp_if_stats/vpp_if_stats_test.go
new file mode 100755
index 0000000..07a5983
--- /dev/null
+++ b/extras/vpp_if_stats/vpp_if_stats_test.go
@@ -0,0 +1,192 @@
+package main
+
+import (
+	"git.fd.io/govpp.git/adapter"
+	"git.fd.io/govpp.git/api"
+	"git.fd.io/govpp.git/examples/bin_api/interfaces"
+	"git.fd.io/govpp.git/examples/bin_api/vpe"
+	"github.com/golang/mock/gomock"
+	"github.com/stretchr/testify/assert"
+	"math/rand"
+	"testing"
+	"time"
+)
+
+var (
+	vppDetails = vpe.ShowVersionReply{
+		Program: []byte("vpe"),
+		Version: []byte("18.10"),
+	}
+
+	testSwIfIndex = uint32(0)
+	testInterface = func() *vppInterface {
+		return &vppInterface{
+			SwInterfaceDetails: interfaces.SwInterfaceDetails{SwIfIndex: testSwIfIndex}, // TODO
+			Stats:              interfaceStats{},                                        // TODO
+		}
+	}
+	testInterfaces = func() *map[uint32]*vppInterface {
+		return &map[uint32]*vppInterface{
+			testSwIfIndex: testInterface(),
+		}
+	}
+
+	r                 = rand.New(rand.NewSource(time.Now().UnixNano()))
+	testCombinedStats = interfaceStats{
+		TxBytes:   r.Uint64(),
+		TxPackets: r.Uint64(),
+		RxBytes:   r.Uint64(),
+		RxPackets: r.Uint64(),
+	}
+	testCombinedStatsDump = []*adapter.StatEntry{
+		{
+			Name: "/if/tx",
+			Type: adapter.CombinedCounterVector,
+			Data: adapter.CombinedCounterStat{
+				[]adapter.CombinedCounter{
+					{
+						Bytes:   adapter.Counter(testCombinedStats.TxBytes),
+						Packets: adapter.Counter(testCombinedStats.TxPackets),
+					},
+				},
+			},
+		},
+		{
+			Name: "/if/rx",
+			Type: adapter.CombinedCounterVector,
+			Data: adapter.CombinedCounterStat{
+				[]adapter.CombinedCounter{
+					{
+						Bytes:   adapter.Counter(testCombinedStats.RxBytes),
+						Packets: adapter.Counter(testCombinedStats.RxPackets),
+					},
+				},
+			},
+		},
+	}
+
+	testSimpleStats = interfaceStats{
+		TxErrors: r.Uint64(),
+		RxErrors: r.Uint64(),
+		Drops:    r.Uint64(),
+		Punts:    r.Uint64(),
+	}
+	testSimpleStatsDump = []*adapter.StatEntry{
+		{
+			Name: "/if/tx-error",
+			Type: adapter.SimpleCounterVector,
+			Data: adapter.SimpleCounterStat{
+				[]adapter.Counter{adapter.Counter(testSimpleStats.TxErrors)},
+			},
+		},
+		{
+			Name: "/if/rx-error",
+			Type: adapter.SimpleCounterVector,
+			Data: adapter.SimpleCounterStat{
+				[]adapter.Counter{adapter.Counter(testSimpleStats.RxErrors)},
+			},
+		},
+		{
+			Name: "/if/drops",
+			Type: adapter.SimpleCounterVector,
+			Data: adapter.SimpleCounterStat{
+				[]adapter.Counter{adapter.Counter(testSimpleStats.Drops)},
+			},
+		},
+		{
+			Name: "/if/punt",
+			Type: adapter.SimpleCounterVector,
+			Data: adapter.SimpleCounterStat{
+				[]adapter.Counter{adapter.Counter(testSimpleStats.Punts)},
+			},
+		},
+	}
+)
+
+type showDetailsContext struct {
+	details vpe.ShowVersionReply
+}
+
+func (ctx *showDetailsContext) ReceiveReply(msg api.Message) (err error) {
+	*(msg.(*vpe.ShowVersionReply)) = vppDetails
+	return nil
+}
+
+type interfaceDumpContext struct {
+	interfaces   []interfaces.SwInterfaceDetails
+	currentIndex int
+}
+
+func (ctx *interfaceDumpContext) ReceiveReply(msg api.Message) (lastReplyReceived bool, err error) {
+	stop := ctx.currentIndex >= len(ctx.interfaces)
+	if !stop {
+		*(msg.(*interfaces.SwInterfaceDetails)) = ctx.interfaces[ctx.currentIndex]
+		ctx.currentIndex++
+	}
+	return stop, nil
+}
+
+func TestVppIfStats_GetVppVersion(t *testing.T) {
+	mockCtrl := gomock.NewController(t)
+	defer mockCtrl.Finish()
+
+	mockChannel := NewMockChannel(mockCtrl)
+	mockChannel.EXPECT().SendRequest(&vpe.ShowVersion{}).Return(&showDetailsContext{details: vppDetails})
+
+	v := vppConnector{api: mockChannel}
+	err := v.getVppVersion()
+	assert.NoError(t, err, "GetVppVersion should not return an error")
+	assert.Equal(t, vppDetails, v.VppDetails, "VPP details should be saved")
+}
+
+func TestVppIfStats_GetInterfaces(t *testing.T) {
+	mockCtrl := gomock.NewController(t)
+	defer mockCtrl.Finish()
+
+	testContext := interfaceDumpContext{interfaces: []interfaces.SwInterfaceDetails{testInterface().SwInterfaceDetails}}
+	mockChannel := NewMockChannel(mockCtrl)
+	mockChannel.EXPECT().SendMultiRequest(&interfaces.SwInterfaceDump{}).Return(&testContext)
+
+	v := vppConnector{api: mockChannel}
+	err := v.getInterfaces()
+	assert.NoError(t, err, "GetInterfaces should not return an error")
+	assert.Len(t, v.Interfaces, len(testContext.interfaces), "All dumped interfaces should be saved")
+	if len(testContext.interfaces) > 0 {
+		assert.Equal(t, testContext.interfaces[0], v.Interfaces[testInterface().SwIfIndex].SwInterfaceDetails,
+			"All dumped interface info should be saved")
+	}
+}
+
+func TestVppIfStats_GetStatsForAllInterfacesNoStats(t *testing.T) {
+	mockCtrl := gomock.NewController(t)
+	defer mockCtrl.Finish()
+
+	mockStatsAPI := NewMockStatsAPI(mockCtrl)
+	mockStatsAPI.EXPECT().DumpStats("/if").Return([]*adapter.StatEntry{}, nil)
+
+	v := vppConnector{stats: mockStatsAPI, Interfaces: *testInterfaces()}
+	err := v.getStatsForAllInterfaces()
+	assert.NoError(t, err, "GetStatsForAllInterfaces should not return an error")
+	assert.Equal(t, interfaceStats{}, v.Interfaces[testSwIfIndex].Stats, "Stats should be empty")
+}
+
+func testStats(t *testing.T, statsDump *[]*adapter.StatEntry, expectedStats *interfaceStats) {
+	mockCtrl := gomock.NewController(t)
+	defer mockCtrl.Finish()
+
+	mockStatsAPI := NewMockStatsAPI(mockCtrl)
+	mockStatsAPI.EXPECT().DumpStats("/if").Return(*statsDump, nil)
+
+	v := vppConnector{stats: mockStatsAPI, Interfaces: *testInterfaces()}
+	err := v.getStatsForAllInterfaces()
+	assert.NoError(t, err, "GetStatsForAllInterfaces should not return an error")
+	assert.Equal(t, *expectedStats, v.Interfaces[testSwIfIndex].Stats, "Collected and saved stats should match")
+}
+
+func TestVppIfStats_GetStatsForAllInterfacesCombinedStats(t *testing.T) {
+	testStats(t, &testCombinedStatsDump, &testCombinedStats)
+}
+
+func TestVppIfStats_GetStatsForAllInterfacesSimpleStats(t *testing.T) {
+	testStats(t, &testSimpleStatsDump, &testSimpleStats)
+}