From 45e38494c1d65ad9178ad15f4048c0ab16f98b77 Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Tue, 19 Feb 2019 13:57:12 +0100 Subject: [PATCH] Introduce higer-level API for retrieving statistics - see stats-api example Change-Id: I11d29d32b60d25238e75cb6b86ee34842348ab38 Signed-off-by: Ondrej Fabry --- adapter/stats_api.go | 24 ++- api/{api.go => binapi.go} | 0 api/stats.go | 79 ++++++++ core/connection.go | 25 ++- core/request_handler.go | 8 +- core/stats.go | 397 ++++++++++++++++++++++++++++++++++++++++ examples/stats-api/README.md | 93 +++++++++- examples/stats-api/stats_api.go | 55 +++++- 8 files changed, 649 insertions(+), 32 deletions(-) rename api/{api.go => binapi.go} (100%) create mode 100644 api/stats.go create mode 100644 core/stats.go diff --git a/adapter/stats_api.go b/adapter/stats_api.go index 4a3f130..3538176 100644 --- a/adapter/stats_api.go +++ b/adapter/stats_api.go @@ -14,6 +14,10 @@ package adapter +import ( + "fmt" +) + // StatsAPI provides connection to VPP stats API. type StatsAPI interface { // Connect establishes client connection to the stats API. @@ -34,11 +38,11 @@ type StatsAPI interface { type StatType int const ( - _ StatType = iota - ScalarIndex - SimpleCounterVector - CombinedCounterVector - ErrorIndex + _ StatType = 0 + ScalarIndex = 1 + SimpleCounterVector = 2 + CombinedCounterVector = 3 + ErrorIndex = 4 ) func (d StatType) String() string { @@ -52,7 +56,7 @@ func (d StatType) String() string { case ErrorIndex: return "ErrorIndex" } - return "UnknownStatType" + return fmt.Sprintf("UnknownStatType(%d)", d) } // StatEntry represents single stat entry. The type of stat stored in Data @@ -79,13 +83,13 @@ type ScalarStat float64 type ErrorStat uint64 // SimpleCounterStat represents stat for SimpleCounterVector. -// The outer array represents workers and the inner array represents sw_if_index. -// Values should be aggregated per interface for every worker. +// The outer array represents workers and the inner array represents interface/node/.. indexes. +// Values should be aggregated per interface/node for every worker. type SimpleCounterStat [][]Counter // CombinedCounterStat represents stat for CombinedCounterVector. -// The outer array represents workers and the inner array represents sw_if_index. -// Values should be aggregated per interface for every worker. +// The outer array represents workers and the inner array represents interface/node/.. indexes. +// Values should be aggregated per interface/node for every worker. type CombinedCounterStat [][]CombinedCounter // Data represents some type of stat which is usually defined by StatType. diff --git a/api/api.go b/api/binapi.go similarity index 100% rename from api/api.go rename to api/binapi.go diff --git a/api/stats.go b/api/stats.go new file mode 100644 index 0000000..ec623c7 --- /dev/null +++ b/api/stats.go @@ -0,0 +1,79 @@ +package api + +// SystemStats represents global system statistics. +type SystemStats struct { + VectorRate float64 + InputRate float64 + LastUpdate float64 + LastStatsClear float64 + Heartbeat float64 +} + +// NodeStats represents per node statistics. +type NodeStats struct { + Nodes []NodeCounters +} + +// NodeCounters represents node counters. +type NodeCounters struct { + NodeIndex uint32 + // TODO: node name is not currently retrievable via stats API (will be most likely added in 19.04) + //NodeName string + + Clocks uint64 + Vectors uint64 + Calls uint64 + Suspends uint64 +} + +// InterfaceStats represents per interface statistics. +type InterfaceStats struct { + Interfaces []InterfaceCounters +} + +// InterfaceCounters represents interface counters. +type InterfaceCounters struct { + InterfaceIndex uint32 + // TODO: interface name is not currently retrievable via stats API (will be most likely added in 19.04) + //InterfaceName string + + RxPackets uint64 + RxBytes uint64 + RxErrors uint64 + TxPackets uint64 + TxBytes uint64 + TxErrors uint64 + + RxUnicast [2]uint64 // packets[0], bytes[1] + RxMulticast [2]uint64 // packets[0], bytes[1] + RxBroadcast [2]uint64 // packets[0], bytes[1] + TxUnicastMiss [2]uint64 // packets[0], bytes[1] + TxMulticast [2]uint64 // packets[0], bytes[1] + TxBroadcast [2]uint64 // packets[0], bytes[1] + + Drops uint64 + Punts uint64 + IP4 uint64 + IP6 uint64 + RxNoBuf uint64 + RxMiss uint64 +} + +// ErrorStats represents statistics per error counter. +type ErrorStats struct { + Errors []ErrorCounter +} + +// ErrorCounter represents error counter. +type ErrorCounter struct { + CounterName string + Value uint64 +} + +// StatsProvider provides the methods for getting statistics. +type StatsProvider interface { + GetSystemStats() (*SystemStats, error) + GetNodeStats() (*NodeStats, error) + GetInterfaceStats() (*InterfaceStats, error) + GetErrorStats(names ...string) (*ErrorStats, error) +} diff --git a/core/connection.go b/core/connection.go index 14b0af4..a21cc28 100644 --- a/core/connection.go +++ b/core/connection.go @@ -41,7 +41,7 @@ var ( HealthCheckThreshold = 1 // number of failed health checks until the error is reported DefaultReplyTimeout = time.Second * 1 // default timeout for replies from VPP ReconnectInterval = time.Second * 1 // default interval for reconnect attempts - MaxReconnectAttempts = 10 // maximum number of reconnect attempts + MaxReconnectAttempts = 3 // maximum number of reconnect attempts ) // ConnectionState represents the current state of the connection to VPP. @@ -58,6 +58,19 @@ const ( Failed ) +func (s ConnectionState) String() string { + switch s { + case Connected: + return "Connected" + case Disconnected: + return "Disconnected" + case Failed: + return "Failed" + default: + return fmt.Sprintf("UnknownState(%d)", s) + } +} + // ConnectionEvent is a notification about change in the VPP connection state. type ConnectionEvent struct { // Timestamp holds the time when the event has been created. @@ -72,7 +85,8 @@ type ConnectionEvent struct { // Connection represents a shared memory connection to VPP via vppAdapter. type Connection struct { - vppClient adapter.VppAPI // VPP binary API client adapter + vppClient adapter.VppAPI // VPP binary API client + //statsClient adapter.StatsAPI // VPP stats API client vppConnected uint32 // non-zero if the adapter is connected to VPP @@ -107,8 +121,9 @@ func newConnection(binapi adapter.VppAPI) *Connection { return c } -// Connect connects to VPP using specified VPP adapter and returns the connection handle. -// This call blocks until VPP is connected, or an error occurs. Only one connection attempt will be performed. +// Connect connects to VPP API using specified adapter and returns a connection handle. +// This call blocks until it is either connected, or an error occurs. +// Only one connection attempt will be performed. func Connect(binapi adapter.VppAPI) (*Connection, error) { // create new connection handle c := newConnection(binapi) @@ -158,7 +173,7 @@ func (c *Connection) connectVPP() error { return nil } -// Disconnect disconnects from VPP and releases all connection-related resources. +// Disconnect disconnects from VPP API and releases all connection-related resources. func (c *Connection) Disconnect() { if c == nil { return diff --git a/core/request_handler.go b/core/request_handler.go index dc90747..55a825a 100644 --- a/core/request_handler.go +++ b/core/request_handler.go @@ -88,11 +88,10 @@ func (c *Connection) processRequest(ch *Channel, req *vppRequest) error { "context": context, "is_multi": req.multi, "msg_id": msgID, - "msg_name": req.msg.GetMessageName(), "msg_size": len(data), "seq_num": req.seqNum, "msg_crc": req.msg.GetCrcString(), - }).Debug(" -> Sending a message to VPP.") + }).Debugf(" --> sending msg: %s", req.msg.GetMessageName()) } // send the request to VPP @@ -117,7 +116,7 @@ func (c *Connection) processRequest(ch *Channel, req *vppRequest) error { "msg_id": c.pingReqID, "msg_size": len(pingData), "seq_num": req.seqNum, - }).Debug(" -> Sending a control ping to VPP.") + }).Debug(" -> sending control ping") if err := c.vppClient.SendMsg(context, pingData); err != nil { log.WithFields(logger.Fields{ @@ -159,13 +158,12 @@ func (c *Connection) msgCallback(msgID uint16, data []byte) { log.WithFields(logger.Fields{ "context": context, "msg_id": msgID, - "msg_name": msg.GetMessageName(), "msg_size": len(data), "channel": chanID, "is_multi": isMulti, "seq_num": seqNum, "msg_crc": msg.GetCrcString(), - }).Debug(" <- Received a message from VPP.") + }).Debugf(" <- received msg: %s", msg.GetMessageName()) } if context == 0 || c.isNotificationMessage(msgID) { diff --git a/core/stats.go b/core/stats.go new file mode 100644 index 0000000..26b9bc9 --- /dev/null +++ b/core/stats.go @@ -0,0 +1,397 @@ +package core + +import ( + "strings" + "sync/atomic" + + "git.fd.io/govpp.git/adapter" + "git.fd.io/govpp.git/api" +) + +const ( + CounterStatsPrefix = "/err/" + + SystemStatsPrefix = "/sys/" + SystemStats_VectorRate = SystemStatsPrefix + "vector_rate" + SystemStats_InputRate = SystemStatsPrefix + "input_rate" + SystemStats_LastUpdate = SystemStatsPrefix + "last_update" + SystemStats_LastStatsClear = SystemStatsPrefix + "last_stats_clear" + SystemStats_Heartbeat = SystemStatsPrefix + "heartbeat" + + NodeStatsPrefix = "/sys/node/" + NodeStats_Clocks = NodeStatsPrefix + "clocks" + NodeStats_Vectors = NodeStatsPrefix + "vectors" + NodeStats_Calls = NodeStatsPrefix + "calls" + NodeStats_Suspends = NodeStatsPrefix + "suspends" + + InterfaceStatsPrefix = "/if/" + InterfaceStats_Drops = InterfaceStatsPrefix + "drops" + InterfaceStats_Punt = InterfaceStatsPrefix + "punt" + InterfaceStats_IP4 = InterfaceStatsPrefix + "ip4" + InterfaceStats_IP6 = InterfaceStatsPrefix + "ip6" + InterfaceStats_RxNoBuf = InterfaceStatsPrefix + "rx-no-buf" + InterfaceStats_RxMiss = InterfaceStatsPrefix + "rx-miss" + InterfaceStats_RxError = InterfaceStatsPrefix + "rx-error" + InterfaceStats_TxError = InterfaceStatsPrefix + "tx-error" + InterfaceStats_Rx = InterfaceStatsPrefix + "rx" + InterfaceStats_RxUnicast = InterfaceStatsPrefix + "rx-unicast" + InterfaceStats_RxMulticast = InterfaceStatsPrefix + "rx-multicast" + InterfaceStats_RxBroadcast = InterfaceStatsPrefix + "rx-broadcast" + InterfaceStats_Tx = InterfaceStatsPrefix + "tx" + InterfaceStats_TxUnicastMiss = InterfaceStatsPrefix + "tx-unicast-miss" + InterfaceStats_TxMulticast = InterfaceStatsPrefix + "tx-multicast" + InterfaceStats_TxBroadcast = InterfaceStatsPrefix + "tx-broadcast" + + NetworkStatsPrefix = "/net/" + NetworkStats_RouteTo = NetworkStatsPrefix + "route/to" + NetworkStats_RouteVia = NetworkStatsPrefix + "route/via" + NetworkStats_MRoute = NetworkStatsPrefix + "mroute" + NetworkStats_Adjacency = NetworkStatsPrefix + "adjacency" +) + +type StatsConnection struct { + statsClient adapter.StatsAPI + + connected uint32 // non-zero if the adapter is connected to VPP +} + +func newStatsConnection(stats adapter.StatsAPI) *StatsConnection { + return &StatsConnection{ + statsClient: stats, + } +} + +// Connect connects to Stats API using specified adapter and returns a connection handle. +// This call blocks until it is either connected, or an error occurs. +// Only one connection attempt will be performed. +func ConnectStats(stats adapter.StatsAPI) (*StatsConnection, error) { + c := newStatsConnection(stats) + + if err := c.connectClient(); err != nil { + return nil, err + } + + return c, nil +} + +func (c *StatsConnection) connectClient() error { + log.Debug("Connecting to stats..") + + if err := c.statsClient.Connect(); err != nil { + return err + } + + log.Debugf("Connected to stats.") + + // store connected state + atomic.StoreUint32(&c.connected, 1) + + return nil +} + +// Disconnect disconnects from Stats API and releases all connection-related resources. +func (c *StatsConnection) Disconnect() { + if c == nil { + return + } + + if c.statsClient != nil { + c.disconnectClient() + } +} + +func (c *StatsConnection) disconnectClient() { + if atomic.CompareAndSwapUint32(&c.connected, 1, 0) { + c.statsClient.Disconnect() + } +} + +// GetSystemStats retrieves VPP system stats. +func (c *StatsConnection) GetSystemStats() (*api.SystemStats, error) { + stats, err := c.statsClient.DumpStats(SystemStatsPrefix) + if err != nil { + return nil, err + } + + sysStats := &api.SystemStats{} + + for _, stat := range stats { + switch stat.Name { + case SystemStats_VectorRate: + sysStats.VectorRate = scalarStatToFloat64(stat.Data) + case SystemStats_InputRate: + sysStats.InputRate = scalarStatToFloat64(stat.Data) + case SystemStats_LastUpdate: + sysStats.LastUpdate = scalarStatToFloat64(stat.Data) + case SystemStats_LastStatsClear: + sysStats.LastStatsClear = scalarStatToFloat64(stat.Data) + case SystemStats_Heartbeat: + sysStats.Heartbeat = scalarStatToFloat64(stat.Data) + } + } + + return sysStats, nil +} + +// GetErrorStats retrieves VPP error stats. +func (c *StatsConnection) GetErrorStats(names ...string) (*api.ErrorStats, error) { + var patterns []string + if len(names) > 0 { + patterns = make([]string, len(names)) + for i, name := range names { + patterns[i] = CounterStatsPrefix + name + } + } else { + // retrieve all error counters by default + patterns = []string{CounterStatsPrefix} + } + stats, err := c.statsClient.DumpStats(patterns...) + if err != nil { + return nil, err + } + + var errorStats = &api.ErrorStats{} + + for _, stat := range stats { + statName := strings.TrimPrefix(stat.Name, CounterStatsPrefix) + + /* TODO: deal with stats that contain '/' in node/counter name + parts := strings.Split(statName, "/") + var nodeName, counterName string + switch len(parts) { + case 2: + nodeName = parts[0] + counterName = parts[1] + case 3: + nodeName = parts[0] + parts[1] + counterName = parts[2] + }*/ + + errorStats.Errors = append(errorStats.Errors, api.ErrorCounter{ + CounterName: statName, + Value: errorStatToUint64(stat.Data), + }) + } + + return errorStats, nil +} + +// GetNodeStats retrieves VPP per node stats. +func (c *StatsConnection) GetNodeStats() (*api.NodeStats, error) { + stats, err := c.statsClient.DumpStats(NodeStatsPrefix) + if err != nil { + return nil, err + } + + nodeStats := &api.NodeStats{} + var setPerNode = func(perNode []uint64, fn func(c *api.NodeCounters, v uint64)) { + if nodeStats.Nodes == nil { + nodeStats.Nodes = make([]api.NodeCounters, len(perNode)) + for i := range perNode { + nodeStats.Nodes[i].NodeIndex = uint32(i) + } + } + for i, v := range perNode { + nodeCounters := nodeStats.Nodes[i] + fn(&nodeCounters, v) + nodeStats.Nodes[i] = nodeCounters + } + } + + for _, stat := range stats { + switch stat.Name { + case NodeStats_Clocks: + setPerNode(reduceSimpleCounterStat(stat.Data), func(c *api.NodeCounters, v uint64) { + c.Clocks = v + }) + case NodeStats_Vectors: + setPerNode(reduceSimpleCounterStat(stat.Data), func(c *api.NodeCounters, v uint64) { + c.Vectors = v + }) + case NodeStats_Calls: + setPerNode(reduceSimpleCounterStat(stat.Data), func(c *api.NodeCounters, v uint64) { + c.Calls = v + }) + case NodeStats_Suspends: + setPerNode(reduceSimpleCounterStat(stat.Data), func(c *api.NodeCounters, v uint64) { + c.Suspends = v + }) + } + } + + return nodeStats, nil +} + +// GetInterfaceStats retrieves VPP per interface stats. +func (c *StatsConnection) GetInterfaceStats() (*api.InterfaceStats, error) { + stats, err := c.statsClient.DumpStats(InterfaceStatsPrefix) + if err != nil { + return nil, err + } + + ifStats := &api.InterfaceStats{} + var setPerIf = func(perIf []uint64, fn func(c *api.InterfaceCounters, v uint64)) { + if ifStats.Interfaces == nil { + ifStats.Interfaces = make([]api.InterfaceCounters, len(perIf)) + for i := range perIf { + ifStats.Interfaces[i].InterfaceIndex = uint32(i) + } + } + for i, v := range perIf { + ifCounters := ifStats.Interfaces[i] + fn(&ifCounters, v) + ifStats.Interfaces[i] = ifCounters + } + } + + for _, stat := range stats { + switch stat.Name { + case InterfaceStats_Drops: + setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) { + c.Drops = v + }) + case InterfaceStats_Punt: + setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) { + c.Punts = v + }) + case InterfaceStats_IP4: + setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) { + c.IP4 = v + }) + case InterfaceStats_IP6: + setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) { + c.IP6 = v + }) + case InterfaceStats_RxNoBuf: + setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) { + c.RxNoBuf = v + }) + case InterfaceStats_RxMiss: + setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) { + c.RxMiss = v + }) + case InterfaceStats_RxError: + setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) { + c.RxErrors = v + }) + case InterfaceStats_TxError: + setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) { + c.TxErrors = v + }) + case InterfaceStats_Rx: + per := reduceCombinedCounterStat(stat.Data) + setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) { + c.RxPackets = v + }) + setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) { + c.RxBytes = v + }) + case InterfaceStats_RxUnicast: + per := reduceCombinedCounterStat(stat.Data) + setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) { + c.RxUnicast[0] = v + }) + setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) { + c.RxUnicast[1] = v + }) + case InterfaceStats_RxMulticast: + per := reduceCombinedCounterStat(stat.Data) + setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) { + c.RxMulticast[0] = v + }) + setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) { + c.RxMulticast[1] = v + }) + case InterfaceStats_RxBroadcast: + per := reduceCombinedCounterStat(stat.Data) + setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) { + c.RxBroadcast[0] = v + }) + setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) { + c.RxBroadcast[1] = v + }) + case InterfaceStats_Tx: + per := reduceCombinedCounterStat(stat.Data) + setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) { + c.TxPackets = v + }) + setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) { + c.TxBytes = v + }) + case InterfaceStats_TxUnicastMiss: + per := reduceCombinedCounterStat(stat.Data) + setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) { + c.TxUnicastMiss[0] = v + }) + setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) { + c.TxUnicastMiss[1] = v + }) + case InterfaceStats_TxMulticast: + per := reduceCombinedCounterStat(stat.Data) + setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) { + c.TxMulticast[0] = v + }) + setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) { + c.TxMulticast[1] = v + }) + case InterfaceStats_TxBroadcast: + per := reduceCombinedCounterStat(stat.Data) + setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) { + c.TxBroadcast[0] = v + }) + setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) { + c.TxBroadcast[1] = v + }) + } + } + + return ifStats, nil +} + +func scalarStatToFloat64(stat adapter.Stat) float64 { + if s, ok := stat.(adapter.ScalarStat); ok { + return float64(s) + } + return 0 +} + +func errorStatToUint64(stat adapter.Stat) uint64 { + if s, ok := stat.(adapter.ErrorStat); ok { + return uint64(s) + } + return 0 +} + +func reduceSimpleCounterStat(stat adapter.Stat) []uint64 { + if s, ok := stat.(adapter.SimpleCounterStat); ok { + if len(s) == 0 { + return []uint64{} + } + var per = make([]uint64, len(s[0])) + for _, w := range s { + for i, n := range w { + per[i] += uint64(n) + } + } + return per + } + return nil +} + +func reduceCombinedCounterStat(stat adapter.Stat) [2][]uint64 { + if s, ok := stat.(adapter.CombinedCounterStat); ok { + if len(s) == 0 { + return [2][]uint64{{}, {}} + } + var perPackets = make([]uint64, len(s[0])) + var perBytes = make([]uint64, len(s[0])) + for _, w := range s { + for i, n := range w { + perPackets[i] += uint64(n.Packets) + perBytes[i] += uint64(n.Bytes) + } + } + return [2][]uint64{perPackets, perBytes} + } + return [2][]uint64{} +} diff --git a/examples/stats-api/README.md b/examples/stats-api/README.md index 0f26197..77afad8 100644 --- a/examples/stats-api/README.md +++ b/examples/stats-api/README.md @@ -12,18 +12,96 @@ The following requirements are required to run this example: ```sh statseg { default + per-node-counters on } - ``` + ``` > The [default socket](https://wiki.fd.io/view/VPP/Command-line_Arguments#.22statseg.22_parameters) is located at `/run/vpp/stats.sock`. -- run the VPP, ideally with some traffic +- run the VPP (ideally with some traffic) ## Running example -First build the example: `go build git.fd.io/govpp.git/examples/stats-api`. +First build the example: `go build git.fd.io/govpp.git/examples/stats-api`. -Use commands `ls` and `dump` to list and dump statistics. Optionally, patterns can be used to filter the results. +### Higher-level access to stats -### List stats matching patterns `/sys/` and `/if/` +Use commands following commands to retrieve stats that are aggregated and +processed into logical structures from [api package](../../api). + +- `system` to retrieve system statistics +- `nodes` to retrieve per node statistics +- `interfaces` to retrieve per interface statistics +- `errors` to retrieve error statistics (you can use patterns to filter the errors) + +#### System stats + +Following command will retrieve system stats. +``` +$ ./stats-api system +System stats: &{VectorRate:0 InputRate:0 LastUpdate:32560 LastStatsClear:0 Heartbeat:3255} +``` + +#### Node stats + +Following command will retrieve per node stats. +``` +$ ./stats-api nodes +Listing node stats.. +... + - {NodeIndex:554 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:555 Clocks:189609 Vectors:15 Calls:15 Suspends:0} + - {NodeIndex:556 Clocks:2281847 Vectors:0 Calls:0 Suspends:21} + - {NodeIndex:557 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:558 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:559 Clocks:7094 Vectors:0 Calls:1 Suspends:1} + - {NodeIndex:560 Clocks:88159323916601 Vectors:0 Calls:14066116 Suspends:0} + - {NodeIndex:561 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:562 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:563 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:564 Clocks:447894125 Vectors:0 Calls:0 Suspends:32395} + - {NodeIndex:565 Clocks:1099655497824612 Vectors:0 Calls:40 Suspends:117} + - {NodeIndex:566 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:567 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:568 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:569 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:570 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:571 Clocks:0 Vectors:0 Calls:0 Suspends:0} + - {NodeIndex:572 Clocks:0 Vectors:0 Calls:0 Suspends:0} +Listed 573 node counters +``` + +#### Interface stats + +Following command will retrieve per interface stats. +``` +$ ./stats-api interfaces +Listing interface stats.. + - {InterfaceIndex:0 RxPackets:0 RxBytes:0 RxErrors:0 TxPackets:0 TxBytes:0 TxErrors:0 RxUnicast:[0 0] RxMulticast:[0 0] RxBroadcast:[0 0] TxUnicastMiss:[0 0] TxMulticast:[0 0] TxBroadcast:[0 0] Drops:0 Punts:0 IP4:0 IP6:0 RxNoBuf:0 RxMiss:0} + - {InterfaceIndex:1 RxPackets:0 RxBytes:0 RxErrors:0 TxPackets:0 TxBytes:0 TxErrors:0 RxUnicast:[0 0] RxMulticast:[0 0] RxBroadcast:[0 0] TxUnicastMiss:[0 0] TxMulticast:[0 0] TxBroadcast:[0 0] Drops:5 Punts:0 IP4:0 IP6:0 RxNoBuf:0 RxMiss:0} + - {InterfaceIndex:2 RxPackets:0 RxBytes:0 RxErrors:0 TxPackets:0 TxBytes:0 TxErrors:0 RxUnicast:[0 0] RxMulticast:[0 0] RxBroadcast:[0 0] TxUnicastMiss:[0 0] TxMulticast:[0 0] TxBroadcast:[0 0] Drops:0 Punts:0 IP4:0 IP6:0 RxNoBuf:0 RxMiss:0} + - {InterfaceIndex:3 RxPackets:0 RxBytes:0 RxErrors:0 TxPackets:0 TxBytes:0 TxErrors:0 RxUnicast:[0 0] RxMulticast:[0 0] RxBroadcast:[0 0] TxUnicastMiss:[0 0] TxMulticast:[0 0] TxBroadcast:[0 0] Drops:0 Punts:0 IP4:0 IP6:0 RxNoBuf:0 RxMiss:0} +Listed 4 interface counters +``` + +#### Error stats + +Following command will retrieve error stats. +Use flag `-all` to include stats with zero values. +``` +$ ./stats-api errors ip +Listing error stats.. ip + - {ip4-input/ip4 spoofed local-address packet drops 15} +Listed 1 (825) error counters +``` + +### Low-level access to stats + +Use commands `ls` and `dump` to list and dump statistics in their raw format +from [adapter package](../../adapter). +Optionally, patterns can be used to filter the results. + +#### List stats + +Following command will list stats matching patterns `/sys/` and `/if/`. ``` $ ./stats-api ls /sys/ /if/ Listing stats.. /sys/ /if/ @@ -55,7 +133,10 @@ Listing stats.. /sys/ /if/ Listed 25 stats ``` -### Dump all stats with their types and values +#### Dump stats + +Following command will dump stats with their types and actual values. +Use flag `-all` to include stats with zero values. ``` $ ./stats-api dump Dumping stats.. diff --git a/examples/stats-api/stats_api.go b/examples/stats-api/stats_api.go index 6fd46d2..b905f60 100644 --- a/examples/stats-api/stats_api.go +++ b/examples/stats-api/stats_api.go @@ -23,6 +23,7 @@ import ( "git.fd.io/govpp.git/adapter" "git.fd.io/govpp.git/adapter/vppapiclient" + "git.fd.io/govpp.git/core" ) // ------------------------------------------------------------------ @@ -39,7 +40,7 @@ var ( func init() { flag.Usage = func() { - fmt.Fprintf(os.Stderr, "%s: usage [ls|dump] ...\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "%s: usage [ls|dump|errors|interfaces|nodes|system] ...\n", os.Args[0]) flag.PrintDefaults() os.Exit(1) } @@ -47,11 +48,11 @@ func init() { func main() { flag.Parse() + skipZeros := !*dumpAll cmd := flag.Arg(0) - switch cmd { - case "", "ls", "dump": + case "", "ls", "dump", "errors", "interfaces", "nodes", "system": default: flag.Usage() } @@ -65,14 +66,56 @@ func main() { fmt.Printf("Connecting to stats socket: %s\n", *statsSocket) - if err := client.Connect(); err != nil { + c, err := core.ConnectStats(client) + if err != nil { log.Fatalln("Connecting failed:", err) } - defer client.Disconnect() + defer c.Disconnect() switch cmd { + case "system": + stats, err := c.GetSystemStats() + if err != nil { + log.Fatalln("getting system stats failed:", err) + } + fmt.Printf("System stats: %+v\n", stats) + case "nodes": + fmt.Println("Listing node stats..") + stats, err := c.GetNodeStats() + if err != nil { + log.Fatalln("getting node stats failed:", err) + } + for _, iface := range stats.Nodes { + fmt.Printf(" - %+v\n", iface) + } + fmt.Printf("Listed %d node counters\n", len(stats.Nodes)) + case "interfaces": + fmt.Println("Listing interface stats..") + stats, err := c.GetInterfaceStats() + if err != nil { + log.Fatalln("getting interface stats failed:", err) + } + for _, iface := range stats.Interfaces { + fmt.Printf(" - %+v\n", iface) + } + fmt.Printf("Listed %d interface counters\n", len(stats.Interfaces)) + case "errors": + fmt.Printf("Listing error stats.. %s\n", strings.Join(patterns, " ")) + stats, err := c.GetErrorStats(patterns...) + if err != nil { + log.Fatalln("getting error stats failed:", err) + } + n := 0 + for _, counter := range stats.Errors { + if counter.Value == 0 && skipZeros { + continue + } + fmt.Printf(" - %v\n", counter) + n++ + } + fmt.Printf("Listed %d (%d) error counters\n", n, len(stats.Errors)) case "dump": - dumpStats(client, patterns, !*dumpAll) + dumpStats(client, patterns, skipZeros) default: listStats(client, patterns) } -- 2.16.6