stats: golang vpp_if_stats_client
see README for details Change-Id: Ida603ccaee21dabc903512699b5b355cebb70320 Signed-off-by: Koren Lev <korenlev@gmail.com>
This commit is contained in:
35
extras/vpp_if_stats/README.md
Executable file
35
extras/vpp_if_stats/README.md
Executable file
@ -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)
|
||||
|
92
extras/vpp_if_stats/apimock.go
Executable file
92
extras/vpp_if_stats/apimock.go
Executable file
@ -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)
|
||||
}
|
93
extras/vpp_if_stats/json_structs.go
Executable file
93
extras/vpp_if_stats/json_structs.go
Executable file
@ -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
|
||||
}
|
200
extras/vpp_if_stats/response_example.json
Executable file
200
extras/vpp_if_stats/response_example.json
Executable file
@ -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
|
||||
}
|
||||
]
|
||||
}
|
253
extras/vpp_if_stats/response_schema.json
Executable file
253
extras/vpp_if_stats/response_schema.json
Executable file
@ -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
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
92
extras/vpp_if_stats/statsmock.go
Executable file
92
extras/vpp_if_stats/statsmock.go
Executable file
@ -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...)
|
||||
}
|
223
extras/vpp_if_stats/vpp_if_stats.go
Executable file
223
extras/vpp_if_stats/vpp_if_stats.go
Executable file
@ -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)
|
||||
}
|
192
extras/vpp_if_stats/vpp_if_stats_test.go
Executable file
192
extras/vpp_if_stats/vpp_if_stats_test.go
Executable file
@ -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)
|
||||
}
|
Reference in New Issue
Block a user