1 // Copyright (c) 2019 Cisco and/or its affiliates.
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:
7 // http://www.apache.org/licenses/LICENSE-2.0
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.
28 "github.com/ftrvxmtrx/fd"
29 logger "github.com/sirupsen/logrus"
31 "git.fd.io/govpp.git/adapter"
35 // Debug is global variable that determines debug mode
36 Debug = os.Getenv("DEBUG_GOVPP_STATS") != ""
38 // Log is global logger
42 // init initializes global logger, which logs debug level messages to stdout.
46 Log.Level = logger.DebugLevel
47 Log.Debug("enabled debug mode")
51 // StatsClient is the pure Go implementation for VPP stats API.
52 type StatsClient struct {
57 directoryVector uintptr
61 // NewStatsClient returns new VPP stats API client.
62 func NewStatsClient(socketName string) *StatsClient {
68 func (c *StatsClient) Connect() error {
71 sockName = adapter.DefaultStatsSocket
76 if _, err := os.Stat(sockName); err != nil {
77 if os.IsNotExist(err) {
78 return fmt.Errorf("stats socket file %q does not exists, ensure that VPP is running with `statseg { ... }` section in config", sockName)
80 return fmt.Errorf("stats socket file error: %v", err)
83 if err := c.statSegmentConnect(sockName); err != nil {
90 const statshmFilename = "statshm"
92 func (c *StatsClient) statSegmentConnect(sockName string) error {
93 addr := &net.UnixAddr{
98 Log.Debugf("connecting to: %v", addr)
100 conn, err := net.DialUnix(addr.Net, nil, addr)
102 Log.Warnf("connecting to socket %s failed: %s", addr, err)
106 if err := conn.Close(); err != nil {
107 Log.Warnf("closing socket failed: %v", err)
111 Log.Debugf("connected to socket: %v", addr)
113 files, err := fd.Get(conn, 1, []string{statshmFilename})
115 return fmt.Errorf("getting file descriptor over socket failed: %v", err)
116 } else if len(files) == 0 {
117 return fmt.Errorf("no files received over socket")
120 for _, f := range files {
121 if err := f.Close(); err != nil {
122 Log.Warnf("closing file %s failed: %v", f.Name(), err)
127 Log.Debugf("received %d files over socket", len(files))
131 info, err := f.Stat()
136 size := int(info.Size())
138 Log.Debugf("fd: name=%v size=%v", info.Name(), size)
140 data, err := syscall.Mmap(int(f.Fd()), 0, size, syscall.PROT_READ, syscall.MAP_SHARED)
142 Log.Warnf("mapping shared memory failed: %v", err)
143 return fmt.Errorf("mapping shared memory failed: %v", err)
146 Log.Debugf("successfuly mapped shared memory")
148 c.sharedHeader = data
154 func (c *StatsClient) Disconnect() error {
155 err := syscall.Munmap(c.sharedHeader)
157 Log.Warnf("unmapping shared memory failed: %v", err)
158 return fmt.Errorf("unmapping shared memory failed: %v", err)
161 Log.Debugf("successfuly unmapped shared memory")
166 func nameMatches(name string, patterns []string) bool {
167 if len(patterns) == 0 {
170 for _, pattern := range patterns {
171 matched, err := regexp.MatchString(pattern, name)
172 if err == nil && matched {
179 func (c *StatsClient) ListStats(patterns ...string) (statNames []string, err error) {
180 sa := c.accessStart()
182 return nil, fmt.Errorf("access failed")
185 dirOffset, _, _ := c.readOffsets()
186 Log.Debugf("dirOffset: %v", dirOffset)
188 vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirOffset]))
189 Log.Debugf("vecLen: %v", vecLen)
190 Log.Debugf("unsafe.Sizeof(statSegDirectoryEntry{}): %v", unsafe.Sizeof(statSegDirectoryEntry{}))
192 for i := uint64(0); i < vecLen; i++ {
193 offset := uintptr(i) * unsafe.Sizeof(statSegDirectoryEntry{})
194 dirEntry := (*statSegDirectoryEntry)(add(unsafe.Pointer(&c.sharedHeader[dirOffset]), offset))
196 nul := bytes.IndexByte(dirEntry.name[:], '\x00')
198 Log.Warnf("no zero byte found for: %q", dirEntry.name[:])
201 name := string(dirEntry.name[:nul])
203 Log.Debugf(" %80q (type: %v, data: %d, offset: %d) ", name, dirEntry.directoryType, dirEntry.unionData, dirEntry.offsetVector)
205 if nameMatches(name, patterns) {
206 statNames = append(statNames, name)
209 // TODO: copy the listed entries elsewhere
212 if !c.accessEnd(sa) {
213 return nil, adapter.ErrStatDirBusy
216 c.currentEpoch = sa.epoch
218 return statNames, nil
221 func (c *StatsClient) DumpStats(patterns ...string) (entries []*adapter.StatEntry, err error) {
222 epoch, _ := c.readEpoch()
223 if c.currentEpoch > 0 && c.currentEpoch != epoch { // TODO: do list stats before dump
224 return nil, fmt.Errorf("old data")
227 sa := c.accessStart()
229 return nil, fmt.Errorf("access failed")
232 dirOffset, _, _ := c.readOffsets()
233 vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirOffset]))
235 for i := uint64(0); i < vecLen; i++ {
236 offset := uintptr(i) * unsafe.Sizeof(statSegDirectoryEntry{})
237 dirEntry := (*statSegDirectoryEntry)(add(unsafe.Pointer(&c.sharedHeader[dirOffset]), offset))
239 entry := c.copyData(dirEntry)
240 if nameMatches(entry.Name, patterns) {
241 entries = append(entries, &entry)
245 if !c.accessEnd(sa) {
246 return nil, adapter.ErrStatDumpBusy
252 func (c *StatsClient) copyData(dirEntry *statSegDirectoryEntry) (statEntry adapter.StatEntry) {
253 name := dirEntry.name[:]
254 if nul := bytes.IndexByte(name, '\x00'); nul < 0 {
255 Log.Warnf("no zero byte found for: %q", dirEntry.name[:])
257 name = dirEntry.name[:nul]
260 statEntry.Name = string(name)
261 statEntry.Type = adapter.StatType(dirEntry.directoryType)
263 Log.Debugf(" - %s (type: %v, data: %v, offset: %v) ", statEntry.Name, statEntry.Type, dirEntry.unionData, dirEntry.offsetVector)
265 switch statEntry.Type {
266 case adapter.ScalarIndex:
267 statEntry.Data = adapter.ScalarStat(dirEntry.unionData)
269 case adapter.ErrorIndex:
270 _, errOffset, _ := c.readOffsets()
271 offsetVector := unsafe.Pointer(&c.sharedHeader[errOffset])
272 vecLen := vectorLen(offsetVector)
274 var errData adapter.Counter
275 for i := uint64(0); i < vecLen; i++ {
276 cb := *(*uint64)(add(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
277 offset := uintptr(cb) + uintptr(dirEntry.unionData)*unsafe.Sizeof(adapter.Counter(0))
278 val := *(*adapter.Counter)(add(unsafe.Pointer(&c.sharedHeader[0]), offset))
281 statEntry.Data = adapter.ErrorStat(errData)
283 case adapter.SimpleCounterVector:
284 if dirEntry.unionData == 0 {
285 Log.Debugf("\toffset is not valid")
287 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
288 Log.Debugf("\toffset out of range")
292 simpleCounter := unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]) // offset
293 vecLen := vectorLen(simpleCounter)
294 offsetVector := add(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
296 data := make([][]adapter.Counter, vecLen)
297 for i := uint64(0); i < vecLen; i++ {
298 cb := *(*uint64)(add(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
299 counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
300 vecLen2 := vectorLen(counterVec)
301 for j := uint64(0); j < vecLen2; j++ {
302 offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
303 val := *(*adapter.Counter)(add(counterVec, offset))
304 data[i] = append(data[i], val)
307 statEntry.Data = adapter.SimpleCounterStat(data)
309 case adapter.CombinedCounterVector:
310 if dirEntry.unionData == 0 {
311 Log.Debugf("\toffset is not valid")
313 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
314 Log.Debugf("\toffset out of range")
318 combinedCounter := unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]) // offset
319 vecLen := vectorLen(combinedCounter)
320 offsetVector := add(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
322 data := make([][]adapter.CombinedCounter, vecLen)
323 for i := uint64(0); i < vecLen; i++ {
324 cb := *(*uint64)(add(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
325 counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
326 vecLen2 := vectorLen(counterVec)
327 for j := uint64(0); j < vecLen2; j++ {
328 offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
329 val := *(*adapter.CombinedCounter)(add(counterVec, offset))
330 data[i] = append(data[i], val)
333 statEntry.Data = adapter.CombinedCounterStat(data)
335 case adapter.NameVector:
336 if dirEntry.unionData == 0 {
337 Log.Debugf("\toffset is not valid")
339 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
340 Log.Debugf("\toffset out of range")
344 nameVector := unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]) // offset
345 vecLen := vectorLen(nameVector)
346 offsetVector := add(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
348 data := make([]adapter.Name, vecLen)
349 for i := uint64(0); i < vecLen; i++ {
350 cb := *(*uint64)(add(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
351 nameVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
352 vecLen2 := vectorLen(nameVec)
355 for j := uint64(0); j < vecLen2; j++ {
356 offset := uintptr(j) * unsafe.Sizeof(byte(0))
357 val := *(*byte)(add(nameVec, offset))
359 nameStr = append(nameStr, val)
362 data[i] = adapter.Name(nameStr)
364 statEntry.Data = adapter.NameStat(data)
367 Log.Warnf("Unknown type %d for stat entry: %s", statEntry.Type, statEntry.Name)
370 Log.Debugf("\tentry data: %#v", statEntry.Data)
375 type statDirectoryType int32
377 func (t statDirectoryType) String() string {
378 return adapter.StatType(t).String()
381 type statSegDirectoryEntry struct {
382 directoryType statDirectoryType
383 // unionData can represent: offset, index or value
389 type statSegSharedHeader struct {
393 directoryOffset int64
398 func (c *StatsClient) readVersion() uint64 {
399 header := *(*statSegSharedHeader)(unsafe.Pointer(&c.sharedHeader[0]))
400 version := atomic.LoadUint64(&header.version)
404 func (c *StatsClient) readEpoch() (int64, bool) {
405 header := *(*statSegSharedHeader)(unsafe.Pointer(&c.sharedHeader[0]))
406 epoch := atomic.LoadInt64(&header.epoch)
407 inprog := atomic.LoadInt64(&header.inProgress)
408 return epoch, inprog != 0
411 func (c *StatsClient) readOffsets() (dir, err, stat int64) {
412 header := *(*statSegSharedHeader)(unsafe.Pointer(&c.sharedHeader[0]))
413 dirOffset := atomic.LoadInt64(&header.directoryOffset)
414 errOffset := atomic.LoadInt64(&header.errorOffset)
415 statOffset := atomic.LoadInt64(&header.statsOffset)
416 return dirOffset, errOffset, statOffset
419 type statSegAccess struct {
423 var maxWaitInProgress = 1 * time.Second
425 func (c *StatsClient) accessStart() *statSegAccess {
426 epoch, inprog := c.readEpoch()
429 if time.Since(t) > maxWaitInProgress {
432 epoch, inprog = c.readEpoch()
434 return &statSegAccess{
439 func (c *StatsClient) accessEnd(acc *statSegAccess) bool {
440 epoch, inprog := c.readEpoch()
441 if acc.epoch != epoch || inprog {
447 type vecHeader struct {
452 func vectorLen(v unsafe.Pointer) uint64 {
453 vec := *(*vecHeader)(unsafe.Pointer(uintptr(v) - unsafe.Sizeof(uintptr(0))))
458 func add(p unsafe.Pointer, x uintptr) unsafe.Pointer {
459 return unsafe.Pointer(uintptr(p) + x)