Fix crash in stats for removed interfaces/nodes
[govpp.git] / core / stats.go
1 package core
2
3 import (
4         "fmt"
5         "strings"
6         "sync/atomic"
7
8         "git.fd.io/govpp.git/adapter"
9         "git.fd.io/govpp.git/api"
10 )
11
12 const (
13         CounterStatsPrefix = "/err/"
14
15         SystemStatsPrefix          = "/sys/"
16         SystemStats_VectorRate     = SystemStatsPrefix + "vector_rate"
17         SystemStats_InputRate      = SystemStatsPrefix + "input_rate"
18         SystemStats_LastUpdate     = SystemStatsPrefix + "last_update"
19         SystemStats_LastStatsClear = SystemStatsPrefix + "last_stats_clear"
20         SystemStats_Heartbeat      = SystemStatsPrefix + "heartbeat"
21
22         NodeStatsPrefix    = "/sys/node/"
23         NodeStats_Names    = NodeStatsPrefix + "names"
24         NodeStats_Clocks   = NodeStatsPrefix + "clocks"
25         NodeStats_Vectors  = NodeStatsPrefix + "vectors"
26         NodeStats_Calls    = NodeStatsPrefix + "calls"
27         NodeStats_Suspends = NodeStatsPrefix + "suspends"
28
29         InterfaceStatsPrefix         = "/if/"
30         InterfaceStats_Names         = InterfaceStatsPrefix + "names"
31         InterfaceStats_Drops         = InterfaceStatsPrefix + "drops"
32         InterfaceStats_Punt          = InterfaceStatsPrefix + "punt"
33         InterfaceStats_IP4           = InterfaceStatsPrefix + "ip4"
34         InterfaceStats_IP6           = InterfaceStatsPrefix + "ip6"
35         InterfaceStats_RxNoBuf       = InterfaceStatsPrefix + "rx-no-buf"
36         InterfaceStats_RxMiss        = InterfaceStatsPrefix + "rx-miss"
37         InterfaceStats_RxError       = InterfaceStatsPrefix + "rx-error"
38         InterfaceStats_TxError       = InterfaceStatsPrefix + "tx-error"
39         InterfaceStats_Rx            = InterfaceStatsPrefix + "rx"
40         InterfaceStats_RxUnicast     = InterfaceStatsPrefix + "rx-unicast"
41         InterfaceStats_RxMulticast   = InterfaceStatsPrefix + "rx-multicast"
42         InterfaceStats_RxBroadcast   = InterfaceStatsPrefix + "rx-broadcast"
43         InterfaceStats_Tx            = InterfaceStatsPrefix + "tx"
44         InterfaceStats_TxUnicastMiss = InterfaceStatsPrefix + "tx-unicast-miss"
45         InterfaceStats_TxMulticast   = InterfaceStatsPrefix + "tx-multicast"
46         InterfaceStats_TxBroadcast   = InterfaceStatsPrefix + "tx-broadcast"
47
48         NetworkStatsPrefix = "/net/"
49         // TODO: network stats
50         NetworkStats_RouteTo   = NetworkStatsPrefix + "route/to"
51         NetworkStats_RouteVia  = NetworkStatsPrefix + "route/via"
52         NetworkStats_MRoute    = NetworkStatsPrefix + "mroute"
53         NetworkStats_Adjacency = NetworkStatsPrefix + "adjacency"
54 )
55
56 type StatsConnection struct {
57         statsClient adapter.StatsAPI
58
59         connected uint32 // non-zero if the adapter is connected to VPP
60 }
61
62 func newStatsConnection(stats adapter.StatsAPI) *StatsConnection {
63         return &StatsConnection{
64                 statsClient: stats,
65         }
66 }
67
68 // Connect connects to Stats API using specified adapter and returns a connection handle.
69 // This call blocks until it is either connected, or an error occurs.
70 // Only one connection attempt will be performed.
71 func ConnectStats(stats adapter.StatsAPI) (*StatsConnection, error) {
72         c := newStatsConnection(stats)
73
74         if err := c.connectClient(); err != nil {
75                 return nil, err
76         }
77
78         return c, nil
79 }
80
81 func (c *StatsConnection) connectClient() error {
82         log.Debug("Connecting to stats..")
83
84         if err := c.statsClient.Connect(); err != nil {
85                 return err
86         }
87
88         log.Debugf("Connected to stats.")
89
90         // store connected state
91         atomic.StoreUint32(&c.connected, 1)
92
93         return nil
94 }
95
96 // Disconnect disconnects from Stats API and releases all connection-related resources.
97 func (c *StatsConnection) Disconnect() {
98         if c == nil {
99                 return
100         }
101
102         if c.statsClient != nil {
103                 c.disconnectClient()
104         }
105 }
106
107 func (c *StatsConnection) disconnectClient() {
108         if atomic.CompareAndSwapUint32(&c.connected, 1, 0) {
109                 c.statsClient.Disconnect()
110         }
111 }
112
113 // GetSystemStats retrieves VPP system stats.
114 func (c *StatsConnection) GetSystemStats() (*api.SystemStats, error) {
115         stats, err := c.statsClient.DumpStats(SystemStatsPrefix)
116         if err != nil {
117                 return nil, err
118         }
119
120         sysStats := &api.SystemStats{}
121
122         for _, stat := range stats {
123                 switch stat.Name {
124                 case SystemStats_VectorRate:
125                         sysStats.VectorRate = scalarStatToFloat64(stat.Data)
126                 case SystemStats_InputRate:
127                         sysStats.InputRate = scalarStatToFloat64(stat.Data)
128                 case SystemStats_LastUpdate:
129                         sysStats.LastUpdate = scalarStatToFloat64(stat.Data)
130                 case SystemStats_LastStatsClear:
131                         sysStats.LastStatsClear = scalarStatToFloat64(stat.Data)
132                 case SystemStats_Heartbeat:
133                         sysStats.Heartbeat = scalarStatToFloat64(stat.Data)
134                 }
135         }
136
137         return sysStats, nil
138 }
139
140 // GetErrorStats retrieves VPP error stats.
141 func (c *StatsConnection) GetErrorStats(names ...string) (*api.ErrorStats, error) {
142         var patterns []string
143         if len(names) > 0 {
144                 patterns = make([]string, len(names))
145                 for i, name := range names {
146                         patterns[i] = CounterStatsPrefix + name
147                 }
148         } else {
149                 // retrieve all error counters by default
150                 patterns = []string{CounterStatsPrefix}
151         }
152         stats, err := c.statsClient.DumpStats(patterns...)
153         if err != nil {
154                 return nil, err
155         }
156
157         var errorStats = &api.ErrorStats{}
158
159         for _, stat := range stats {
160                 statName := strings.TrimPrefix(stat.Name, CounterStatsPrefix)
161
162                 /* TODO: deal with stats that contain '/' in node/counter name
163                 parts := strings.Split(statName, "/")
164                 var nodeName, counterName string
165                 switch len(parts) {
166                 case 2:
167                         nodeName = parts[0]
168                         counterName = parts[1]
169                 case 3:
170                         nodeName = parts[0] + parts[1]
171                         counterName = parts[2]
172                 }*/
173
174                 errorStats.Errors = append(errorStats.Errors, api.ErrorCounter{
175                         CounterName: statName,
176                         Value:       errorStatToUint64(stat.Data),
177                 })
178         }
179
180         return errorStats, nil
181 }
182
183 // GetNodeStats retrieves VPP per node stats.
184 func (c *StatsConnection) GetNodeStats() (*api.NodeStats, error) {
185         stats, err := c.statsClient.DumpStats(NodeStatsPrefix)
186         if err != nil {
187                 return nil, err
188         }
189
190         nodeStats := &api.NodeStats{}
191         var setPerNode = func(perNode []uint64, fn func(c *api.NodeCounters, v uint64)) {
192                 if nodeStats.Nodes == nil {
193                         nodeStats.Nodes = make([]api.NodeCounters, len(perNode))
194                         for i := range perNode {
195                                 nodeStats.Nodes[i].NodeIndex = uint32(i)
196                         }
197                 }
198                 for i, v := range perNode {
199                         if len(nodeStats.Nodes) <= i {
200                                 break
201                         }
202                         nodeCounters := nodeStats.Nodes[i]
203                         fn(&nodeCounters, v)
204                         nodeStats.Nodes[i] = nodeCounters
205                 }
206         }
207
208         for _, stat := range stats {
209                 switch stat.Name {
210                 case NodeStats_Names:
211                         if names, ok := stat.Data.(adapter.NameStat); !ok {
212                                 return nil, fmt.Errorf("invalid stat type for %s", stat.Name)
213                         } else {
214                                 if nodeStats.Nodes == nil {
215                                         nodeStats.Nodes = make([]api.NodeCounters, len(names))
216                                         for i := range names {
217                                                 nodeStats.Nodes[i].NodeIndex = uint32(i)
218                                         }
219                                 }
220                                 for i, name := range names {
221                                         nodeStats.Nodes[i].NodeName = string(name)
222                                 }
223                         }
224                 case NodeStats_Clocks:
225                         setPerNode(reduceSimpleCounterStat(stat.Data), func(c *api.NodeCounters, v uint64) {
226                                 c.Clocks = v
227                         })
228                 case NodeStats_Vectors:
229                         setPerNode(reduceSimpleCounterStat(stat.Data), func(c *api.NodeCounters, v uint64) {
230                                 c.Vectors = v
231                         })
232                 case NodeStats_Calls:
233                         setPerNode(reduceSimpleCounterStat(stat.Data), func(c *api.NodeCounters, v uint64) {
234                                 c.Calls = v
235                         })
236                 case NodeStats_Suspends:
237                         setPerNode(reduceSimpleCounterStat(stat.Data), func(c *api.NodeCounters, v uint64) {
238                                 c.Suspends = v
239                         })
240                 }
241         }
242
243         return nodeStats, nil
244 }
245
246 // GetInterfaceStats retrieves VPP per interface stats.
247 func (c *StatsConnection) GetInterfaceStats() (*api.InterfaceStats, error) {
248         stats, err := c.statsClient.DumpStats(InterfaceStatsPrefix)
249         if err != nil {
250                 return nil, err
251         }
252
253         ifStats := &api.InterfaceStats{}
254
255         var setPerIf = func(perIf []uint64, fn func(c *api.InterfaceCounters, v uint64)) {
256                 if ifStats.Interfaces == nil {
257                         ifStats.Interfaces = make([]api.InterfaceCounters, len(perIf))
258                         for i := range perIf {
259                                 ifStats.Interfaces[i].InterfaceIndex = uint32(i)
260                         }
261                 }
262                 for i, v := range perIf {
263                         if len(ifStats.Interfaces) <= i {
264                                 break
265                         }
266                         ifCounters := ifStats.Interfaces[i]
267                         fn(&ifCounters, v)
268                         ifStats.Interfaces[i] = ifCounters
269                 }
270         }
271
272         for _, stat := range stats {
273                 switch stat.Name {
274                 case InterfaceStats_Names:
275                         if names, ok := stat.Data.(adapter.NameStat); !ok {
276                                 return nil, fmt.Errorf("invalid stat type for %s", stat.Name)
277                         } else {
278                                 if ifStats.Interfaces == nil {
279                                         ifStats.Interfaces = make([]api.InterfaceCounters, len(names))
280                                         for i := range names {
281                                                 ifStats.Interfaces[i].InterfaceIndex = uint32(i)
282                                         }
283                                 }
284                                 for i, name := range names {
285                                         ifStats.Interfaces[i].InterfaceName = string(name)
286                                 }
287                         }
288                 case InterfaceStats_Drops:
289                         setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) {
290                                 c.Drops = v
291                         })
292                 case InterfaceStats_Punt:
293                         setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) {
294                                 c.Punts = v
295                         })
296                 case InterfaceStats_IP4:
297                         setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) {
298                                 c.IP4 = v
299                         })
300                 case InterfaceStats_IP6:
301                         setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) {
302                                 c.IP6 = v
303                         })
304                 case InterfaceStats_RxNoBuf:
305                         setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) {
306                                 c.RxNoBuf = v
307                         })
308                 case InterfaceStats_RxMiss:
309                         setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) {
310                                 c.RxMiss = v
311                         })
312                 case InterfaceStats_RxError:
313                         setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) {
314                                 c.RxErrors = v
315                         })
316                 case InterfaceStats_TxError:
317                         setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) {
318                                 c.TxErrors = v
319                         })
320                 case InterfaceStats_Rx:
321                         per := reduceCombinedCounterStat(stat.Data)
322                         setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) {
323                                 c.RxPackets = v
324                         })
325                         setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) {
326                                 c.RxBytes = v
327                         })
328                 case InterfaceStats_RxUnicast:
329                         per := reduceCombinedCounterStat(stat.Data)
330                         setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) {
331                                 c.RxUnicast[0] = v
332                         })
333                         setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) {
334                                 c.RxUnicast[1] = v
335                         })
336                 case InterfaceStats_RxMulticast:
337                         per := reduceCombinedCounterStat(stat.Data)
338                         setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) {
339                                 c.RxMulticast[0] = v
340                         })
341                         setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) {
342                                 c.RxMulticast[1] = v
343                         })
344                 case InterfaceStats_RxBroadcast:
345                         per := reduceCombinedCounterStat(stat.Data)
346                         setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) {
347                                 c.RxBroadcast[0] = v
348                         })
349                         setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) {
350                                 c.RxBroadcast[1] = v
351                         })
352                 case InterfaceStats_Tx:
353                         per := reduceCombinedCounterStat(stat.Data)
354                         setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) {
355                                 c.TxPackets = v
356                         })
357                         setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) {
358                                 c.TxBytes = v
359                         })
360                 case InterfaceStats_TxUnicastMiss:
361                         per := reduceCombinedCounterStat(stat.Data)
362                         setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) {
363                                 c.TxUnicastMiss[0] = v
364                         })
365                         setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) {
366                                 c.TxUnicastMiss[1] = v
367                         })
368                 case InterfaceStats_TxMulticast:
369                         per := reduceCombinedCounterStat(stat.Data)
370                         setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) {
371                                 c.TxMulticast[0] = v
372                         })
373                         setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) {
374                                 c.TxMulticast[1] = v
375                         })
376                 case InterfaceStats_TxBroadcast:
377                         per := reduceCombinedCounterStat(stat.Data)
378                         setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) {
379                                 c.TxBroadcast[0] = v
380                         })
381                         setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) {
382                                 c.TxBroadcast[1] = v
383                         })
384                 }
385         }
386
387         return ifStats, nil
388 }
389
390 func scalarStatToFloat64(stat adapter.Stat) float64 {
391         if s, ok := stat.(adapter.ScalarStat); ok {
392                 return float64(s)
393         }
394         return 0
395 }
396
397 func errorStatToUint64(stat adapter.Stat) uint64 {
398         if s, ok := stat.(adapter.ErrorStat); ok {
399                 return uint64(s)
400         }
401         return 0
402 }
403
404 func reduceSimpleCounterStat(stat adapter.Stat) []uint64 {
405         if s, ok := stat.(adapter.SimpleCounterStat); ok {
406                 if len(s) == 0 {
407                         return []uint64{}
408                 }
409                 var per = make([]uint64, len(s[0]))
410                 for _, w := range s {
411                         for i, n := range w {
412                                 per[i] += uint64(n)
413                         }
414                 }
415                 return per
416         }
417         return nil
418 }
419
420 func reduceCombinedCounterStat(stat adapter.Stat) [2][]uint64 {
421         if s, ok := stat.(adapter.CombinedCounterStat); ok {
422                 if len(s) == 0 {
423                         return [2][]uint64{{}, {}}
424                 }
425                 var perPackets = make([]uint64, len(s[0]))
426                 var perBytes = make([]uint64, len(s[0]))
427                 for _, w := range s {
428                         for i, n := range w {
429                                 perPackets[i] += uint64(n.Packets)
430                                 perBytes[i] += uint64(n.Bytes)
431                         }
432                 }
433                 return [2][]uint64{perPackets, perBytes}
434         }
435         return [2][]uint64{}
436 }