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.
24 logger "github.com/sirupsen/logrus"
26 "git.fd.io/govpp.git/adapter"
30 // DefaultSocketName is default VPP stats socket file path.
31 DefaultSocketName = adapter.DefaultStatsSocket
34 const socketMissing = `
35 ------------------------------------------------------------
36 VPP stats socket file %s is missing!
38 - is VPP running with stats segment enabled?
39 - is the correct socket name configured?
41 To enable it add following section to your VPP config:
45 ------------------------------------------------------------
49 // Debug is global variable that determines debug mode
50 Debug = os.Getenv("DEBUG_GOVPP_STATS") != ""
52 // Log is global logger
56 // init initializes global logger, which logs debug level messages to stdout.
60 Log.Level = logger.DebugLevel
61 Log.Debug("govpp/statsclient: enabled debug mode")
65 func debugf(f string, a ...interface{}) {
71 // implements StatsAPI
72 var _ adapter.StatsAPI = (*StatsClient)(nil)
74 // StatsClient is the pure Go implementation for VPP stats API.
75 type StatsClient struct {
81 // NewStatsClient returns new VPP stats API client.
82 func NewStatsClient(sockAddr string) *StatsClient {
84 sockAddr = DefaultSocketName
91 func (c *StatsClient) Connect() error {
92 // check if socket exists
93 if _, err := os.Stat(c.sockAddr); os.IsNotExist(err) {
94 fmt.Fprintf(os.Stderr, socketMissing, c.sockAddr)
95 return fmt.Errorf("stats socket file %s does not exist", c.sockAddr)
96 } else if err != nil {
97 return fmt.Errorf("stats socket error: %v", err)
100 if err := c.statSegment.connect(c.sockAddr); err != nil {
107 func (c *StatsClient) Disconnect() error {
108 if err := c.statSegment.disconnect(); err != nil {
114 func (c *StatsClient) ListStats(patterns ...string) (names []string, err error) {
115 sa := c.accessStart()
117 return nil, adapter.ErrStatsAccessFailed
120 indexes, err := c.listIndexes(patterns...)
125 dirVector := c.getStatDirVector()
126 vecLen := uint32(vectorLen(dirVector))
128 for _, index := range indexes {
130 return nil, fmt.Errorf("stat entry index %d out of dir vector len (%d)", index, vecLen)
133 dirEntry := c.getStatDirIndex(dirVector, index)
135 for n := 0; n < len(dirEntry.name); n++ {
136 if dirEntry.name[n] == 0 {
137 name = dirEntry.name[:n]
141 names = append(names, string(name))
144 if !c.accessEnd(&sa) {
145 return nil, adapter.ErrStatsDataBusy
151 func (c *StatsClient) DumpStats(patterns ...string) (entries []adapter.StatEntry, err error) {
152 sa := c.accessStart()
154 return nil, adapter.ErrStatsAccessFailed
157 indexes, err := c.listIndexes(patterns...)
161 if entries, err = c.dumpEntries(indexes); err != nil {
165 if !c.accessEnd(&sa) {
166 return nil, adapter.ErrStatsDataBusy
172 func (c *StatsClient) PrepareDir(patterns ...string) (*adapter.StatDir, error) {
173 dir := new(adapter.StatDir)
175 sa := c.accessStart()
177 return nil, adapter.ErrStatsAccessFailed
180 indexes, err := c.listIndexes(patterns...)
184 dir.Indexes = indexes
186 entries, err := c.dumpEntries(indexes)
190 dir.Entries = entries
192 if !c.accessEnd(&sa) {
193 return nil, adapter.ErrStatsDataBusy
200 func (c *StatsClient) UpdateDir(dir *adapter.StatDir) (err error) {
201 epoch, _ := c.getEpoch()
202 if dir.Epoch != epoch {
203 return adapter.ErrStatsDirStale
206 sa := c.accessStart()
208 return adapter.ErrStatsAccessFailed
211 dirVector := c.getStatDirVector()
213 for i, index := range dir.Indexes {
214 dirEntry := c.getStatDirIndex(dirVector, index)
217 for n := 0; n < len(dirEntry.name); n++ {
218 if dirEntry.name[n] == 0 {
219 name = dirEntry.name[:n]
227 entry := &dir.Entries[i]
228 if !bytes.Equal(name, entry.Name) {
231 if adapter.StatType(dirEntry.directoryType) != entry.Type {
234 if entry.Data == nil {
237 if err := c.updateEntryData(dirEntry, &entry.Data); err != nil {
238 return fmt.Errorf("updating stat data for entry %s failed: %v", name, err)
243 if !c.accessEnd(&sa) {
244 return adapter.ErrStatsDataBusy
250 // listIndexes lists indexes for all stat entries that match any of the regex patterns.
251 func (c *StatsClient) listIndexes(patterns ...string) (indexes []uint32, err error) {
252 if len(patterns) == 0 {
253 return c.listIndexesFunc(nil)
255 var regexes = make([]*regexp.Regexp, len(patterns))
256 for i, pattern := range patterns {
257 r, err := regexp.Compile(pattern)
259 return nil, fmt.Errorf("compiling regexp failed: %v", err)
263 nameMatches := func(name []byte) bool {
264 for _, r := range regexes {
271 return c.listIndexesFunc(nameMatches)
274 func (c *StatsClient) listIndexesFunc(f func(name []byte) bool) (indexes []uint32, err error) {
276 // there is around ~3157 stats, so to avoid too many allocations
277 // we set capacity to 3200 when listing all stats
278 indexes = make([]uint32, 0, 3200)
281 dirVector := c.getStatDirVector()
282 vecLen := uint32(vectorLen(dirVector))
284 for i := uint32(0); i < vecLen; i++ {
285 dirEntry := c.getStatDirIndex(dirVector, i)
289 for n := 0; n < len(dirEntry.name); n++ {
290 if dirEntry.name[n] == 0 {
291 name = dirEntry.name[:n]
295 if len(name) == 0 || !f(name) {
299 indexes = append(indexes, i)
305 func (c *StatsClient) dumpEntries(indexes []uint32) (entries []adapter.StatEntry, err error) {
306 dirVector := c.getStatDirVector()
307 dirLen := uint32(vectorLen(dirVector))
309 debugf("dumping entres for %d indexes", len(indexes))
311 entries = make([]adapter.StatEntry, 0, len(indexes))
312 for _, index := range indexes {
314 return nil, fmt.Errorf("stat entry index %d out of dir vector length (%d)", index, dirLen)
317 dirEntry := c.getStatDirIndex(dirVector, index)
320 for n := 0; n < len(dirEntry.name); n++ {
321 if dirEntry.name[n] == 0 {
322 name = dirEntry.name[:n]
328 debugf(" - %3d. dir: %q type: %v offset: %d union: %d", index, name,
329 adapter.StatType(dirEntry.directoryType), dirEntry.offsetVector, dirEntry.unionData)
336 entry := adapter.StatEntry{
337 Name: append([]byte(nil), name...),
338 Type: adapter.StatType(dirEntry.directoryType),
339 Data: c.copyEntryData(dirEntry),
341 entries = append(entries, entry)