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.
15 // Package statsclient is pure Go implementation of VPP stats API client.
25 logger "github.com/sirupsen/logrus"
27 "git.fd.io/govpp.git/adapter"
31 // DefaultSocketName is default VPP stats socket file path.
32 DefaultSocketName = adapter.DefaultStatsSocket
35 const socketMissing = `
36 ------------------------------------------------------------
37 VPP stats socket file %s is missing!
39 - is VPP running with stats segment enabled?
40 - is the correct socket name configured?
42 To enable it add following section to your VPP config:
46 ------------------------------------------------------------
50 // Debug is global variable that determines debug mode
51 Debug = os.Getenv("DEBUG_GOVPP_STATS") != ""
53 // Log is global logger
57 // init initializes global logger, which logs debug level messages to stdout.
61 Log.Level = logger.DebugLevel
62 Log.Debug("govpp/statsclient: enabled debug mode")
66 // StatsClient is the pure Go implementation for VPP stats API.
67 type StatsClient struct {
74 // NewStatsClient returns new VPP stats API client.
75 func NewStatsClient(sockAddr string) *StatsClient {
77 sockAddr = DefaultSocketName
84 func (c *StatsClient) Connect() error {
85 // check if socket exists
86 if _, err := os.Stat(c.sockAddr); os.IsNotExist(err) {
87 fmt.Fprintf(os.Stderr, socketMissing, c.sockAddr)
88 return fmt.Errorf("stats socket file %s does not exist", c.sockAddr)
89 } else if err != nil {
90 return fmt.Errorf("stats socket error: %v", err)
93 if err := c.statSegment.connect(c.sockAddr); err != nil {
97 ver := c.readVersion()
98 Log.Debugf("stat segment version: %v", ver)
100 if err := checkVersion(ver); err != nil {
107 func (c *StatsClient) Disconnect() error {
108 if err := c.statSegment.disconnect(); err != nil {
115 func (c *StatsClient) ListStats(patterns ...string) (statNames []string, err error) {
116 sa := c.accessStart()
118 return nil, fmt.Errorf("access failed")
121 dirOffset, _, _ := c.readOffsets()
122 Log.Debugf("dirOffset: %v", dirOffset)
124 vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirOffset]))
125 Log.Debugf("vecLen: %v", vecLen)
126 Log.Debugf("unsafe.Sizeof(statSegDirectoryEntry{}): %v", unsafe.Sizeof(statSegDirectoryEntry{}))
128 for i := uint64(0); i < vecLen; i++ {
129 offset := uintptr(i) * unsafe.Sizeof(statSegDirectoryEntry{})
130 dirEntry := (*statSegDirectoryEntry)(add(unsafe.Pointer(&c.sharedHeader[dirOffset]), offset))
132 nul := bytes.IndexByte(dirEntry.name[:], '\x00')
134 Log.Debugf("no zero byte found for: %q", dirEntry.name[:])
137 name := string(dirEntry.name[:nul])
139 Log.Debugf("entry with empty name found (%d)", i)
143 Log.Debugf(" %80q (type: %v, data: %d, offset: %d) ", name, dirEntry.directoryType, dirEntry.unionData, dirEntry.offsetVector)
145 if nameMatches(name, patterns) {
146 statNames = append(statNames, name)
149 // TODO: copy the listed entries elsewhere
152 if !c.accessEnd(sa) {
153 return nil, adapter.ErrStatDirBusy
156 c.currentEpoch = sa.epoch
158 return statNames, nil
161 func (c *StatsClient) DumpStats(patterns ...string) (entries []*adapter.StatEntry, err error) {
162 epoch, _ := c.readEpoch()
163 if c.currentEpoch > 0 && c.currentEpoch != epoch { // TODO: do list stats before dump
164 return nil, fmt.Errorf("old data")
167 sa := c.accessStart()
169 return nil, fmt.Errorf("access failed")
172 dirOffset, _, _ := c.readOffsets()
173 vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirOffset]))
175 for i := uint64(0); i < vecLen; i++ {
176 offset := uintptr(i) * unsafe.Sizeof(statSegDirectoryEntry{})
177 dirEntry := (*statSegDirectoryEntry)(add(unsafe.Pointer(&c.sharedHeader[dirOffset]), offset))
179 nul := bytes.IndexByte(dirEntry.name[:], '\x00')
181 Log.Debugf("no zero byte found for: %q", dirEntry.name[:])
184 name := string(dirEntry.name[:nul])
186 Log.Debugf("entry with empty name found (%d)", i)
190 Log.Debugf(" - %s (type: %v, data: %v, offset: %v) ", name, dirEntry.directoryType, dirEntry.unionData, dirEntry.offsetVector)
192 entry := adapter.StatEntry{
194 Type: adapter.StatType(dirEntry.directoryType),
195 Data: c.copyData(dirEntry),
198 Log.Debugf("\tentry data: %+v %#v (%T)", entry.Data, entry.Data, entry.Data)
200 if nameMatches(entry.Name, patterns) {
201 entries = append(entries, &entry)
205 if !c.accessEnd(sa) {
206 return nil, adapter.ErrStatDumpBusy
212 func nameMatches(name string, patterns []string) bool {
213 if len(patterns) == 0 {
216 for _, pattern := range patterns {
217 matched, err := regexp.MatchString(pattern, name)
218 if err == nil && matched {