Optimizations for statsclient
[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         for _, index := range indexes {
125                 name, err := c.entryName(index)
126                 if err != nil {
127                         return nil, err
128                 }
129                 names = append(names, name)
130         }
131
132         if !c.accessEnd(&sa) {
133                 return nil, adapter.ErrStatsDataBusy
134         }
135
136         return names, nil
137 }
138
139 func (c *StatsClient) DumpStats(patterns ...string) (entries []adapter.StatEntry, err error) {
140         sa := c.accessStart()
141         if sa.epoch == 0 {
142                 return nil, adapter.ErrStatsAccessFailed
143         }
144
145         dir, err := c.listIndexes(patterns...)
146         if err != nil {
147                 return nil, err
148         }
149         if entries, err = c.dumpEntries(dir); err != nil {
150                 return nil, err
151         }
152
153         if !c.accessEnd(&sa) {
154                 return nil, adapter.ErrStatsDataBusy
155         }
156
157         return entries, nil
158 }
159
160 func (c *StatsClient) PrepareDir(patterns ...string) (*adapter.StatDir, error) {
161         dir := new(adapter.StatDir)
162
163         sa := c.accessStart()
164         if sa.epoch == 0 {
165                 return nil, adapter.ErrStatsAccessFailed
166         }
167
168         indexes, err := c.listIndexes(patterns...)
169         if err != nil {
170                 return nil, err
171         }
172         dir.Indexes = indexes
173
174         entries, err := c.dumpEntries(indexes)
175         if err != nil {
176                 return nil, err
177         }
178         dir.Entries = entries
179
180         if !c.accessEnd(&sa) {
181                 return nil, adapter.ErrStatsDataBusy
182         }
183         dir.Epoch = sa.epoch
184
185         return dir, nil
186 }
187
188 func (c *StatsClient) UpdateDir(dir *adapter.StatDir) (err error) {
189         epoch, _ := c.getEpoch()
190         if dir.Epoch != epoch {
191                 return adapter.ErrStatsDirStale
192         }
193
194         sa := c.accessStart()
195         if sa.epoch == 0 {
196                 return adapter.ErrStatsAccessFailed
197         }
198
199         dirVector := c.getStatDirVector()
200
201         for i, index := range dir.Indexes {
202                 dirEntry := c.getStatDirIndex(dirVector, index)
203
204                 var name []byte
205                 for n := 0; n < len(dirEntry.name); n++ {
206                         if dirEntry.name[n] == 0 {
207                                 name = dirEntry.name[:n]
208                                 break
209                         }
210                 }
211                 if len(name) == 0 {
212                         continue
213                 }
214
215                 entry := &dir.Entries[i]
216                 if !bytes.Equal(name, entry.Name) {
217                         continue
218                 }
219                 if adapter.StatType(dirEntry.directoryType) != entry.Type {
220                         continue
221                 }
222                 if entry.Data == nil {
223                         continue
224                 }
225                 if err := c.updateEntryData(dirEntry, &entry.Data); err != nil {
226                         return fmt.Errorf("updating stat data for entry %s failed: %v", name, err)
227                 }
228
229         }
230
231         if !c.accessEnd(&sa) {
232                 return adapter.ErrStatsDataBusy
233         }
234
235         return nil
236 }
237
238 // listIndexes lists indexes for all stat entries that match any of the regex patterns.
239 func (c *StatsClient) listIndexes(patterns ...string) (indexes []uint32, err error) {
240         if len(patterns) == 0 {
241                 return c.listIndexesFunc(nil)
242         }
243         var regexes = make([]*regexp.Regexp, len(patterns))
244         for i, pattern := range patterns {
245                 r, err := regexp.Compile(pattern)
246                 if err != nil {
247                         return nil, fmt.Errorf("compiling regexp failed: %v", err)
248                 }
249                 regexes[i] = r
250         }
251         nameMatches := func(name []byte) bool {
252                 for _, r := range regexes {
253                         if r.Match(name) {
254                                 return true
255                         }
256                 }
257                 return false
258         }
259         return c.listIndexesFunc(nameMatches)
260 }
261
262 func (c *StatsClient) listIndexesFunc(f func(name []byte) bool) (indexes []uint32, err error) {
263         if f == nil {
264                 // there is around ~3150 stats, so to avoid too many allocations
265                 // we set capacity to 3200 when listing all stats
266                 indexes = make([]uint32, 0, 3200)
267         }
268
269         dirVector := c.getStatDirVector()
270         vecLen := uint32(vectorLen(dirVector))
271
272         for i := uint32(0); i < vecLen; i++ {
273                 dirEntry := c.getStatDirIndex(dirVector, i)
274
275                 if f != nil {
276                         var name []byte
277                         for n := 0; n < len(dirEntry.name); n++ {
278                                 if dirEntry.name[n] == 0 {
279                                         name = dirEntry.name[:n]
280                                         break
281                                 }
282                         }
283                         if len(name) == 0 || !f(name) {
284                                 continue
285                         }
286                 }
287                 indexes = append(indexes, i)
288         }
289
290         return indexes, nil
291 }
292
293 func (c *StatsClient) entryName(index uint32) (string, error) {
294         dirVector := c.getStatDirVector()
295         vecLen := uint32(vectorLen(dirVector))
296
297         if index >= vecLen {
298                 return "", fmt.Errorf("stat entry index %d out of range (%d)", index, vecLen)
299         }
300
301         dirEntry := c.getStatDirIndex(dirVector, index)
302
303         var name []byte
304         for n := 0; n < len(dirEntry.name); n++ {
305                 if dirEntry.name[n] == 0 {
306                         name = dirEntry.name[:n]
307                         break
308                 }
309         }
310
311         return string(name), nil
312 }
313
314 func (c *StatsClient) dumpEntries(indexes []uint32) (entries []adapter.StatEntry, err error) {
315         entries = make([]adapter.StatEntry, 0, len(indexes))
316
317         dirVector := c.getStatDirVector()
318
319         for _, index := range indexes {
320                 dirEntry := c.getStatDirIndex(dirVector, index)
321
322                 var name []byte
323                 for n := 0; n < len(dirEntry.name); n++ {
324                         if dirEntry.name[n] == 0 {
325                                 name = dirEntry.name[:n]
326                                 break
327                         }
328                 }
329                 if len(name) == 0 {
330                         continue
331                 }
332
333                 entry := adapter.StatEntry{
334                         Name: append([]byte(nil), name...),
335                         Type: adapter.StatType(dirEntry.directoryType),
336                         Data: c.copyEntryData(dirEntry),
337                 }
338                 entries = append(entries, entry)
339         }
340
341         return entries, nil
342 }