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