Introduce higer-level API for retrieving statistics 79/17679/2
authorOndrej Fabry <ofabry@cisco.com>
Tue, 19 Feb 2019 12:57:12 +0000 (13:57 +0100)
committerOndrej Fabry <ofabry@cisco.com>
Tue, 19 Feb 2019 13:02:45 +0000 (14:02 +0100)
- see stats-api example

Change-Id: I11d29d32b60d25238e75cb6b86ee34842348ab38
Signed-off-by: Ondrej Fabry <ofabry@cisco.com>
adapter/stats_api.go
api/binapi.go [moved from api/api.go with 100% similarity]
api/stats.go [new file with mode: 0644]
core/connection.go
core/request_handler.go
core/stats.go [new file with mode: 0644]
examples/stats-api/README.md
examples/stats-api/stats_api.go

index 4a3f130..3538176 100644 (file)
 
 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.
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 (file)
index 0000000..ec623c7
--- /dev/null
@@ -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)
+}
index 14b0af4..a21cc28 100644 (file)
@@ -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     =                      // 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
index dc90747..55a825a 100644 (file)
@@ -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 (file)
index 0000000..26b9bc9
--- /dev/null
@@ -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{}
+}
index 0f26197..77afad8 100644 (file)
@@ -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..
index 6fd46d2..b905f60 100644 (file)
@@ -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] <patterns>...\n", os.Args[0])
+               fmt.Fprintf(os.Stderr, "%s: usage [ls|dump|errors|interfaces|nodes|system] <patterns>...\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)
        }