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 // Debug is global variable that determines debug mode
32 Debug = os.Getenv("DEBUG_GOVPP_STATS") != ""
34 // Log is global logger
38 // init initializes global logger, which logs debug level messages to stdout.
42 Log.Level = logger.DebugLevel
43 Log.Debug("enabled debug mode")
47 // StatsClient is the pure Go implementation for VPP stats API.
48 type StatsClient struct {
55 // NewStatsClient returns new VPP stats API client.
56 func NewStatsClient(sockAddr string) *StatsClient {
58 sockAddr = adapter.DefaultStatsSocket
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:
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)
82 if err := c.statSegment.connect(c.sockAddr); err != nil {
86 ver := c.readVersion()
87 Log.Debugf("stat segment version: %v", ver)
89 if err := checkVersion(ver); err != nil {
96 func (c *StatsClient) Disconnect() error {
97 if err := c.statSegment.disconnect(); err != nil {
104 func (c *StatsClient) ListStats(patterns ...string) (statNames []string, err error) {
105 sa := c.accessStart()
107 return nil, fmt.Errorf("access failed")
110 dirOffset, _, _ := c.readOffsets()
111 Log.Debugf("dirOffset: %v", dirOffset)
113 vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirOffset]))
114 Log.Debugf("vecLen: %v", vecLen)
115 Log.Debugf("unsafe.Sizeof(statSegDirectoryEntry{}): %v", unsafe.Sizeof(statSegDirectoryEntry{}))
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))
121 nul := bytes.IndexByte(dirEntry.name[:], '\x00')
123 Log.Warnf("no zero byte found for: %q", dirEntry.name[:])
126 name := string(dirEntry.name[:nul])
128 Log.Debugf(" %80q (type: %v, data: %d, offset: %d) ", name, dirEntry.directoryType, dirEntry.unionData, dirEntry.offsetVector)
130 if nameMatches(name, patterns) {
131 statNames = append(statNames, name)
134 // TODO: copy the listed entries elsewhere
137 if !c.accessEnd(sa) {
138 return nil, adapter.ErrStatDirBusy
141 c.currentEpoch = sa.epoch
143 return statNames, nil
146 func (c *StatsClient) DumpStats(patterns ...string) (entries []*adapter.StatEntry, err error) {
147 epoch, _ := c.readEpoch()
148 if c.currentEpoch > 0 && c.currentEpoch != epoch { // TODO: do list stats before dump
149 return nil, fmt.Errorf("old data")
152 sa := c.accessStart()
154 return nil, fmt.Errorf("access failed")
157 dirOffset, _, _ := c.readOffsets()
158 vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirOffset]))
160 for i := uint64(0); i < vecLen; i++ {
161 offset := uintptr(i) * unsafe.Sizeof(statSegDirectoryEntry{})
162 dirEntry := (*statSegDirectoryEntry)(add(unsafe.Pointer(&c.sharedHeader[dirOffset]), offset))
164 entry := c.copyData(dirEntry)
165 if nameMatches(entry.Name, patterns) {
166 entries = append(entries, &entry)
170 if !c.accessEnd(sa) {
171 return nil, adapter.ErrStatDumpBusy
177 func (c *StatsClient) copyData(dirEntry *statSegDirectoryEntry) (statEntry adapter.StatEntry) {
178 name := dirEntry.name[:]
179 if nul := bytes.IndexByte(name, '\x00'); nul < 0 {
180 Log.Warnf("no zero byte found for: %q", dirEntry.name[:])
182 name = dirEntry.name[:nul]
185 statEntry.Name = string(name)
186 statEntry.Type = adapter.StatType(dirEntry.directoryType)
188 Log.Debugf(" - %s (type: %v, data: %v, offset: %v) ", statEntry.Name, statEntry.Type, dirEntry.unionData, dirEntry.offsetVector)
190 switch statEntry.Type {
191 case adapter.ScalarIndex:
192 statEntry.Data = adapter.ScalarStat(dirEntry.unionData)
194 case adapter.ErrorIndex:
195 _, errOffset, _ := c.readOffsets()
196 offsetVector := unsafe.Pointer(&c.sharedHeader[errOffset])
197 vecLen := vectorLen(offsetVector)
199 var errData adapter.Counter
200 for i := uint64(0); i < vecLen; i++ {
201 cb := *(*uint64)(add(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
202 offset := uintptr(cb) + uintptr(dirEntry.unionData)*unsafe.Sizeof(adapter.Counter(0))
203 val := *(*adapter.Counter)(add(unsafe.Pointer(&c.sharedHeader[0]), offset))
206 statEntry.Data = adapter.ErrorStat(errData)
208 case adapter.SimpleCounterVector:
209 if dirEntry.unionData == 0 {
210 Log.Debugf("\toffset is not valid")
212 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
213 Log.Debugf("\toffset out of range")
217 simpleCounter := unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]) // offset
218 vecLen := vectorLen(simpleCounter)
219 offsetVector := add(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
221 data := make([][]adapter.Counter, vecLen)
222 for i := uint64(0); i < vecLen; i++ {
223 cb := *(*uint64)(add(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
224 counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
225 vecLen2 := vectorLen(counterVec)
226 for j := uint64(0); j < vecLen2; j++ {
227 offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
228 val := *(*adapter.Counter)(add(counterVec, offset))
229 data[i] = append(data[i], val)
232 statEntry.Data = adapter.SimpleCounterStat(data)
234 case adapter.CombinedCounterVector:
235 if dirEntry.unionData == 0 {
236 Log.Debugf("\toffset is not valid")
238 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
239 Log.Debugf("\toffset out of range")
243 combinedCounter := unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]) // offset
244 vecLen := vectorLen(combinedCounter)
245 offsetVector := add(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
247 data := make([][]adapter.CombinedCounter, vecLen)
248 for i := uint64(0); i < vecLen; i++ {
249 cb := *(*uint64)(add(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
250 counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
251 vecLen2 := vectorLen(counterVec)
252 for j := uint64(0); j < vecLen2; j++ {
253 offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
254 val := *(*adapter.CombinedCounter)(add(counterVec, offset))
255 data[i] = append(data[i], val)
258 statEntry.Data = adapter.CombinedCounterStat(data)
260 case adapter.NameVector:
261 if dirEntry.unionData == 0 {
262 Log.Debugf("\toffset is not valid")
264 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
265 Log.Debugf("\toffset out of range")
269 nameVector := unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]) // offset
270 vecLen := vectorLen(nameVector)
271 offsetVector := add(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
272 fmt.Printf("vecLen: %v\n", vecLen)
274 data := make([]adapter.Name, vecLen)
275 for i := uint64(0); i < vecLen; i++ {
276 cb := *(*uint64)(add(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
278 Log.Debugf("\tname vector cb out of range")
281 nameVec := unsafe.Pointer(&c.sharedHeader[cb])
282 fmt.Printf("offsetVector: %v, cb: %v\n", offsetVector, cb)
283 vecLen2 := vectorLen(nameVec)
286 for j := uint64(0); j < vecLen2; j++ {
287 offset := uintptr(j) * unsafe.Sizeof(byte(0))
288 val := *(*byte)(add(nameVec, offset))
290 nameStr = append(nameStr, val)
293 data[i] = adapter.Name(nameStr)
295 statEntry.Data = adapter.NameStat(data)
298 Log.Warnf("Unknown type %d for stat entry: %s", statEntry.Type, statEntry.Name)
301 Log.Debugf("\tentry data: %#v", statEntry.Data)
306 func nameMatches(name string, patterns []string) bool {
307 if len(patterns) == 0 {
310 for _, pattern := range patterns {
311 matched, err := regexp.MatchString(pattern, name)
312 if err == nil && matched {