stats: golang vpp_if_stats_client 01/16101/4
authorKoren Lev <korenlev@gmail.com>
Wed, 21 Nov 2018 16:46:54 +0000 (18:46 +0200)
committerOle Trøan <otroan@employees.org>
Thu, 22 Nov 2018 06:31:30 +0000 (06:31 +0000)
see README for details

Change-Id: Ida603ccaee21dabc903512699b5b355cebb70320
Signed-off-by: Koren Lev <korenlev@gmail.com>
extras/vpp_if_stats/README.md [new file with mode: 0755]
extras/vpp_if_stats/apimock.go [new file with mode: 0755]
extras/vpp_if_stats/json_structs.go [new file with mode: 0755]
extras/vpp_if_stats/response_example.json [new file with mode: 0755]
extras/vpp_if_stats/response_schema.json [new file with mode: 0755]
extras/vpp_if_stats/statsmock.go [new file with mode: 0755]
extras/vpp_if_stats/vpp_if_stats.go [new file with mode: 0755]
extras/vpp_if_stats/vpp_if_stats_test.go [new file with mode: 0755]

diff --git a/extras/vpp_if_stats/README.md b/extras/vpp_if_stats/README.md
new file mode 100755 (executable)
index 0000000..7185d6c
--- /dev/null
@@ -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 (executable)
index 0000000..7736334
--- /dev/null
@@ -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 (executable)
index 0000000..a42b6d8
--- /dev/null
@@ -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 (executable)
index 0000000..ed9f933
--- /dev/null
@@ -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 (executable)
index 0000000..aa5f948
--- /dev/null
@@ -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 (executable)
index 0000000..72528f5
--- /dev/null
@@ -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 (executable)
index 0000000..48ce085
--- /dev/null
@@ -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 (executable)
index 0000000..07a5983
--- /dev/null
@@ -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)
+}