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