8 "git.fd.io/govpp.git/adapter"
9 "git.fd.io/govpp.git/api"
14 RetryUpdateDelay = time.Millisecond * 10
15 HealthCheckInterval = time.Second // default health check probe interval
19 SystemStatsPrefix = "/sys/"
20 SystemStats_VectorRate = SystemStatsPrefix + "vector_rate"
21 SystemStats_NumWorkerThreads = SystemStatsPrefix + "num_worker_threads"
22 SystemStats_VectorRatePerWorker = SystemStatsPrefix + "vector_rate_per_worker"
23 SystemStats_InputRate = SystemStatsPrefix + "input_rate"
24 SystemStats_LastUpdate = SystemStatsPrefix + "last_update"
25 SystemStats_LastStatsClear = SystemStatsPrefix + "last_stats_clear"
26 SystemStats_Heartbeat = SystemStatsPrefix + "heartbeat"
28 NodeStatsPrefix = "/sys/node/"
29 NodeStats_Names = NodeStatsPrefix + "names"
30 NodeStats_Clocks = NodeStatsPrefix + "clocks"
31 NodeStats_Vectors = NodeStatsPrefix + "vectors"
32 NodeStats_Calls = NodeStatsPrefix + "calls"
33 NodeStats_Suspends = NodeStatsPrefix + "suspends"
35 BufferStatsPrefix = "/buffer-pools/"
36 BufferStats_Cached = "cached"
37 BufferStats_Used = "used"
38 BufferStats_Available = "available"
40 CounterStatsPrefix = "/err/"
42 MemoryStatPrefix = "/mem/statseg"
43 MemoryStats_Total = "total"
44 MemoryStats_Used = "used"
46 InterfaceStatsPrefix = "/if/"
47 InterfaceStats_Names = InterfaceStatsPrefix + "names"
48 InterfaceStats_Drops = InterfaceStatsPrefix + "drops"
49 InterfaceStats_Punt = InterfaceStatsPrefix + "punt"
50 InterfaceStats_IP4 = InterfaceStatsPrefix + "ip4"
51 InterfaceStats_IP6 = InterfaceStatsPrefix + "ip6"
52 InterfaceStats_RxNoBuf = InterfaceStatsPrefix + "rx-no-buf"
53 InterfaceStats_RxMiss = InterfaceStatsPrefix + "rx-miss"
54 InterfaceStats_RxError = InterfaceStatsPrefix + "rx-error"
55 InterfaceStats_TxError = InterfaceStatsPrefix + "tx-error"
56 InterfaceStats_Mpls = InterfaceStatsPrefix + "mpls"
57 InterfaceStats_Rx = InterfaceStatsPrefix + "rx"
58 InterfaceStats_RxUnicast = InterfaceStatsPrefix + "rx-unicast"
59 InterfaceStats_RxMulticast = InterfaceStatsPrefix + "rx-multicast"
60 InterfaceStats_RxBroadcast = InterfaceStatsPrefix + "rx-broadcast"
61 InterfaceStats_Tx = InterfaceStatsPrefix + "tx"
62 InterfaceStats_TxUnicast = InterfaceStatsPrefix + "tx-unicast"
63 InterfaceStats_TxUnicastMiss = InterfaceStatsPrefix + "tx-unicast-miss"
64 InterfaceStats_TxMulticast = InterfaceStatsPrefix + "tx-multicast"
65 InterfaceStats_TxBroadcast = InterfaceStatsPrefix + "tx-broadcast"
67 // TODO: network stats
68 NetworkStatsPrefix = "/net/"
69 NetworkStats_RouteTo = NetworkStatsPrefix + "route/to"
70 NetworkStats_RouteVia = NetworkStatsPrefix + "route/via"
71 NetworkStats_MRoute = NetworkStatsPrefix + "mroute"
72 NetworkStats_Adjacency = NetworkStatsPrefix + "adjacency"
73 NetworkStats_Punt = NetworkStatsPrefix + "punt"
76 type StatsConnection struct {
77 statsClient adapter.StatsAPI
79 maxAttempts int // interval for reconnect attempts
80 recInterval time.Duration // maximum number of reconnect attempts
82 connChan chan ConnectionEvent // connection event channel
83 done chan struct{} // to terminate stats connection watcher
85 errorStatsData *adapter.StatDir
86 nodeStatsData *adapter.StatDir
87 ifaceStatsData *adapter.StatDir
88 sysStatsData *adapter.StatDir
89 bufStatsData *adapter.StatDir
90 memStatsData *adapter.StatDir
93 func newStatsConnection(stats adapter.StatsAPI, attempts int, interval time.Duration) *StatsConnection {
95 attempts = DefaultMaxReconnectAttempts
98 interval = DefaultReconnectInterval
101 return &StatsConnection{
103 maxAttempts: attempts,
104 recInterval: interval,
105 connChan: make(chan ConnectionEvent, NotificationChanBufSize),
106 done: make(chan struct{}),
110 // ConnectStats connects to Stats API using specified adapter and returns a connection handle.
111 // This call blocks until it is either connected, or an error occurs.
112 // Only one connection attempt will be performed.
113 func ConnectStats(stats adapter.StatsAPI) (*StatsConnection, error) {
114 log.Debug("Connecting to stats..")
115 c := newStatsConnection(stats, DefaultMaxReconnectAttempts, DefaultReconnectInterval)
117 if err := c.statsClient.Connect(); err != nil {
120 log.Debugf("Connected to stats.")
125 // AsyncConnectStats connects to the VPP stats socket asynchronously and returns the connection
126 // handle with state channel. The call is non-blocking and the caller is expected to watch ConnectionEvent
127 // values from the channel and wait for connect/disconnect events. Connection loop tries to reconnect the
128 // socket in case the session was disconnected.
129 func AsyncConnectStats(stats adapter.StatsAPI, attempts int, interval time.Duration) (*StatsConnection, chan ConnectionEvent, error) {
130 log.Debug("Connecting to stats asynchronously..")
131 c := newStatsConnection(stats, attempts, interval)
135 return c, c.connChan, nil
138 func (c *StatsConnection) connectLoop() {
139 log.Debug("Asynchronously connecting to stats..")
140 var reconnectAttempts int
142 // loop until connected
144 if err := c.statsClient.Connect(); err == nil {
145 c.sendStatsConnEvent(ConnectionEvent{Timestamp: time.Now(), State: Connected})
147 } else if reconnectAttempts < c.maxAttempts {
149 log.Warnf("connecting stats failed (attempt %d/%d): %v", reconnectAttempts, c.maxAttempts, err)
150 time.Sleep(c.recInterval)
152 c.sendStatsConnEvent(ConnectionEvent{Timestamp: time.Now(), State: Failed, Error: err})
156 // start monitoring stats connection state
160 // Disconnect disconnects from Stats API and releases all connection-related resources.
161 func (c *StatsConnection) Disconnect() {
165 if c.statsClient != nil {
166 if err := c.statsClient.Disconnect(); err != nil {
167 log.Debugf("disconnecting stats client failed: %v", err)
174 func (c *StatsConnection) monitorSocket() {
175 var state, lastState ConnectionState
176 ticker := time.NewTicker(HealthCheckInterval)
181 _, err := c.statsClient.ListStats(SystemStats_Heartbeat)
183 if err == adapter.ErrStatsDataBusy {
184 state = NotResponding
186 if err == adapter.ErrStatsDisconnected {
189 if err == adapter.ErrStatsAccessFailed {
192 if state == lastState {
196 c.sendStatsConnEvent(ConnectionEvent{Timestamp: time.Now(), State: state, Error: err})
198 log.Debugf("health check watcher closed")
199 c.sendStatsConnEvent(ConnectionEvent{Timestamp: time.Now(), State: Disconnected, Error: nil})
205 func (c *StatsConnection) updateStats(statDir **adapter.StatDir, patterns ...string) error {
207 panic("statDir must not nil")
209 try := func() error {
210 if (*statDir) == nil {
211 dir, err := c.statsClient.PrepareDir(patterns...)
213 log.Debugln("preparing dir failed:", err)
218 if err := c.statsClient.UpdateDir(*statDir); err != nil {
219 log.Debugln("updating dir failed:", err)
228 for r := 0; r < RetryUpdateCount; r++ {
229 if err = try(); err == nil {
231 log.Debugf("retry successfull (r=%d)", r)
234 } else if err == adapter.ErrStatsDirStale || err == adapter.ErrStatsDataBusy {
237 log.Debugf("sleeping for %v before next try", RetryUpdateDelay)
238 time.Sleep(RetryUpdateDelay)
241 // error is not retryable
248 // GetSystemStats retrieves VPP system stats.
249 func (c *StatsConnection) GetSystemStats(sysStats *api.SystemStats) (err error) {
250 if err := c.updateStats(&c.sysStatsData, SystemStatsPrefix); err != nil {
254 for _, stat := range c.sysStatsData.Entries {
256 if s, ok := stat.Data.(adapter.ScalarStat); ok {
259 switch string(stat.Name) {
260 case SystemStats_VectorRate:
261 sysStats.VectorRate = val
262 case SystemStats_NumWorkerThreads:
263 sysStats.NumWorkerThreads = val
264 case SystemStats_VectorRatePerWorker:
266 if ss, ok := stat.Data.(adapter.SimpleCounterStat); ok {
267 vals = make([]uint64, len(ss))
272 vals[w] = uint64(ss[w][0])
275 sysStats.VectorRatePerWorker = vals
276 case SystemStats_InputRate:
277 sysStats.InputRate = val
278 case SystemStats_LastUpdate:
279 sysStats.LastUpdate = val
280 case SystemStats_LastStatsClear:
281 sysStats.LastStatsClear = val
282 case SystemStats_Heartbeat:
283 sysStats.Heartbeat = val
290 // GetErrorStats retrieves VPP error stats.
291 func (c *StatsConnection) GetErrorStats(errorStats *api.ErrorStats) (err error) {
292 if err := c.updateStats(&c.errorStatsData, CounterStatsPrefix); err != nil {
296 if errorStats.Errors == nil || len(errorStats.Errors) != len(c.errorStatsData.Entries) {
297 errorStats.Errors = make([]api.ErrorCounter, len(c.errorStatsData.Entries))
298 for i := 0; i < len(c.errorStatsData.Entries); i++ {
299 errorStats.Errors[i].CounterName = string(c.errorStatsData.Entries[i].Name)
303 for i, stat := range c.errorStatsData.Entries {
304 if stat.Type != adapter.ErrorIndex {
307 if errStat, ok := stat.Data.(adapter.ErrorStat); ok {
308 values := make([]uint64, len(errStat))
309 for j, errStatW := range errStat {
310 values[j] = uint64(errStatW)
312 errorStats.Errors[i].Values = values
319 func (c *StatsConnection) GetNodeStats(nodeStats *api.NodeStats) (err error) {
320 if err := c.updateStats(&c.nodeStatsData, NodeStatsPrefix); err != nil {
324 prepNodes := func(l int) {
325 if nodeStats.Nodes == nil || len(nodeStats.Nodes) != l {
326 nodeStats.Nodes = make([]api.NodeCounters, l)
327 for i := 0; i < l; i++ {
328 nodeStats.Nodes[i].NodeIndex = uint32(i)
332 perNode := func(stat adapter.StatEntry, fn func(*api.NodeCounters, uint64)) {
333 if s, ok := stat.Data.(adapter.SimpleCounterStat); ok {
335 for i := range nodeStats.Nodes {
336 val := adapter.ReduceSimpleCounterStatIndex(s, i)
337 fn(&nodeStats.Nodes[i], val)
342 for _, stat := range c.nodeStatsData.Entries {
343 switch string(stat.Name) {
344 case NodeStats_Names:
345 if stat, ok := stat.Data.(adapter.NameStat); ok {
347 for i, nc := range nodeStats.Nodes {
348 if nc.NodeName != string(stat[i]) {
349 nc.NodeName = string(stat[i])
350 nodeStats.Nodes[i] = nc
354 case NodeStats_Clocks:
355 perNode(stat, func(node *api.NodeCounters, val uint64) {
358 case NodeStats_Vectors:
359 perNode(stat, func(node *api.NodeCounters, val uint64) {
362 case NodeStats_Calls:
363 perNode(stat, func(node *api.NodeCounters, val uint64) {
366 case NodeStats_Suspends:
367 perNode(stat, func(node *api.NodeCounters, val uint64) {
376 // GetInterfaceStats retrieves VPP per interface stats.
377 func (c *StatsConnection) GetInterfaceStats(ifaceStats *api.InterfaceStats) (err error) {
378 if err := c.updateStats(&c.ifaceStatsData, InterfaceStatsPrefix); err != nil {
382 prep := func(l int) {
383 if ifaceStats.Interfaces == nil || len(ifaceStats.Interfaces) != l {
384 ifaceStats.Interfaces = make([]api.InterfaceCounters, l)
385 for i := 0; i < l; i++ {
386 ifaceStats.Interfaces[i].InterfaceIndex = uint32(i)
390 perNode := func(stat adapter.StatEntry, fn func(*api.InterfaceCounters, uint64)) {
391 if s, ok := stat.Data.(adapter.SimpleCounterStat); ok {
393 for i := range ifaceStats.Interfaces {
394 val := adapter.ReduceSimpleCounterStatIndex(s, i)
395 fn(&ifaceStats.Interfaces[i], val)
399 perNodeComb := func(stat adapter.StatEntry, fn func(*api.InterfaceCounters, [2]uint64)) {
400 if s, ok := stat.Data.(adapter.CombinedCounterStat); ok {
402 for i := range ifaceStats.Interfaces {
403 val := adapter.ReduceCombinedCounterStatIndex(s, i)
404 fn(&ifaceStats.Interfaces[i], val)
409 for _, stat := range c.ifaceStatsData.Entries {
410 switch string(stat.Name) {
411 case InterfaceStats_Names:
412 if stat, ok := stat.Data.(adapter.NameStat); ok {
414 for i, nc := range ifaceStats.Interfaces {
415 if nc.InterfaceName != string(stat[i]) {
416 nc.InterfaceName = string(stat[i])
417 ifaceStats.Interfaces[i] = nc
421 case InterfaceStats_Drops:
422 perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
425 case InterfaceStats_Punt:
426 perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
429 case InterfaceStats_IP4:
430 perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
433 case InterfaceStats_IP6:
434 perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
437 case InterfaceStats_RxNoBuf:
438 perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
441 case InterfaceStats_RxMiss:
442 perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
445 case InterfaceStats_RxError:
446 perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
449 case InterfaceStats_TxError:
450 perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
453 case InterfaceStats_Mpls:
454 perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
457 case InterfaceStats_Rx:
458 perNodeComb(stat, func(iface *api.InterfaceCounters, val [2]uint64) {
459 iface.Rx.Packets = val[0]
460 iface.Rx.Bytes = val[1]
462 case InterfaceStats_RxUnicast:
463 perNodeComb(stat, func(iface *api.InterfaceCounters, val [2]uint64) {
464 iface.RxUnicast.Packets = val[0]
465 iface.RxUnicast.Bytes = val[1]
467 case InterfaceStats_RxMulticast:
468 perNodeComb(stat, func(iface *api.InterfaceCounters, val [2]uint64) {
469 iface.RxMulticast.Packets = val[0]
470 iface.RxMulticast.Bytes = val[1]
472 case InterfaceStats_RxBroadcast:
473 perNodeComb(stat, func(iface *api.InterfaceCounters, val [2]uint64) {
474 iface.RxBroadcast.Packets = val[0]
475 iface.RxBroadcast.Bytes = val[1]
477 case InterfaceStats_Tx:
478 perNodeComb(stat, func(iface *api.InterfaceCounters, val [2]uint64) {
479 iface.Tx.Packets = val[0]
480 iface.Tx.Bytes = val[1]
482 case InterfaceStats_TxUnicastMiss:
483 // tx-unicast-miss was a spelling mistake in older versions
486 case InterfaceStats_TxUnicast:
487 perNodeComb(stat, func(iface *api.InterfaceCounters, val [2]uint64) {
488 iface.TxUnicast.Packets = val[0]
489 iface.TxUnicast.Bytes = val[1]
491 case InterfaceStats_TxMulticast:
492 perNodeComb(stat, func(iface *api.InterfaceCounters, val [2]uint64) {
493 iface.TxMulticast.Packets = val[0]
494 iface.TxMulticast.Bytes = val[1]
496 case InterfaceStats_TxBroadcast:
497 perNodeComb(stat, func(iface *api.InterfaceCounters, val [2]uint64) {
498 iface.TxBroadcast.Packets = val[0]
499 iface.TxBroadcast.Bytes = val[1]
507 // GetBufferStats retrieves VPP buffer pools stats.
508 func (c *StatsConnection) GetBufferStats(bufStats *api.BufferStats) (err error) {
509 if err := c.updateStats(&c.bufStatsData, BufferStatsPrefix); err != nil {
513 if bufStats.Buffer == nil {
514 bufStats.Buffer = make(map[string]api.BufferPool)
517 for _, stat := range c.bufStatsData.Entries {
518 d, f := path.Split(string(stat.Name))
519 d = strings.TrimSuffix(d, "/")
521 name := strings.TrimPrefix(d, BufferStatsPrefix)
522 b, ok := bufStats.Buffer[name]
528 s, ok := stat.Data.(adapter.ScalarStat)
533 case BufferStats_Cached:
535 case BufferStats_Used:
537 case BufferStats_Available:
541 bufStats.Buffer[name] = b
547 func (c *StatsConnection) GetMemoryStats(memStats *api.MemoryStats) (err error) {
548 if err := c.updateStats(&c.memStatsData, MemoryStatPrefix); err != nil {
552 for _, stat := range c.memStatsData.Entries {
553 _, f := path.Split(string(stat.Name))
555 m, ok := stat.Data.(adapter.ScalarStat)
560 case MemoryStats_Total:
562 case MemoryStats_Used:
569 func (c *StatsConnection) sendStatsConnEvent(event ConnectionEvent) {
571 case c.connChan <- event:
573 log.Warn("Stats connection state channel is full, discarding value.")