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.
27 "git.fd.io/govpp.git/adapter"
28 "github.com/ftrvxmtrx/fd"
29 logger "github.com/sirupsen/logrus"
33 // DefaultSocketName is default VPP stats socket file path.
34 DefaultSocketName = adapter.DefaultStatsSocket
37 const socketMissing = `
38 ------------------------------------------------------------
39 VPP stats socket file %s is missing!
41 - is VPP running with stats segment enabled?
42 - is the correct socket name configured?
44 To enable it add following section to your VPP config:
46 socket-name /run/vpp/stats.sock
48 ------------------------------------------------------------
52 // Debug is global variable that determines debug mode
53 Debug = os.Getenv("DEBUG_GOVPP_STATS") != ""
55 // Log is global logger
59 // init initializes global logger, which logs debug level messages to stdout.
63 Log.Level = logger.DebugLevel
64 Log.Debug("govpp/statsclient: enabled debug mode")
68 func debugf(f string, a ...interface{}) {
74 // implements StatsAPI
75 var _ adapter.StatsAPI = (*StatsClient)(nil)
77 // StatsClient is the pure Go implementation for VPP stats API.
78 type StatsClient struct {
86 // NewStatsClient returns new VPP stats API client.
87 func NewStatsClient(sockAddr string) *StatsClient {
89 sockAddr = DefaultSocketName
95 // Connect to the VPP stats socket
96 func (sc *StatsClient) Connect() (err error) {
97 // check if socket exists
98 if _, err := os.Stat(sc.sockAddr); os.IsNotExist(err) {
99 fmt.Fprintf(os.Stderr, socketMissing, sc.sockAddr)
100 return fmt.Errorf("stats socket file %s does not exist", sc.sockAddr)
101 } else if err != nil {
102 return fmt.Errorf("stats socket error: %v", err)
105 return fmt.Errorf("already connected")
107 if sc.statSegment, err = sc.connect(); err != nil {
110 sc.isConnected = true
114 // Disconnect from the socket and unmap shared memory
115 func (sc *StatsClient) Disconnect() error {
116 sc.isConnected = false
117 if sc.headerData == nil {
120 if err := syscall.Munmap(sc.headerData); err != nil {
121 Log.Debugf("unmapping shared memory failed: %v", err)
122 return fmt.Errorf("unmapping shared memory failed: %v", err)
126 Log.Debugf("successfully unmapped shared memory")
130 func (sc *StatsClient) ListStats(patterns ...string) ([]string, error) {
131 accessEpoch := sc.accessStart()
132 if accessEpoch == 0 {
133 return nil, adapter.ErrStatsAccessFailed
136 indexes, err := sc.listIndexes(patterns...)
141 dirVector := sc.GetDirectoryVector()
142 if dirVector == nil {
143 return nil, fmt.Errorf("failed to list stats: %v", err)
145 vecLen := *(*uint32)(vectorLen(dirVector))
148 for _, index := range indexes {
150 return nil, fmt.Errorf("stat entry index %d out of dir vector len (%d)", index, vecLen)
152 _, dirName, _ := sc.GetStatDirOnIndex(dirVector, index)
153 names = append(names, string(dirName))
156 if !sc.accessEnd(accessEpoch) {
157 return nil, adapter.ErrStatsDataBusy
163 func (sc *StatsClient) DumpStats(patterns ...string) (entries []adapter.StatEntry, err error) {
164 accessEpoch := sc.accessStart()
165 if accessEpoch == 0 {
166 return nil, adapter.ErrStatsAccessFailed
169 indexes, err := sc.listIndexes(patterns...)
174 dirVector := sc.GetDirectoryVector()
175 if dirVector == nil {
178 dirLen := *(*uint32)(vectorLen(dirVector))
180 debugf("dumping entries for %d indexes", len(indexes))
182 entries = make([]adapter.StatEntry, 0, len(indexes))
183 for _, index := range indexes {
185 return nil, fmt.Errorf("stat entry index %d out of dir vector length (%d)", index, dirLen)
187 dirPtr, dirName, dirType := sc.GetStatDirOnIndex(dirVector, index)
188 if len(dirName) == 0 {
191 entry := adapter.StatEntry{
192 Name: append([]byte(nil), dirName...),
193 Type: adapter.StatType(dirType),
194 Data: sc.CopyEntryData(dirPtr),
196 entries = append(entries, entry)
199 if !sc.accessEnd(accessEpoch) {
200 return nil, adapter.ErrStatsDataBusy
206 func (sc *StatsClient) PrepareDir(patterns ...string) (*adapter.StatDir, error) {
207 dir := new(adapter.StatDir)
209 accessEpoch := sc.accessStart()
210 if accessEpoch == 0 {
211 return nil, adapter.ErrStatsAccessFailed
214 indexes, err := sc.listIndexes(patterns...)
218 dir.Indexes = indexes
220 dirVector := sc.GetDirectoryVector()
221 if dirVector == nil {
224 dirLen := *(*uint32)(vectorLen(dirVector))
226 debugf("dumping entries for %d indexes", len(indexes))
228 entries := make([]adapter.StatEntry, 0, len(indexes))
229 for _, index := range indexes {
231 return nil, fmt.Errorf("stat entry index %d out of dir vector length (%d)", index, dirLen)
233 dirPtr, dirName, dirType := sc.GetStatDirOnIndex(dirVector, index)
234 if len(dirName) == 0 {
237 entry := adapter.StatEntry{
238 Name: append([]byte(nil), dirName...),
239 Type: adapter.StatType(dirType),
240 Data: sc.CopyEntryData(dirPtr),
242 entries = append(entries, entry)
244 dir.Entries = entries
246 if !sc.accessEnd(accessEpoch) {
247 return nil, adapter.ErrStatsDataBusy
249 dir.Epoch = accessEpoch
254 // UpdateDir refreshes directory data for all counters
255 func (sc *StatsClient) UpdateDir(dir *adapter.StatDir) (err error) {
256 epoch, _ := sc.GetEpoch()
257 if dir.Epoch != epoch {
258 return adapter.ErrStatsDirStale
261 accessEpoch := sc.accessStart()
262 if accessEpoch == 0 {
263 return adapter.ErrStatsAccessFailed
266 dirVector := sc.GetDirectoryVector()
267 if dirVector == nil {
270 for i, index := range dir.Indexes {
271 statSegDir, dirName, dirType := sc.GetStatDirOnIndex(dirVector, index)
272 if len(dirName) == 0 {
275 entry := &dir.Entries[i]
276 if !bytes.Equal(dirName, entry.Name) {
279 if adapter.StatType(dirType) != entry.Type {
282 if entry.Data == nil {
285 if err := sc.UpdateEntryData(statSegDir, &entry.Data); err != nil {
286 return fmt.Errorf("updating stat data for entry %s failed: %v", dirName, err)
289 if !sc.accessEnd(accessEpoch) {
290 return adapter.ErrStatsDataBusy
296 func (sc *StatsClient) connect() (statSegment, error) {
297 addr := net.UnixAddr{
301 Log.Debugf("connecting to: %v", addr)
303 conn, err := net.DialUnix(addr.Net, nil, &addr)
305 Log.Warnf("connecting to socket %s failed: %s", addr, err)
309 if err := conn.Close(); err != nil {
310 Log.Warnf("closing socket failed: %v", err)
313 Log.Debugf("connected to socket")
315 files, err := fd.Get(conn, 1, nil)
317 return nil, fmt.Errorf("getting file descriptor over socket failed: %v", err)
320 return nil, fmt.Errorf("no files received over socket")
325 if err := file.Close(); err != nil {
326 Log.Warnf("closing file failed: %v", err)
330 info, err := file.Stat()
336 sc.headerData, err = syscall.Mmap(int(file.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
338 Log.Debugf("mapping shared memory failed: %v", err)
339 return nil, fmt.Errorf("mapping shared memory failed: %v", err)
341 Log.Debugf("successfully mmapped shared memory segment (size: %v) %v", size, len(sc.headerData))
343 version := getVersion(sc.headerData)
346 return newStatSegmentV1(sc.headerData, size), nil
348 return newStatSegmentV2(sc.headerData, size), nil
350 return nil, fmt.Errorf("stat segment version is not supported: %v (min: %v, max: %v)",
351 version, minVersion, maxVersion)
355 // Starts monitoring 'inProgress' field. Returns stats segment
356 // access epoch when completed, or zero value if not finished
357 // within MaxWaitInProgress
358 func (sc *StatsClient) accessStart() (epoch int64) {
361 epoch, inProg := sc.GetEpoch()
363 if time.Since(t) > MaxWaitInProgress {
366 time.Sleep(CheckDelayInProgress)
367 epoch, inProg = sc.GetEpoch()
372 // AccessEnd returns true if stats data reading was finished, false
374 func (sc *StatsClient) accessEnd(accessEpoch int64) bool {
375 epoch, inProgress := sc.GetEpoch()
376 if accessEpoch != epoch || inProgress {
382 // listIndexes lists indexes for all stat entries that match any of the regex patterns.
383 func (sc *StatsClient) listIndexes(patterns ...string) (indexes []uint32, err error) {
384 if len(patterns) == 0 {
385 return sc.listIndexesFunc(nil)
387 var regexes = make([]*regexp.Regexp, len(patterns))
388 for i, pattern := range patterns {
389 r, err := regexp.Compile(pattern)
391 return nil, fmt.Errorf("compiling regexp failed: %v", err)
395 nameMatches := func(name []byte) bool {
396 for _, r := range regexes {
403 return sc.listIndexesFunc(nameMatches)
406 // listIndexesFunc lists stats indexes. The optional function
407 // argument filters returned values or returns all if empty
408 func (sc *StatsClient) listIndexesFunc(f func(name []byte) bool) (indexes []uint32, err error) {
410 // there is around ~3157 stats, so to avoid too many allocations
411 // we set capacity to 3200 when listing all stats
412 indexes = make([]uint32, 0, 3200)
415 dirVector := sc.GetDirectoryVector()
416 if dirVector == nil {
419 vecLen := *(*uint32)(vectorLen(dirVector))
421 for i := uint32(0); i < vecLen; i++ {
422 _, dirName, _ := sc.GetStatDirOnIndex(dirVector, i)
424 if len(dirName) == 0 || !f(dirName) {
428 indexes = append(indexes, i)