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