Fix reading statsclient entries
[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("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 = `VPP stats socket not found at: %s!
66         Check if VPP is running with stats segment enabled.
67         To enable it include following section in VPP config:
68           statseg {
69             default
70           }
71 `
72
73 func (c *StatsClient) Connect() error {
74         // check if socket exists
75         if _, err := os.Stat(c.sockAddr); os.IsNotExist(err) {
76                 Log.Warnf(sockNotFoundWarn, c.sockAddr)
77                 return fmt.Errorf("stats socket file %s does not exist", c.sockAddr)
78         } else if err != nil {
79                 return fmt.Errorf("stats socket error: %v", err)
80         }
81
82         if err := c.statSegment.connect(c.sockAddr); err != nil {
83                 return err
84         }
85
86         ver := c.readVersion()
87         Log.Debugf("stat segment version: %v", ver)
88
89         if err := checkVersion(ver); err != nil {
90                 return err
91         }
92
93         return nil
94 }
95
96 func (c *StatsClient) Disconnect() error {
97         if err := c.statSegment.disconnect(); err != nil {
98                 return err
99         }
100
101         return nil
102 }
103
104 func (c *StatsClient) ListStats(patterns ...string) (statNames []string, err error) {
105         sa := c.accessStart()
106         if sa == nil {
107                 return nil, fmt.Errorf("access failed")
108         }
109
110         dirOffset, _, _ := c.readOffsets()
111         Log.Debugf("dirOffset: %v", dirOffset)
112
113         vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirOffset]))
114         Log.Debugf("vecLen: %v", vecLen)
115         Log.Debugf("unsafe.Sizeof(statSegDirectoryEntry{}): %v", unsafe.Sizeof(statSegDirectoryEntry{}))
116
117         for i := uint64(0); i < vecLen; i++ {
118                 offset := uintptr(i) * unsafe.Sizeof(statSegDirectoryEntry{})
119                 dirEntry := (*statSegDirectoryEntry)(add(unsafe.Pointer(&c.sharedHeader[dirOffset]), offset))
120
121                 nul := bytes.IndexByte(dirEntry.name[:], '\x00')
122                 if nul < 0 {
123                         Log.Debugf("no zero byte found for: %q", dirEntry.name[:])
124                         continue
125                 }
126                 name := string(dirEntry.name[:nul])
127                 if name == "" {
128                         Log.Debugf("entry with empty name found (%d)", i)
129                         continue
130                 }
131
132                 Log.Debugf(" %80q (type: %v, data: %d, offset: %d) ", name, dirEntry.directoryType, dirEntry.unionData, dirEntry.offsetVector)
133
134                 if nameMatches(name, patterns) {
135                         statNames = append(statNames, name)
136                 }
137
138                 // TODO: copy the listed entries elsewhere
139         }
140
141         if !c.accessEnd(sa) {
142                 return nil, adapter.ErrStatDirBusy
143         }
144
145         c.currentEpoch = sa.epoch
146
147         return statNames, nil
148 }
149
150 func (c *StatsClient) DumpStats(patterns ...string) (entries []*adapter.StatEntry, err error) {
151         epoch, _ := c.readEpoch()
152         if c.currentEpoch > 0 && c.currentEpoch != epoch { // TODO: do list stats before dump
153                 return nil, fmt.Errorf("old data")
154         }
155
156         sa := c.accessStart()
157         if sa == nil {
158                 return nil, fmt.Errorf("access failed")
159         }
160
161         dirOffset, _, _ := c.readOffsets()
162         vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirOffset]))
163
164         for i := uint64(0); i < vecLen; i++ {
165                 offset := uintptr(i) * unsafe.Sizeof(statSegDirectoryEntry{})
166                 dirEntry := (*statSegDirectoryEntry)(add(unsafe.Pointer(&c.sharedHeader[dirOffset]), offset))
167
168                 nul := bytes.IndexByte(dirEntry.name[:], '\x00')
169                 if nul < 0 {
170                         Log.Debugf("no zero byte found for: %q", dirEntry.name[:])
171                         continue
172                 }
173                 name := string(dirEntry.name[:nul])
174                 if name == "" {
175                         Log.Debugf("entry with empty name found (%d)", i)
176                         continue
177                 }
178
179                 Log.Debugf(" - %s (type: %v, data: %v, offset: %v) ", name, dirEntry.directoryType, dirEntry.unionData, dirEntry.offsetVector)
180
181                 entry := adapter.StatEntry{
182                         Name: name,
183                         Type: adapter.StatType(dirEntry.directoryType),
184                         Data: c.copyData(dirEntry),
185                 }
186
187                 Log.Debugf("\tentry data: %#v", entry.Data)
188
189                 if nameMatches(entry.Name, patterns) {
190                         entries = append(entries, &entry)
191                 }
192         }
193
194         if !c.accessEnd(sa) {
195                 return nil, adapter.ErrStatDumpBusy
196         }
197
198         return entries, nil
199 }
200
201 func (c *StatsClient) copyData(dirEntry *statSegDirectoryEntry) adapter.Stat {
202         switch typ := adapter.StatType(dirEntry.directoryType); typ {
203         case adapter.ScalarIndex:
204                 return adapter.ScalarStat(dirEntry.unionData)
205
206         case adapter.ErrorIndex:
207                 _, errOffset, _ := c.readOffsets()
208                 offsetVector := unsafe.Pointer(&c.sharedHeader[errOffset])
209                 vecLen := vectorLen(offsetVector)
210
211                 var errData adapter.Counter
212                 for i := uint64(0); i < vecLen; i++ {
213                         cb := *(*uint64)(add(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
214                         offset := uintptr(cb) + uintptr(dirEntry.unionData)*unsafe.Sizeof(adapter.Counter(0))
215                         val := *(*adapter.Counter)(add(unsafe.Pointer(&c.sharedHeader[0]), offset))
216                         errData += val
217                 }
218                 return adapter.ErrorStat(errData)
219
220         case adapter.SimpleCounterVector:
221                 if dirEntry.unionData == 0 {
222                         Log.Debugf("\toffset is not valid")
223                         break
224                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
225                         Log.Debugf("\toffset out of range")
226                         break
227                 }
228
229                 simpleCounter := unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]) // offset
230                 vecLen := vectorLen(simpleCounter)
231                 offsetVector := add(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
232
233                 data := make([][]adapter.Counter, vecLen)
234                 for i := uint64(0); i < vecLen; i++ {
235                         cb := *(*uint64)(add(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
236                         counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
237                         vecLen2 := vectorLen(counterVec)
238                         for j := uint64(0); j < vecLen2; j++ {
239                                 offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
240                                 val := *(*adapter.Counter)(add(counterVec, offset))
241                                 data[i] = append(data[i], val)
242                         }
243                 }
244                 return adapter.SimpleCounterStat(data)
245
246         case adapter.CombinedCounterVector:
247                 if dirEntry.unionData == 0 {
248                         Log.Debugf("\toffset is not valid")
249                         break
250                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
251                         Log.Debugf("\toffset out of range")
252                         break
253                 }
254
255                 combinedCounter := unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]) // offset
256                 vecLen := vectorLen(combinedCounter)
257                 offsetVector := add(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
258
259                 data := make([][]adapter.CombinedCounter, vecLen)
260                 for i := uint64(0); i < vecLen; i++ {
261                         cb := *(*uint64)(add(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
262                         counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
263                         vecLen2 := vectorLen(counterVec)
264                         for j := uint64(0); j < vecLen2; j++ {
265                                 offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
266                                 val := *(*adapter.CombinedCounter)(add(counterVec, offset))
267                                 data[i] = append(data[i], val)
268                         }
269                 }
270                 return adapter.CombinedCounterStat(data)
271
272         case adapter.NameVector:
273                 if dirEntry.unionData == 0 {
274                         Log.Debugf("\toffset is not valid")
275                         break
276                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
277                         Log.Debugf("\toffset out of range")
278                         break
279                 }
280
281                 nameVector := unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]) // offset
282                 vecLen := vectorLen(nameVector)
283                 offsetVector := add(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
284
285                 data := make([]adapter.Name, vecLen)
286                 for i := uint64(0); i < vecLen; i++ {
287                         cb := *(*uint64)(add(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
288                         if cb == 0 {
289                                 Log.Debugf("\tname vector cb out of range")
290                                 continue
291                         }
292                         nameVec := unsafe.Pointer(&c.sharedHeader[cb])
293                         vecLen2 := vectorLen(nameVec)
294
295                         var nameStr []byte
296                         for j := uint64(0); j < vecLen2; j++ {
297                                 offset := uintptr(j) * unsafe.Sizeof(byte(0))
298                                 val := *(*byte)(add(nameVec, offset))
299                                 if val > 0 {
300                                         nameStr = append(nameStr, val)
301                                 }
302                         }
303                         data[i] = adapter.Name(nameStr)
304                 }
305                 return adapter.NameStat(data)
306
307         default:
308                 Log.Warnf("Unknown type %d for stat entry: %q", dirEntry.directoryType, dirEntry.name)
309         }
310
311         return nil
312 }
313
314 func nameMatches(name string, patterns []string) bool {
315         if len(patterns) == 0 {
316                 return true
317         }
318         for _, pattern := range patterns {
319                 matched, err := regexp.MatchString(pattern, name)
320                 if err == nil && matched {
321                         return true
322                 }
323         }
324         return false
325 }