Fix error counters for VPP 19.04
[govpp.git] / adapter / statsclient / statsclient.go
1 // Copyright (c) 2019 Cisco and/or its affiliates.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at:
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 // Package statsclient is pure Go implementation of VPP stats API client.
16 package statsclient
17
18 import (
19         "bytes"
20         "fmt"
21         "os"
22         "regexp"
23         "unsafe"
24
25         logger "github.com/sirupsen/logrus"
26
27         "git.fd.io/govpp.git/adapter"
28 )
29
30 var (
31         // Debug is global variable that determines debug mode
32         Debug = os.Getenv("DEBUG_GOVPP_STATS") != ""
33
34         // Log is global logger
35         Log = logger.New()
36 )
37
38 // init initializes global logger, which logs debug level messages to stdout.
39 func init() {
40         Log.Out = os.Stdout
41         if Debug {
42                 Log.Level = logger.DebugLevel
43                 Log.Debug("govpp/statsclient: enabled debug mode")
44         }
45 }
46
47 // StatsClient is the pure Go implementation for VPP stats API.
48 type StatsClient struct {
49         sockAddr string
50
51         currentEpoch int64
52         statSegment
53 }
54
55 // NewStatsClient returns new VPP stats API client.
56 func NewStatsClient(sockAddr string) *StatsClient {
57         if sockAddr == "" {
58                 sockAddr = adapter.DefaultStatsSocket
59         }
60         return &StatsClient{
61                 sockAddr: sockAddr,
62         }
63 }
64
65 const sockNotFoundWarn = `stats socket not found at: %s
66 ------------------------------------------------------------
67  VPP stats socket is missing!
68  Is VPP running with stats segment enabled?
69
70  To enable it add following section to startup config:
71    statseg {
72      default
73    }
74 ------------------------------------------------------------
75 `
76
77 func (c *StatsClient) Connect() error {
78         // check if socket exists
79         if _, err := os.Stat(c.sockAddr); os.IsNotExist(err) {
80                 Log.Warnf(sockNotFoundWarn, c.sockAddr)
81                 return fmt.Errorf("stats socket file %s does not exist", c.sockAddr)
82         } else if err != nil {
83                 return fmt.Errorf("stats socket error: %v", err)
84         }
85
86         if err := c.statSegment.connect(c.sockAddr); err != nil {
87                 return err
88         }
89
90         ver := c.readVersion()
91         Log.Debugf("stat segment version: %v", ver)
92
93         if err := checkVersion(ver); err != nil {
94                 return err
95         }
96
97         return nil
98 }
99
100 func (c *StatsClient) Disconnect() error {
101         if err := c.statSegment.disconnect(); err != nil {
102                 return err
103         }
104
105         return nil
106 }
107
108 func (c *StatsClient) ListStats(patterns ...string) (statNames []string, err error) {
109         sa := c.accessStart()
110         if sa == nil {
111                 return nil, fmt.Errorf("access failed")
112         }
113
114         dirOffset, _, _ := c.readOffsets()
115         Log.Debugf("dirOffset: %v", dirOffset)
116
117         vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirOffset]))
118         Log.Debugf("vecLen: %v", vecLen)
119         Log.Debugf("unsafe.Sizeof(statSegDirectoryEntry{}): %v", unsafe.Sizeof(statSegDirectoryEntry{}))
120
121         for i := uint64(0); i < vecLen; i++ {
122                 offset := uintptr(i) * unsafe.Sizeof(statSegDirectoryEntry{})
123                 dirEntry := (*statSegDirectoryEntry)(add(unsafe.Pointer(&c.sharedHeader[dirOffset]), offset))
124
125                 nul := bytes.IndexByte(dirEntry.name[:], '\x00')
126                 if nul < 0 {
127                         Log.Debugf("no zero byte found for: %q", dirEntry.name[:])
128                         continue
129                 }
130                 name := string(dirEntry.name[:nul])
131                 if name == "" {
132                         Log.Debugf("entry with empty name found (%d)", i)
133                         continue
134                 }
135
136                 Log.Debugf(" %80q (type: %v, data: %d, offset: %d) ", name, dirEntry.directoryType, dirEntry.unionData, dirEntry.offsetVector)
137
138                 if nameMatches(name, patterns) {
139                         statNames = append(statNames, name)
140                 }
141
142                 // TODO: copy the listed entries elsewhere
143         }
144
145         if !c.accessEnd(sa) {
146                 return nil, adapter.ErrStatDirBusy
147         }
148
149         c.currentEpoch = sa.epoch
150
151         return statNames, nil
152 }
153
154 func (c *StatsClient) DumpStats(patterns ...string) (entries []*adapter.StatEntry, err error) {
155         epoch, _ := c.readEpoch()
156         if c.currentEpoch > 0 && c.currentEpoch != epoch { // TODO: do list stats before dump
157                 return nil, fmt.Errorf("old data")
158         }
159
160         sa := c.accessStart()
161         if sa == nil {
162                 return nil, fmt.Errorf("access failed")
163         }
164
165         dirOffset, _, _ := c.readOffsets()
166         vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirOffset]))
167
168         for i := uint64(0); i < vecLen; i++ {
169                 offset := uintptr(i) * unsafe.Sizeof(statSegDirectoryEntry{})
170                 dirEntry := (*statSegDirectoryEntry)(add(unsafe.Pointer(&c.sharedHeader[dirOffset]), offset))
171
172                 nul := bytes.IndexByte(dirEntry.name[:], '\x00')
173                 if nul < 0 {
174                         Log.Debugf("no zero byte found for: %q", dirEntry.name[:])
175                         continue
176                 }
177                 name := string(dirEntry.name[:nul])
178                 if name == "" {
179                         Log.Debugf("entry with empty name found (%d)", i)
180                         continue
181                 }
182
183                 Log.Debugf(" - %s (type: %v, data: %v, offset: %v) ", name, dirEntry.directoryType, dirEntry.unionData, dirEntry.offsetVector)
184
185                 entry := adapter.StatEntry{
186                         Name: name,
187                         Type: adapter.StatType(dirEntry.directoryType),
188                         Data: c.copyData(dirEntry),
189                 }
190
191                 Log.Debugf("\tentry data: %+v %#v (%T)", entry.Data, entry.Data, entry.Data)
192
193                 if nameMatches(entry.Name, patterns) {
194                         entries = append(entries, &entry)
195                 }
196         }
197
198         if !c.accessEnd(sa) {
199                 return nil, adapter.ErrStatDumpBusy
200         }
201
202         return entries, nil
203 }
204
205 func nameMatches(name string, patterns []string) bool {
206         if len(patterns) == 0 {
207                 return true
208         }
209         for _, pattern := range patterns {
210                 matched, err := regexp.MatchString(pattern, name)
211                 if err == nil && matched {
212                         return true
213                 }
214         }
215         return false
216 }