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