Print info for users to stderr when socket files are missing
[govpp.git] / adapter / statsclient / statsclient.go
1 // Copyright (c) 2019 Cisco and/or its affiliates.
2 //
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:
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
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.
14
15 // Package statsclient is pure Go implementation of VPP stats API client.
16 package statsclient
17
18 import (
19         "bytes"
20         "fmt"
21         "os"
22         "regexp"
23         "unsafe"
24
25         logger "github.com/sirupsen/logrus"
26
27         "git.fd.io/govpp.git/adapter"
28 )
29
30 const (
31         // DefaultSocketName is default VPP stats socket file path.
32         DefaultSocketName = adapter.DefaultStatsSocket
33 )
34
35 const socketMissing = `
36 ------------------------------------------------------------
37  VPP stats socket file %s is missing!
38
39   - is VPP running with stats segment enabled?
40   - is the correct socket name configured?
41
42  To enable it add following section to your VPP config:
43    statseg {
44      default
45    }
46 ------------------------------------------------------------
47 `
48
49 var (
50         // Debug is global variable that determines debug mode
51         Debug = os.Getenv("DEBUG_GOVPP_STATS") != ""
52
53         // Log is global logger
54         Log = logger.New()
55 )
56
57 // init initializes global logger, which logs debug level messages to stdout.
58 func init() {
59         Log.Out = os.Stdout
60         if Debug {
61                 Log.Level = logger.DebugLevel
62                 Log.Debug("govpp/statsclient: enabled debug mode")
63         }
64 }
65
66 // StatsClient is the pure Go implementation for VPP stats API.
67 type StatsClient struct {
68         sockAddr string
69
70         currentEpoch int64
71         statSegment
72 }
73
74 // NewStatsClient returns new VPP stats API client.
75 func NewStatsClient(sockAddr string) *StatsClient {
76         if sockAddr == "" {
77                 sockAddr = DefaultSocketName
78         }
79         return &StatsClient{
80                 sockAddr: sockAddr,
81         }
82 }
83
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)
91         }
92
93         if err := c.statSegment.connect(c.sockAddr); err != nil {
94                 return err
95         }
96
97         ver := c.readVersion()
98         Log.Debugf("stat segment version: %v", ver)
99
100         if err := checkVersion(ver); err != nil {
101                 return err
102         }
103
104         return nil
105 }
106
107 func (c *StatsClient) Disconnect() error {
108         if err := c.statSegment.disconnect(); err != nil {
109                 return err
110         }
111
112         return nil
113 }
114
115 func (c *StatsClient) ListStats(patterns ...string) (statNames []string, err error) {
116         sa := c.accessStart()
117         if sa == nil {
118                 return nil, fmt.Errorf("access failed")
119         }
120
121         dirOffset, _, _ := c.readOffsets()
122         Log.Debugf("dirOffset: %v", dirOffset)
123
124         vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirOffset]))
125         Log.Debugf("vecLen: %v", vecLen)
126         Log.Debugf("unsafe.Sizeof(statSegDirectoryEntry{}): %v", unsafe.Sizeof(statSegDirectoryEntry{}))
127
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))
131
132                 nul := bytes.IndexByte(dirEntry.name[:], '\x00')
133                 if nul < 0 {
134                         Log.Debugf("no zero byte found for: %q", dirEntry.name[:])
135                         continue
136                 }
137                 name := string(dirEntry.name[:nul])
138                 if name == "" {
139                         Log.Debugf("entry with empty name found (%d)", i)
140                         continue
141                 }
142
143                 Log.Debugf(" %80q (type: %v, data: %d, offset: %d) ", name, dirEntry.directoryType, dirEntry.unionData, dirEntry.offsetVector)
144
145                 if nameMatches(name, patterns) {
146                         statNames = append(statNames, name)
147                 }
148
149                 // TODO: copy the listed entries elsewhere
150         }
151
152         if !c.accessEnd(sa) {
153                 return nil, adapter.ErrStatDirBusy
154         }
155
156         c.currentEpoch = sa.epoch
157
158         return statNames, nil
159 }
160
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")
165         }
166
167         sa := c.accessStart()
168         if sa == nil {
169                 return nil, fmt.Errorf("access failed")
170         }
171
172         dirOffset, _, _ := c.readOffsets()
173         vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirOffset]))
174
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))
178
179                 nul := bytes.IndexByte(dirEntry.name[:], '\x00')
180                 if nul < 0 {
181                         Log.Debugf("no zero byte found for: %q", dirEntry.name[:])
182                         continue
183                 }
184                 name := string(dirEntry.name[:nul])
185                 if name == "" {
186                         Log.Debugf("entry with empty name found (%d)", i)
187                         continue
188                 }
189
190                 Log.Debugf(" - %s (type: %v, data: %v, offset: %v) ", name, dirEntry.directoryType, dirEntry.unionData, dirEntry.offsetVector)
191
192                 entry := adapter.StatEntry{
193                         Name: name,
194                         Type: adapter.StatType(dirEntry.directoryType),
195                         Data: c.copyData(dirEntry),
196                 }
197
198                 Log.Debugf("\tentry data: %+v %#v (%T)", entry.Data, entry.Data, entry.Data)
199
200                 if nameMatches(entry.Name, patterns) {
201                         entries = append(entries, &entry)
202                 }
203         }
204
205         if !c.accessEnd(sa) {
206                 return nil, adapter.ErrStatDumpBusy
207         }
208
209         return entries, nil
210 }
211
212 func nameMatches(name string, patterns []string) bool {
213         if len(patterns) == 0 {
214                 return true
215         }
216         for _, pattern := range patterns {
217                 matched, err := regexp.MatchString(pattern, name)
218                 if err == nil && matched {
219                         return true
220                 }
221         }
222         return false
223 }