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:
Koren Lev
2018-11-21 18:46:54 +02:00
committed by Ole Trøan
parent 15e8e68181
commit 19ca78fbd7
8 changed files with 1180 additions and 0 deletions

35
extras/vpp_if_stats/README.md Executable file
View 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
View 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)
}

View 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
}

View 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
}
]
}

View 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
]
}
}
}
}
}
}

View 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...)
}

View 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)
}

View 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)
}