Add support for names vector and fill name in interface/node stats
[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                         nodeCounters := nodeStats.Nodes[i]
200                         fn(&nodeCounters, v)
201                         nodeStats.Nodes[i] = nodeCounters
202                 }
203         }
204
205         for _, stat := range stats {
206                 switch stat.Name {
207                 case NodeStats_Names:
208                         if names, ok := stat.Data.(adapter.NameStat); !ok {
209                                 return nil, fmt.Errorf("invalid stat type for %s", stat.Name)
210                         } else {
211                                 if nodeStats.Nodes == nil {
212                                         nodeStats.Nodes = make([]api.NodeCounters, len(names))
213                                         for i := range names {
214                                                 nodeStats.Nodes[i].NodeIndex = uint32(i)
215                                         }
216                                 }
217                                 for i, name := range names {
218                                         nodeStats.Nodes[i].NodeName = string(name)
219                                 }
220                         }
221                 case NodeStats_Clocks:
222                         setPerNode(reduceSimpleCounterStat(stat.Data), func(c *api.NodeCounters, v uint64) {
223                                 c.Clocks = v
224                         })
225                 case NodeStats_Vectors:
226                         setPerNode(reduceSimpleCounterStat(stat.Data), func(c *api.NodeCounters, v uint64) {
227                                 c.Vectors = v
228                         })
229                 case NodeStats_Calls:
230                         setPerNode(reduceSimpleCounterStat(stat.Data), func(c *api.NodeCounters, v uint64) {
231                                 c.Calls = v
232                         })
233                 case NodeStats_Suspends:
234                         setPerNode(reduceSimpleCounterStat(stat.Data), func(c *api.NodeCounters, v uint64) {
235                                 c.Suspends = v
236                         })
237                 }
238         }
239
240         return nodeStats, nil
241 }
242
243 // GetInterfaceStats retrieves VPP per interface stats.
244 func (c *StatsConnection) GetInterfaceStats() (*api.InterfaceStats, error) {
245         stats, err := c.statsClient.DumpStats(InterfaceStatsPrefix)
246         if err != nil {
247                 return nil, err
248         }
249
250         ifStats := &api.InterfaceStats{}
251
252         var setPerIf = func(perIf []uint64, fn func(c *api.InterfaceCounters, v uint64)) {
253                 if ifStats.Interfaces == nil {
254                         ifStats.Interfaces = make([]api.InterfaceCounters, len(perIf))
255                         for i := range perIf {
256                                 ifStats.Interfaces[i].InterfaceIndex = uint32(i)
257                         }
258                 }
259                 for i, v := range perIf {
260                         ifCounters := ifStats.Interfaces[i]
261                         fn(&ifCounters, v)
262                         ifStats.Interfaces[i] = ifCounters
263                 }
264         }
265
266         for _, stat := range stats {
267                 switch stat.Name {
268                 case InterfaceStats_Names:
269                         if names, ok := stat.Data.(adapter.NameStat); !ok {
270                                 return nil, fmt.Errorf("invalid stat type for %s", stat.Name)
271                         } else {
272                                 if ifStats.Interfaces == nil {
273                                         ifStats.Interfaces = make([]api.InterfaceCounters, len(names))
274                                         for i := range names {
275                                                 ifStats.Interfaces[i].InterfaceIndex = uint32(i)
276                                         }
277                                 }
278                                 for i, name := range names {
279                                         ifStats.Interfaces[i].InterfaceName = string(name)
280                                 }
281                         }
282                 case InterfaceStats_Drops:
283                         setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) {
284                                 c.Drops = v
285                         })
286                 case InterfaceStats_Punt:
287                         setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) {
288                                 c.Punts = v
289                         })
290                 case InterfaceStats_IP4:
291                         setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) {
292                                 c.IP4 = v
293                         })
294                 case InterfaceStats_IP6:
295                         setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) {
296                                 c.IP6 = v
297                         })
298                 case InterfaceStats_RxNoBuf:
299                         setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) {
300                                 c.RxNoBuf = v
301                         })
302                 case InterfaceStats_RxMiss:
303                         setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) {
304                                 c.RxMiss = v
305                         })
306                 case InterfaceStats_RxError:
307                         setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) {
308                                 c.RxErrors = v
309                         })
310                 case InterfaceStats_TxError:
311                         setPerIf(reduceSimpleCounterStat(stat.Data), func(c *api.InterfaceCounters, v uint64) {
312                                 c.TxErrors = v
313                         })
314                 case InterfaceStats_Rx:
315                         per := reduceCombinedCounterStat(stat.Data)
316                         setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) {
317                                 c.RxPackets = v
318                         })
319                         setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) {
320                                 c.RxBytes = v
321                         })
322                 case InterfaceStats_RxUnicast:
323                         per := reduceCombinedCounterStat(stat.Data)
324                         setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) {
325                                 c.RxUnicast[0] = v
326                         })
327                         setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) {
328                                 c.RxUnicast[1] = v
329                         })
330                 case InterfaceStats_RxMulticast:
331                         per := reduceCombinedCounterStat(stat.Data)
332                         setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) {
333                                 c.RxMulticast[0] = v
334                         })
335                         setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) {
336                                 c.RxMulticast[1] = v
337                         })
338                 case InterfaceStats_RxBroadcast:
339                         per := reduceCombinedCounterStat(stat.Data)
340                         setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) {
341                                 c.RxBroadcast[0] = v
342                         })
343                         setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) {
344                                 c.RxBroadcast[1] = v
345                         })
346                 case InterfaceStats_Tx:
347                         per := reduceCombinedCounterStat(stat.Data)
348                         setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) {
349                                 c.TxPackets = v
350                         })
351                         setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) {
352                                 c.TxBytes = v
353                         })
354                 case InterfaceStats_TxUnicastMiss:
355                         per := reduceCombinedCounterStat(stat.Data)
356                         setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) {
357                                 c.TxUnicastMiss[0] = v
358                         })
359                         setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) {
360                                 c.TxUnicastMiss[1] = v
361                         })
362                 case InterfaceStats_TxMulticast:
363                         per := reduceCombinedCounterStat(stat.Data)
364                         setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) {
365                                 c.TxMulticast[0] = v
366                         })
367                         setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) {
368                                 c.TxMulticast[1] = v
369                         })
370                 case InterfaceStats_TxBroadcast:
371                         per := reduceCombinedCounterStat(stat.Data)
372                         setPerIf(per[0], func(c *api.InterfaceCounters, v uint64) {
373                                 c.TxBroadcast[0] = v
374                         })
375                         setPerIf(per[1], func(c *api.InterfaceCounters, v uint64) {
376                                 c.TxBroadcast[1] = v
377                         })
378                 }
379         }
380
381         return ifStats, nil
382 }
383
384 func scalarStatToFloat64(stat adapter.Stat) float64 {
385         if s, ok := stat.(adapter.ScalarStat); ok {
386                 return float64(s)
387         }
388         return 0
389 }
390
391 func errorStatToUint64(stat adapter.Stat) uint64 {
392         if s, ok := stat.(adapter.ErrorStat); ok {
393                 return uint64(s)
394         }
395         return 0
396 }
397
398 func reduceSimpleCounterStat(stat adapter.Stat) []uint64 {
399         if s, ok := stat.(adapter.SimpleCounterStat); ok {
400                 if len(s) == 0 {
401                         return []uint64{}
402                 }
403                 var per = make([]uint64, len(s[0]))
404                 for _, w := range s {
405                         for i, n := range w {
406                                 per[i] += uint64(n)
407                         }
408                 }
409                 return per
410         }
411         return nil
412 }
413
414 func reduceCombinedCounterStat(stat adapter.Stat) [2][]uint64 {
415         if s, ok := stat.(adapter.CombinedCounterStat); ok {
416                 if len(s) == 0 {
417                         return [2][]uint64{{}, {}}
418                 }
419                 var perPackets = make([]uint64, len(s[0]))
420                 var perBytes = make([]uint64, len(s[0]))
421                 for _, w := range s {
422                         for i, n := range w {
423                                 perPackets[i] += uint64(n.Packets)
424                                 perBytes[i] += uint64(n.Bytes)
425                         }
426                 }
427                 return [2][]uint64{perPackets, perBytes}
428         }
429         return [2][]uint64{}
430 }