Fix statsclient for VPP 20.05-rc0 (master)
[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
24         logger "github.com/sirupsen/logrus"
25
26         "git.fd.io/govpp.git/adapter"
27 )
28
29 const (
30         // DefaultSocketName is default VPP stats socket file path.
31         DefaultSocketName = adapter.DefaultStatsSocket
32 )
33
34 const socketMissing = `
35 ------------------------------------------------------------
36  VPP stats socket file %s is missing!
37
38   - is VPP running with stats segment enabled?
39   - is the correct socket name configured?
40
41  To enable it add following section to your VPP config:
42    statseg {
43      default
44    }
45 ------------------------------------------------------------
46 `
47
48 var (
49         // Debug is global variable that determines debug mode
50         Debug = os.Getenv("DEBUG_GOVPP_STATS") != ""
51
52         // Log is global logger
53         Log = logger.New()
54 )
55
56 // init initializes global logger, which logs debug level messages to stdout.
57 func init() {
58         Log.Out = os.Stdout
59         if Debug {
60                 Log.Level = logger.DebugLevel
61                 Log.Debug("govpp/statsclient: enabled debug mode")
62         }
63 }
64
65 func debugf(f string, a ...interface{}) {
66         if Debug {
67                 Log.Debugf(f, a...)
68         }
69 }
70
71 // implements StatsAPI
72 var _ adapter.StatsAPI = (*StatsClient)(nil)
73
74 // StatsClient is the pure Go implementation for VPP stats API.
75 type StatsClient struct {
76         sockAddr string
77
78         statSegment
79 }
80
81 // NewStatsClient returns new VPP stats API client.
82 func NewStatsClient(sockAddr string) *StatsClient {
83         if sockAddr == "" {
84                 sockAddr = DefaultSocketName
85         }
86         return &StatsClient{
87                 sockAddr: sockAddr,
88         }
89 }
90
91 func (c *StatsClient) Connect() error {
92         // check if socket exists
93         if _, err := os.Stat(c.sockAddr); os.IsNotExist(err) {
94                 fmt.Fprintf(os.Stderr, socketMissing, c.sockAddr)
95                 return fmt.Errorf("stats socket file %s does not exist", c.sockAddr)
96         } else if err != nil {
97                 return fmt.Errorf("stats socket error: %v", err)
98         }
99
100         if err := c.statSegment.connect(c.sockAddr); err != nil {
101                 return err
102         }
103
104         return nil
105 }
106
107 func (c *StatsClient) Disconnect() error {
108         if err := c.statSegment.disconnect(); err != nil {
109                 return err
110         }
111         return nil
112 }
113
114 func (c *StatsClient) ListStats(patterns ...string) (names []string, err error) {
115         sa := c.accessStart()
116         if sa.epoch == 0 {
117                 return nil, adapter.ErrStatsAccessFailed
118         }
119
120         indexes, err := c.listIndexes(patterns...)
121         if err != nil {
122                 return nil, err
123         }
124
125         dirVector := c.getStatDirVector()
126         vecLen := uint32(vectorLen(dirVector))
127
128         for _, index := range indexes {
129                 if index >= vecLen {
130                         return nil, fmt.Errorf("stat entry index %d out of dir vector len (%d)", index, vecLen)
131                 }
132
133                 dirEntry := c.getStatDirIndex(dirVector, index)
134                 var name []byte
135                 for n := 0; n < len(dirEntry.name); n++ {
136                         if dirEntry.name[n] == 0 {
137                                 name = dirEntry.name[:n]
138                                 break
139                         }
140                 }
141                 names = append(names, string(name))
142         }
143
144         if !c.accessEnd(&sa) {
145                 return nil, adapter.ErrStatsDataBusy
146         }
147
148         return names, nil
149 }
150
151 func (c *StatsClient) DumpStats(patterns ...string) (entries []adapter.StatEntry, err error) {
152         sa := c.accessStart()
153         if sa.epoch == 0 {
154                 return nil, adapter.ErrStatsAccessFailed
155         }
156
157         indexes, err := c.listIndexes(patterns...)
158         if err != nil {
159                 return nil, err
160         }
161         if entries, err = c.dumpEntries(indexes); err != nil {
162                 return nil, err
163         }
164
165         if !c.accessEnd(&sa) {
166                 return nil, adapter.ErrStatsDataBusy
167         }
168
169         return entries, nil
170 }
171
172 func (c *StatsClient) PrepareDir(patterns ...string) (*adapter.StatDir, error) {
173         dir := new(adapter.StatDir)
174
175         sa := c.accessStart()
176         if sa.epoch == 0 {
177                 return nil, adapter.ErrStatsAccessFailed
178         }
179
180         indexes, err := c.listIndexes(patterns...)
181         if err != nil {
182                 return nil, err
183         }
184         dir.Indexes = indexes
185
186         entries, err := c.dumpEntries(indexes)
187         if err != nil {
188                 return nil, err
189         }
190         dir.Entries = entries
191
192         if !c.accessEnd(&sa) {
193                 return nil, adapter.ErrStatsDataBusy
194         }
195         dir.Epoch = sa.epoch
196
197         return dir, nil
198 }
199
200 func (c *StatsClient) UpdateDir(dir *adapter.StatDir) (err error) {
201         epoch, _ := c.getEpoch()
202         if dir.Epoch != epoch {
203                 return adapter.ErrStatsDirStale
204         }
205
206         sa := c.accessStart()
207         if sa.epoch == 0 {
208                 return adapter.ErrStatsAccessFailed
209         }
210
211         dirVector := c.getStatDirVector()
212
213         for i, index := range dir.Indexes {
214                 dirEntry := c.getStatDirIndex(dirVector, index)
215
216                 var name []byte
217                 for n := 0; n < len(dirEntry.name); n++ {
218                         if dirEntry.name[n] == 0 {
219                                 name = dirEntry.name[:n]
220                                 break
221                         }
222                 }
223                 if len(name) == 0 {
224                         continue
225                 }
226
227                 entry := &dir.Entries[i]
228                 if !bytes.Equal(name, entry.Name) {
229                         continue
230                 }
231                 if adapter.StatType(dirEntry.directoryType) != entry.Type {
232                         continue
233                 }
234                 if entry.Data == nil {
235                         continue
236                 }
237                 if err := c.updateEntryData(dirEntry, &entry.Data); err != nil {
238                         return fmt.Errorf("updating stat data for entry %s failed: %v", name, err)
239                 }
240
241         }
242
243         if !c.accessEnd(&sa) {
244                 return adapter.ErrStatsDataBusy
245         }
246
247         return nil
248 }
249
250 // listIndexes lists indexes for all stat entries that match any of the regex patterns.
251 func (c *StatsClient) listIndexes(patterns ...string) (indexes []uint32, err error) {
252         if len(patterns) == 0 {
253                 return c.listIndexesFunc(nil)
254         }
255         var regexes = make([]*regexp.Regexp, len(patterns))
256         for i, pattern := range patterns {
257                 r, err := regexp.Compile(pattern)
258                 if err != nil {
259                         return nil, fmt.Errorf("compiling regexp failed: %v", err)
260                 }
261                 regexes[i] = r
262         }
263         nameMatches := func(name []byte) bool {
264                 for _, r := range regexes {
265                         if r.Match(name) {
266                                 return true
267                         }
268                 }
269                 return false
270         }
271         return c.listIndexesFunc(nameMatches)
272 }
273
274 func (c *StatsClient) listIndexesFunc(f func(name []byte) bool) (indexes []uint32, err error) {
275         if f == nil {
276                 // there is around ~3157 stats, so to avoid too many allocations
277                 // we set capacity to 3200 when listing all stats
278                 indexes = make([]uint32, 0, 3200)
279         }
280
281         dirVector := c.getStatDirVector()
282         vecLen := uint32(vectorLen(dirVector))
283
284         for i := uint32(0); i < vecLen; i++ {
285                 dirEntry := c.getStatDirIndex(dirVector, i)
286
287                 if f != nil {
288                         var name []byte
289                         for n := 0; n < len(dirEntry.name); n++ {
290                                 if dirEntry.name[n] == 0 {
291                                         name = dirEntry.name[:n]
292                                         break
293                                 }
294                         }
295                         if len(name) == 0 || !f(name) {
296                                 continue
297                         }
298                 }
299                 indexes = append(indexes, i)
300         }
301
302         return indexes, nil
303 }
304
305 func (c *StatsClient) dumpEntries(indexes []uint32) (entries []adapter.StatEntry, err error) {
306         dirVector := c.getStatDirVector()
307         dirLen := uint32(vectorLen(dirVector))
308
309         debugf("dumping entres for %d indexes", len(indexes))
310
311         entries = make([]adapter.StatEntry, 0, len(indexes))
312         for _, index := range indexes {
313                 if index >= dirLen {
314                         return nil, fmt.Errorf("stat entry index %d out of dir vector length (%d)", index, dirLen)
315                 }
316
317                 dirEntry := c.getStatDirIndex(dirVector, index)
318
319                 var name []byte
320                 for n := 0; n < len(dirEntry.name); n++ {
321                         if dirEntry.name[n] == 0 {
322                                 name = dirEntry.name[:n]
323                                 break
324                         }
325                 }
326
327                 if Debug {
328                         debugf(" - %3d. dir: %q type: %v offset: %d union: %d", index, name,
329                                 adapter.StatType(dirEntry.directoryType), dirEntry.offsetVector, dirEntry.unionData)
330                 }
331
332                 if len(name) == 0 {
333                         continue
334                 }
335
336                 entry := adapter.StatEntry{
337                         Name: append([]byte(nil), name...),
338                         Type: adapter.StatType(dirEntry.directoryType),
339                         Data: c.copyEntryData(dirEntry),
340                 }
341                 entries = append(entries, entry)
342         }
343
344         return entries, nil
345 }