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...)
124 for _, index := range indexes {
125 name, err := c.entryName(index)
129 names = append(names, name)
132 if !c.accessEnd(&sa) {
133 return nil, adapter.ErrStatsDataBusy
139 func (c *StatsClient) DumpStats(patterns ...string) (entries []adapter.StatEntry, err error) {
140 sa := c.accessStart()
142 return nil, adapter.ErrStatsAccessFailed
145 dir, err := c.listIndexes(patterns...)
149 if entries, err = c.dumpEntries(dir); err != nil {
153 if !c.accessEnd(&sa) {
154 return nil, adapter.ErrStatsDataBusy
160 func (c *StatsClient) PrepareDir(patterns ...string) (*adapter.StatDir, error) {
161 dir := new(adapter.StatDir)
163 sa := c.accessStart()
165 return nil, adapter.ErrStatsAccessFailed
168 indexes, err := c.listIndexes(patterns...)
172 dir.Indexes = indexes
174 entries, err := c.dumpEntries(indexes)
178 dir.Entries = entries
180 if !c.accessEnd(&sa) {
181 return nil, adapter.ErrStatsDataBusy
188 func (c *StatsClient) UpdateDir(dir *adapter.StatDir) (err error) {
189 epoch, _ := c.getEpoch()
190 if dir.Epoch != epoch {
191 return adapter.ErrStatsDirStale
194 sa := c.accessStart()
196 return adapter.ErrStatsAccessFailed
199 dirVector := c.getStatDirVector()
201 for i, index := range dir.Indexes {
202 dirEntry := c.getStatDirIndex(dirVector, index)
205 for n := 0; n < len(dirEntry.name); n++ {
206 if dirEntry.name[n] == 0 {
207 name = dirEntry.name[:n]
215 entry := &dir.Entries[i]
216 if !bytes.Equal(name, entry.Name) {
219 if adapter.StatType(dirEntry.directoryType) != entry.Type {
222 if entry.Data == nil {
225 if err := c.updateEntryData(dirEntry, &entry.Data); err != nil {
226 return fmt.Errorf("updating stat data for entry %s failed: %v", name, err)
231 if !c.accessEnd(&sa) {
232 return adapter.ErrStatsDataBusy
238 // listIndexes lists indexes for all stat entries that match any of the regex patterns.
239 func (c *StatsClient) listIndexes(patterns ...string) (indexes []uint32, err error) {
240 if len(patterns) == 0 {
241 return c.listIndexesFunc(nil)
243 var regexes = make([]*regexp.Regexp, len(patterns))
244 for i, pattern := range patterns {
245 r, err := regexp.Compile(pattern)
247 return nil, fmt.Errorf("compiling regexp failed: %v", err)
251 nameMatches := func(name []byte) bool {
252 for _, r := range regexes {
259 return c.listIndexesFunc(nameMatches)
262 func (c *StatsClient) listIndexesFunc(f func(name []byte) bool) (indexes []uint32, err error) {
264 // there is around ~3150 stats, so to avoid too many allocations
265 // we set capacity to 3200 when listing all stats
266 indexes = make([]uint32, 0, 3200)
269 dirVector := c.getStatDirVector()
270 vecLen := uint32(vectorLen(dirVector))
272 for i := uint32(0); i < vecLen; i++ {
273 dirEntry := c.getStatDirIndex(dirVector, i)
277 for n := 0; n < len(dirEntry.name); n++ {
278 if dirEntry.name[n] == 0 {
279 name = dirEntry.name[:n]
283 if len(name) == 0 || !f(name) {
287 indexes = append(indexes, i)
293 func (c *StatsClient) entryName(index uint32) (string, error) {
294 dirVector := c.getStatDirVector()
295 vecLen := uint32(vectorLen(dirVector))
298 return "", fmt.Errorf("stat entry index %d out of range (%d)", index, vecLen)
301 dirEntry := c.getStatDirIndex(dirVector, index)
304 for n := 0; n < len(dirEntry.name); n++ {
305 if dirEntry.name[n] == 0 {
306 name = dirEntry.name[:n]
311 return string(name), nil
314 func (c *StatsClient) dumpEntries(indexes []uint32) (entries []adapter.StatEntry, err error) {
315 entries = make([]adapter.StatEntry, 0, len(indexes))
317 dirVector := c.getStatDirVector()
319 for _, index := range indexes {
320 dirEntry := c.getStatDirIndex(dirVector, index)
323 for n := 0; n < len(dirEntry.name); n++ {
324 if dirEntry.name[n] == 0 {
325 name = dirEntry.name[:n]
333 entry := adapter.StatEntry{
334 Name: append([]byte(nil), name...),
335 Type: adapter.StatType(dirEntry.directoryType),
336 Data: c.copyEntryData(dirEntry),
338 entries = append(entries, entry)