cd93cc17736bb700f5dd20414459bd0de04ed212
[govpp.git] / core / stats.go
1 package core
2
3 import (
4         "path"
5         "strings"
6         "sync/atomic"
7         "time"
8
9         "git.fd.io/govpp.git/adapter"
10         "git.fd.io/govpp.git/api"
11 )
12
13 var (
14         RetryUpdateCount = 10
15         RetryUpdateDelay = time.Millisecond * 10
16 )
17
18 const (
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"
27
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"
34
35         BufferStatsPrefix     = "/buffer-pools/"
36         BufferStats_Cached    = "cached"
37         BufferStats_Used      = "used"
38         BufferStats_Available = "available"
39
40         CounterStatsPrefix = "/err/"
41
42         InterfaceStatsPrefix         = "/if/"
43         InterfaceStats_Names         = InterfaceStatsPrefix + "names"
44         InterfaceStats_Drops         = InterfaceStatsPrefix + "drops"
45         InterfaceStats_Punt          = InterfaceStatsPrefix + "punt"
46         InterfaceStats_IP4           = InterfaceStatsPrefix + "ip4"
47         InterfaceStats_IP6           = InterfaceStatsPrefix + "ip6"
48         InterfaceStats_RxNoBuf       = InterfaceStatsPrefix + "rx-no-buf"
49         InterfaceStats_RxMiss        = InterfaceStatsPrefix + "rx-miss"
50         InterfaceStats_RxError       = InterfaceStatsPrefix + "rx-error"
51         InterfaceStats_TxError       = InterfaceStatsPrefix + "tx-error"
52         InterfaceStats_Mpls          = InterfaceStatsPrefix + "mpls"
53         InterfaceStats_Rx            = InterfaceStatsPrefix + "rx"
54         InterfaceStats_RxUnicast     = InterfaceStatsPrefix + "rx-unicast"
55         InterfaceStats_RxMulticast   = InterfaceStatsPrefix + "rx-multicast"
56         InterfaceStats_RxBroadcast   = InterfaceStatsPrefix + "rx-broadcast"
57         InterfaceStats_Tx            = InterfaceStatsPrefix + "tx"
58         InterfaceStats_TxUnicast     = InterfaceStatsPrefix + "tx-unicast"
59         InterfaceStats_TxUnicastMiss = InterfaceStatsPrefix + "tx-unicast-miss"
60         InterfaceStats_TxMulticast   = InterfaceStatsPrefix + "tx-multicast"
61         InterfaceStats_TxBroadcast   = InterfaceStatsPrefix + "tx-broadcast"
62
63         // TODO: network stats
64         NetworkStatsPrefix     = "/net/"
65         NetworkStats_RouteTo   = NetworkStatsPrefix + "route/to"
66         NetworkStats_RouteVia  = NetworkStatsPrefix + "route/via"
67         NetworkStats_MRoute    = NetworkStatsPrefix + "mroute"
68         NetworkStats_Adjacency = NetworkStatsPrefix + "adjacency"
69         NetworkStats_Punt      = NetworkStatsPrefix + "punt"
70 )
71
72 type StatsConnection struct {
73         statsClient adapter.StatsAPI
74
75         // connected is true if the adapter is connected to VPP
76         connected uint32
77
78         errorStatsData *adapter.StatDir
79         nodeStatsData  *adapter.StatDir
80         ifaceStatsData *adapter.StatDir
81         sysStatsData   *adapter.StatDir
82         bufStatsData   *adapter.StatDir
83 }
84
85 func newStatsConnection(stats adapter.StatsAPI) *StatsConnection {
86         return &StatsConnection{
87                 statsClient: stats,
88         }
89 }
90
91 // ConnectStats connects to Stats API using specified adapter and returns a connection handle.
92 // This call blocks until it is either connected, or an error occurs.
93 // Only one connection attempt will be performed.
94 func ConnectStats(stats adapter.StatsAPI) (*StatsConnection, error) {
95         c := newStatsConnection(stats)
96
97         if err := c.connectClient(); err != nil {
98                 return nil, err
99         }
100
101         return c, nil
102 }
103
104 func (c *StatsConnection) connectClient() error {
105         log.Debug("Connecting to stats..")
106
107         if err := c.statsClient.Connect(); err != nil {
108                 return err
109         }
110
111         log.Debugf("Connected to stats.")
112
113         // store connected state
114         atomic.StoreUint32(&c.connected, 1)
115
116         return nil
117 }
118
119 // Disconnect disconnects from Stats API and releases all connection-related resources.
120 func (c *StatsConnection) Disconnect() {
121         if c == nil {
122                 return
123         }
124         if c.statsClient != nil {
125                 c.disconnectClient()
126         }
127 }
128
129 func (c *StatsConnection) disconnectClient() {
130         if atomic.CompareAndSwapUint32(&c.connected, 1, 0) {
131                 if err := c.statsClient.Disconnect(); err != nil {
132                         log.Debugf("disconnecting stats client failed: %v", err)
133                 }
134         }
135 }
136
137 func (c *StatsConnection) updateStats(statDir **adapter.StatDir, patterns ...string) error {
138         if statDir == nil {
139                 panic("statDir must not nil")
140         }
141         try := func() error {
142                 if (*statDir) == nil {
143                         dir, err := c.statsClient.PrepareDir(patterns...)
144                         if err != nil {
145                                 log.Debugln("preparing dir failed:", err)
146                                 return err
147                         }
148                         *statDir = dir
149                 } else {
150                         if err := c.statsClient.UpdateDir(*statDir); err != nil {
151                                 log.Debugln("updating dir failed:", err)
152                                 *statDir = nil
153                                 return err
154                         }
155                 }
156
157                 return nil
158         }
159         var err error
160         for r := 0; r < RetryUpdateCount; r++ {
161                 if err = try(); err == nil {
162                         if r > 0 {
163                                 log.Debugf("retry successfull (r=%d)", r)
164                         }
165                         return nil
166                 } else if err == adapter.ErrStatsDirStale || err == adapter.ErrStatsDataBusy {
167                         // retrying
168                         if r > 1 {
169                                 log.Debugf("sleeping for %v before next try", RetryUpdateDelay)
170                                 time.Sleep(RetryUpdateDelay)
171                         }
172                 } else {
173                         // error is not retryable
174                         break
175                 }
176         }
177         return err
178 }
179
180 // GetSystemStats retrieves VPP system stats.
181 func (c *StatsConnection) GetSystemStats(sysStats *api.SystemStats) (err error) {
182         if err := c.updateStats(&c.sysStatsData, SystemStatsPrefix); err != nil {
183                 return err
184         }
185
186         for _, stat := range c.sysStatsData.Entries {
187                 var val uint64
188                 if s, ok := stat.Data.(adapter.ScalarStat); ok {
189                         val = uint64(s)
190                 }
191                 switch string(stat.Name) {
192                 case SystemStats_VectorRate:
193                         sysStats.VectorRate = val
194                 case SystemStats_NumWorkerThreads:
195                         sysStats.NumWorkerThreads = val
196                 case SystemStats_VectorRatePerWorker:
197                         var vals []uint64
198                         if ss, ok := stat.Data.(adapter.SimpleCounterStat); ok {
199                                 vals = make([]uint64, len(ss))
200                                 for w := range ss {
201                                         if ss[w] == nil {
202                                                 continue
203                                         }
204                                         vals[w] = uint64(ss[w][0])
205                                 }
206                         }
207                         sysStats.VectorRatePerWorker = vals
208                 case SystemStats_InputRate:
209                         sysStats.InputRate = val
210                 case SystemStats_LastUpdate:
211                         sysStats.LastUpdate = val
212                 case SystemStats_LastStatsClear:
213                         sysStats.LastStatsClear = val
214                 case SystemStats_Heartbeat:
215                         sysStats.Heartbeat = val
216                 }
217         }
218
219         return nil
220 }
221
222 // GetErrorStats retrieves VPP error stats.
223 func (c *StatsConnection) GetErrorStats(errorStats *api.ErrorStats) (err error) {
224         if err := c.updateStats(&c.errorStatsData, CounterStatsPrefix); err != nil {
225                 return err
226         }
227
228         if errorStats.Errors == nil || len(errorStats.Errors) != len(c.errorStatsData.Entries) {
229                 errorStats.Errors = make([]api.ErrorCounter, len(c.errorStatsData.Entries))
230                 for i := 0; i < len(c.errorStatsData.Entries); i++ {
231                         errorStats.Errors[i].CounterName = string(c.errorStatsData.Entries[i].Name)
232                 }
233         }
234
235         for i, stat := range c.errorStatsData.Entries {
236                 if stat.Type != adapter.ErrorIndex {
237                         continue
238                 }
239                 if errStat, ok := stat.Data.(adapter.ErrorStat); ok {
240                         errorStats.Errors[i].Value = uint64(errStat)
241                 }
242         }
243
244         return nil
245 }
246
247 func (c *StatsConnection) GetNodeStats(nodeStats *api.NodeStats) (err error) {
248         if err := c.updateStats(&c.nodeStatsData, NodeStatsPrefix); err != nil {
249                 return err
250         }
251
252         prepNodes := func(l int) {
253                 if nodeStats.Nodes == nil || len(nodeStats.Nodes) != l {
254                         nodeStats.Nodes = make([]api.NodeCounters, l)
255                         for i := 0; i < l; i++ {
256                                 nodeStats.Nodes[i].NodeIndex = uint32(i)
257                         }
258                 }
259         }
260         perNode := func(stat adapter.StatEntry, fn func(*api.NodeCounters, uint64)) {
261                 if s, ok := stat.Data.(adapter.SimpleCounterStat); ok {
262                         prepNodes(len(s[0]))
263                         for i := range nodeStats.Nodes {
264                                 val := adapter.ReduceSimpleCounterStatIndex(s, i)
265                                 fn(&nodeStats.Nodes[i], val)
266                         }
267                 }
268         }
269
270         for _, stat := range c.nodeStatsData.Entries {
271                 switch string(stat.Name) {
272                 case NodeStats_Names:
273                         if stat, ok := stat.Data.(adapter.NameStat); ok {
274                                 prepNodes(len(stat))
275                                 for i, nc := range nodeStats.Nodes {
276                                         if nc.NodeName != string(stat[i]) {
277                                                 nc.NodeName = string(stat[i])
278                                                 nodeStats.Nodes[i] = nc
279                                         }
280                                 }
281                         }
282                 case NodeStats_Clocks:
283                         perNode(stat, func(node *api.NodeCounters, val uint64) {
284                                 node.Clocks = val
285                         })
286                 case NodeStats_Vectors:
287                         perNode(stat, func(node *api.NodeCounters, val uint64) {
288                                 node.Vectors = val
289                         })
290                 case NodeStats_Calls:
291                         perNode(stat, func(node *api.NodeCounters, val uint64) {
292                                 node.Calls = val
293                         })
294                 case NodeStats_Suspends:
295                         perNode(stat, func(node *api.NodeCounters, val uint64) {
296                                 node.Suspends = val
297                         })
298                 }
299         }
300
301         return nil
302 }
303
304 // GetInterfaceStats retrieves VPP per interface stats.
305 func (c *StatsConnection) GetInterfaceStats(ifaceStats *api.InterfaceStats) (err error) {
306         if err := c.updateStats(&c.ifaceStatsData, InterfaceStatsPrefix); err != nil {
307                 return err
308         }
309
310         prep := func(l int) {
311                 if ifaceStats.Interfaces == nil || len(ifaceStats.Interfaces) != l {
312                         ifaceStats.Interfaces = make([]api.InterfaceCounters, l)
313                         for i := 0; i < l; i++ {
314                                 ifaceStats.Interfaces[i].InterfaceIndex = uint32(i)
315                         }
316                 }
317         }
318         perNode := func(stat adapter.StatEntry, fn func(*api.InterfaceCounters, uint64)) {
319                 if s, ok := stat.Data.(adapter.SimpleCounterStat); ok {
320                         prep(len(s[0]))
321                         for i := range ifaceStats.Interfaces {
322                                 val := adapter.ReduceSimpleCounterStatIndex(s, i)
323                                 fn(&ifaceStats.Interfaces[i], val)
324                         }
325                 }
326         }
327         perNodeComb := func(stat adapter.StatEntry, fn func(*api.InterfaceCounters, [2]uint64)) {
328                 if s, ok := stat.Data.(adapter.CombinedCounterStat); ok {
329                         prep(len(s[0]))
330                         for i := range ifaceStats.Interfaces {
331                                 val := adapter.ReduceCombinedCounterStatIndex(s, i)
332                                 fn(&ifaceStats.Interfaces[i], val)
333                         }
334                 }
335         }
336
337         for _, stat := range c.ifaceStatsData.Entries {
338                 switch string(stat.Name) {
339                 case InterfaceStats_Names:
340                         if stat, ok := stat.Data.(adapter.NameStat); ok {
341                                 prep(len(stat))
342                                 for i, nc := range ifaceStats.Interfaces {
343                                         if nc.InterfaceName != string(stat[i]) {
344                                                 nc.InterfaceName = string(stat[i])
345                                                 ifaceStats.Interfaces[i] = nc
346                                         }
347                                 }
348                         }
349                 case InterfaceStats_Drops:
350                         perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
351                                 iface.Drops = val
352                         })
353                 case InterfaceStats_Punt:
354                         perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
355                                 iface.Punts = val
356                         })
357                 case InterfaceStats_IP4:
358                         perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
359                                 iface.IP4 = val
360                         })
361                 case InterfaceStats_IP6:
362                         perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
363                                 iface.IP6 = val
364                         })
365                 case InterfaceStats_RxNoBuf:
366                         perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
367                                 iface.RxNoBuf = val
368                         })
369                 case InterfaceStats_RxMiss:
370                         perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
371                                 iface.RxMiss = val
372                         })
373                 case InterfaceStats_RxError:
374                         perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
375                                 iface.RxErrors = val
376                         })
377                 case InterfaceStats_TxError:
378                         perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
379                                 iface.TxErrors = val
380                         })
381                 case InterfaceStats_Mpls:
382                         perNode(stat, func(iface *api.InterfaceCounters, val uint64) {
383                                 iface.Mpls = val
384                         })
385                 case InterfaceStats_Rx:
386                         perNodeComb(stat, func(iface *api.InterfaceCounters, val [2]uint64) {
387                                 iface.Rx.Packets = val[0]
388                                 iface.Rx.Bytes = val[1]
389                         })
390                 case InterfaceStats_RxUnicast:
391                         perNodeComb(stat, func(iface *api.InterfaceCounters, val [2]uint64) {
392                                 iface.RxUnicast.Packets = val[0]
393                                 iface.RxUnicast.Bytes = val[1]
394                         })
395                 case InterfaceStats_RxMulticast:
396                         perNodeComb(stat, func(iface *api.InterfaceCounters, val [2]uint64) {
397                                 iface.RxMulticast.Packets = val[0]
398                                 iface.RxMulticast.Bytes = val[1]
399                         })
400                 case InterfaceStats_RxBroadcast:
401                         perNodeComb(stat, func(iface *api.InterfaceCounters, val [2]uint64) {
402                                 iface.RxBroadcast.Packets = val[0]
403                                 iface.RxBroadcast.Bytes = val[1]
404                         })
405                 case InterfaceStats_Tx:
406                         perNodeComb(stat, func(iface *api.InterfaceCounters, val [2]uint64) {
407                                 iface.Tx.Packets = val[0]
408                                 iface.Tx.Bytes = val[1]
409                         })
410                 case InterfaceStats_TxUnicastMiss:
411                         // tx-unicast-miss was a spelling mistake in older versions
412                         //
413                         fallthrough
414                 case InterfaceStats_TxUnicast:
415                         perNodeComb(stat, func(iface *api.InterfaceCounters, val [2]uint64) {
416                                 iface.TxUnicast.Packets = val[0]
417                                 iface.TxUnicast.Bytes = val[1]
418                         })
419                 case InterfaceStats_TxMulticast:
420                         perNodeComb(stat, func(iface *api.InterfaceCounters, val [2]uint64) {
421                                 iface.TxMulticast.Packets = val[0]
422                                 iface.TxMulticast.Bytes = val[1]
423                         })
424                 case InterfaceStats_TxBroadcast:
425                         perNodeComb(stat, func(iface *api.InterfaceCounters, val [2]uint64) {
426                                 iface.TxBroadcast.Packets = val[0]
427                                 iface.TxBroadcast.Bytes = val[1]
428                         })
429                 }
430         }
431
432         return nil
433 }
434
435 // GetBufferStats retrieves VPP buffer pools stats.
436 func (c *StatsConnection) GetBufferStats(bufStats *api.BufferStats) (err error) {
437         if err := c.updateStats(&c.bufStatsData, BufferStatsPrefix); err != nil {
438                 return err
439         }
440
441         if bufStats.Buffer == nil {
442                 bufStats.Buffer = make(map[string]api.BufferPool)
443         }
444
445         for _, stat := range c.bufStatsData.Entries {
446                 d, f := path.Split(string(stat.Name))
447                 d = strings.TrimSuffix(d, "/")
448
449                 name := strings.TrimPrefix(d, BufferStatsPrefix)
450                 b, ok := bufStats.Buffer[name]
451                 if !ok {
452                         b.PoolName = name
453                 }
454
455                 var val float64
456                 s, ok := stat.Data.(adapter.ScalarStat)
457                 if ok {
458                         val = float64(s)
459                 }
460                 switch f {
461                 case BufferStats_Cached:
462                         b.Cached = val
463                 case BufferStats_Used:
464                         b.Used = val
465                 case BufferStats_Available:
466                         b.Available = val
467                 }
468
469                 bufStats.Buffer[name] = b
470         }
471
472         return nil
473 }