Improve doc & fix import ordering
[govpp.git] / adapter / statsclient / stat_segment.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
16
17 import (
18         "fmt"
19         "net"
20         "syscall"
21         "unsafe"
22
23         "github.com/ftrvxmtrx/fd"
24
25         "git.fd.io/govpp.git/adapter"
26 )
27
28 var (
29         ErrStatDataLenIncorrect = fmt.Errorf("stat data length incorrect")
30 )
31
32 const (
33         minVersion = 0
34         maxVersion = 1
35 )
36
37 func checkVersion(ver uint64) error {
38         if ver < minVersion {
39                 return fmt.Errorf("stat segment version is too old: %v (minimal version: %v)", ver, minVersion)
40         } else if ver > maxVersion {
41                 return fmt.Errorf("stat segment version is not supported: %v (minimal version: %v)", ver, maxVersion)
42         }
43         return nil
44 }
45
46 type statSegment struct {
47         sharedHeader []byte
48         memorySize   int64
49
50         // legacyVersion represents stat segment version 0
51         // and is used as fallback for VPP 19.04
52         legacyVersion bool
53 }
54
55 func (c *statSegment) getHeader() (header sharedHeader) {
56         if c.legacyVersion {
57                 return loadSharedHeaderLegacy(c.sharedHeader)
58         }
59         return loadSharedHeader(c.sharedHeader)
60 }
61
62 func (c *statSegment) getEpoch() (int64, bool) {
63         h := c.getHeader()
64         return h.epoch, h.inProgress != 0
65 }
66
67 func (c *statSegment) getOffsets() (dir, err, stat int64) {
68         h := c.getHeader()
69         return h.directoryOffset, h.errorOffset, h.statsOffset
70 }
71
72 func (c *statSegment) connect(sockName string) error {
73         if c.sharedHeader != nil {
74                 return fmt.Errorf("already connected")
75         }
76
77         addr := net.UnixAddr{
78                 Net:  "unixpacket",
79                 Name: sockName,
80         }
81         Log.Debugf("connecting to: %v", addr)
82
83         conn, err := net.DialUnix(addr.Net, nil, &addr)
84         if err != nil {
85                 Log.Warnf("connecting to socket %s failed: %s", addr, err)
86                 return err
87         }
88         defer func() {
89                 if err := conn.Close(); err != nil {
90                         Log.Warnf("closing socket failed: %v", err)
91                 }
92         }()
93
94         Log.Debugf("connected to socket")
95
96         files, err := fd.Get(conn, 1, nil)
97         if err != nil {
98                 return fmt.Errorf("getting file descriptor over socket failed: %v", err)
99         }
100         if len(files) == 0 {
101                 return fmt.Errorf("no files received over socket")
102         }
103
104         file := files[0]
105         defer func() {
106                 if err := file.Close(); err != nil {
107                         Log.Warnf("closing file failed: %v", err)
108                 }
109         }()
110
111         info, err := file.Stat()
112         if err != nil {
113                 return err
114         }
115         size := info.Size()
116
117         data, err := syscall.Mmap(int(file.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
118         if err != nil {
119                 Log.Debugf("mapping shared memory failed: %v", err)
120                 return fmt.Errorf("mapping shared memory failed: %v", err)
121         }
122
123         Log.Debugf("successfuly mmapped shared memory segment (size: %v) %v", size, len(data))
124
125         c.sharedHeader = data
126         c.memorySize = size
127
128         hdr := loadSharedHeader(c.sharedHeader)
129         Log.Debugf("stat segment header: %+v", hdr)
130
131         if hdr.legacyVersion() {
132                 c.legacyVersion = true
133                 hdr = loadSharedHeaderLegacy(c.sharedHeader)
134                 Log.Debugf("falling back to legacy version (VPP <=19.04) of stat segment (header: %+v)", hdr)
135         }
136
137         if err := checkVersion(hdr.version); err != nil {
138                 return err
139         }
140
141         return nil
142 }
143
144 func (c *statSegment) disconnect() error {
145         if c.sharedHeader == nil {
146                 return nil
147         }
148
149         if err := syscall.Munmap(c.sharedHeader); err != nil {
150                 Log.Debugf("unmapping shared memory failed: %v", err)
151                 return fmt.Errorf("unmapping shared memory failed: %v", err)
152         }
153         c.sharedHeader = nil
154
155         Log.Debugf("successfuly unmapped shared memory")
156         return nil
157 }
158
159 type statDirectoryType int32
160
161 const (
162         statDirIllegal               = 0
163         statDirScalarIndex           = 1
164         statDirCounterVectorSimple   = 2
165         statDirCounterVectorCombined = 3
166         statDirErrorIndex            = 4
167         statDirNameVector            = 5
168         statDirEmpty                 = 6
169 )
170
171 func (t statDirectoryType) String() string {
172         return adapter.StatType(t).String()
173 }
174
175 type statSegDirectoryEntry struct {
176         directoryType statDirectoryType
177         // unionData can represent:
178         // - offset
179         // - index
180         // - value
181         unionData    uint64
182         offsetVector uint64
183         name         [128]byte
184 }
185
186 func (c *statSegment) getStatDirVector() unsafe.Pointer {
187         dirOffset, _, _ := c.getOffsets()
188         return unsafe.Pointer(&c.sharedHeader[dirOffset])
189 }
190
191 func (c *statSegment) getStatDirIndex(p unsafe.Pointer, index uint32) *statSegDirectoryEntry {
192         return (*statSegDirectoryEntry)(unsafe.Pointer(uintptr(p) + uintptr(index)*unsafe.Sizeof(statSegDirectoryEntry{})))
193 }
194
195 func (c *statSegment) copyEntryData(dirEntry *statSegDirectoryEntry) adapter.Stat {
196         dirType := adapter.StatType(dirEntry.directoryType)
197
198         switch dirType {
199         case statDirScalarIndex:
200                 return adapter.ScalarStat(dirEntry.unionData)
201
202         case statDirErrorIndex:
203                 if dirEntry.unionData == 0 {
204                         debugf("offset invalid for %s", dirEntry.name)
205                         break
206                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
207                         debugf("offset out of range for %s", dirEntry.name)
208                         break
209                 }
210
211                 _, errOffset, _ := c.getOffsets()
212                 offsetVector := unsafe.Pointer(&c.sharedHeader[errOffset])
213
214                 var errData adapter.Counter
215                 if c.legacyVersion {
216                         // error were not vector (per-worker) in VPP 19.04
217                         offset := uintptr(dirEntry.unionData) * unsafe.Sizeof(uint64(0))
218                         val := *(*adapter.Counter)(statSegPointer(offsetVector, offset))
219                         errData = val
220                 } else {
221                         vecLen := uint32(vectorLen(offsetVector))
222
223                         for i := uint32(0); i < vecLen; i++ {
224                                 cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
225                                 offset := uintptr(cb) + uintptr(dirEntry.unionData)*unsafe.Sizeof(adapter.Counter(0))
226                                 debugf("error index, cb: %d, offset: %d", cb, offset)
227                                 val := *(*adapter.Counter)(statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), offset))
228                                 errData += val
229                         }
230                 }
231                 return adapter.ErrorStat(errData)
232
233         case statDirCounterVectorSimple:
234                 if dirEntry.unionData == 0 {
235                         debugf("offset invalid for %s", dirEntry.name)
236                         break
237                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
238                         debugf("offset out of range for %s", dirEntry.name)
239                         break
240                 }
241
242                 vecLen := uint32(vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData])))
243                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
244
245                 data := make([][]adapter.Counter, vecLen)
246                 for i := uint32(0); i < vecLen; i++ {
247                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
248                         counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
249                         vecLen2 := uint32(vectorLen(counterVec))
250                         data[i] = make([]adapter.Counter, vecLen2)
251                         for j := uint32(0); j < vecLen2; j++ {
252                                 offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
253                                 val := *(*adapter.Counter)(statSegPointer(counterVec, offset))
254                                 data[i][j] = val
255                         }
256                 }
257                 return adapter.SimpleCounterStat(data)
258
259         case statDirCounterVectorCombined:
260                 if dirEntry.unionData == 0 {
261                         debugf("offset invalid for %s", dirEntry.name)
262                         break
263                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
264                         debugf("offset out of range for %s", dirEntry.name)
265                         break
266                 }
267
268                 vecLen := uint32(vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData])))
269                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
270
271                 data := make([][]adapter.CombinedCounter, vecLen)
272                 for i := uint32(0); i < vecLen; i++ {
273                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
274                         counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
275                         vecLen2 := uint32(vectorLen(counterVec))
276                         data[i] = make([]adapter.CombinedCounter, vecLen2)
277                         for j := uint32(0); j < vecLen2; j++ {
278                                 offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
279                                 val := *(*adapter.CombinedCounter)(statSegPointer(counterVec, offset))
280                                 data[i][j] = val
281                         }
282                 }
283                 return adapter.CombinedCounterStat(data)
284
285         case statDirNameVector:
286                 if dirEntry.unionData == 0 {
287                         debugf("offset invalid for %s", dirEntry.name)
288                         break
289                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
290                         debugf("offset out of range for %s", dirEntry.name)
291                         break
292                 }
293
294                 vecLen := uint32(vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData])))
295                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
296
297                 data := make([]adapter.Name, vecLen)
298                 for i := uint32(0); i < vecLen; i++ {
299                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
300                         if cb == 0 {
301                                 debugf("name vector out of range for %s (%v)", dirEntry.name, i)
302                                 continue
303                         }
304                         nameVec := unsafe.Pointer(&c.sharedHeader[cb])
305                         vecLen2 := uint32(vectorLen(nameVec))
306
307                         nameStr := make([]byte, 0, vecLen2)
308                         for j := uint32(0); j < vecLen2; j++ {
309                                 offset := uintptr(j) * unsafe.Sizeof(byte(0))
310                                 val := *(*byte)(statSegPointer(nameVec, offset))
311                                 if val > 0 {
312                                         nameStr = append(nameStr, val)
313                                 }
314                         }
315                         data[i] = adapter.Name(nameStr)
316                 }
317                 return adapter.NameStat(data)
318
319         case statDirEmpty:
320                 // no-op
321
322         default:
323                 // TODO: monitor occurrences with metrics
324                 debugf("Unknown type %d for stat entry: %q", dirEntry.directoryType, dirEntry.name)
325         }
326         return nil
327 }
328
329 func (c *statSegment) updateEntryData(dirEntry *statSegDirectoryEntry, stat *adapter.Stat) error {
330         switch (*stat).(type) {
331         case adapter.ScalarStat:
332                 *stat = adapter.ScalarStat(dirEntry.unionData)
333
334         case adapter.ErrorStat:
335                 if dirEntry.unionData == 0 {
336                         debugf("offset invalid for %s", dirEntry.name)
337                         break
338                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
339                         debugf("offset out of range for %s", dirEntry.name)
340                         break
341                 }
342
343                 _, errOffset, _ := c.getOffsets()
344                 offsetVector := unsafe.Pointer(&c.sharedHeader[errOffset])
345
346                 var errData adapter.Counter
347                 if c.legacyVersion {
348                         // error were not vector (per-worker) in VPP 19.04
349                         offset := uintptr(dirEntry.unionData) * unsafe.Sizeof(uint64(0))
350                         val := *(*adapter.Counter)(statSegPointer(offsetVector, offset))
351                         errData = val
352                 } else {
353                         vecLen := uint32(vectorLen(unsafe.Pointer(&c.sharedHeader[errOffset])))
354
355                         for i := uint32(0); i < vecLen; i++ {
356                                 cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
357                                 offset := uintptr(cb) + uintptr(dirEntry.unionData)*unsafe.Sizeof(adapter.Counter(0))
358                                 val := *(*adapter.Counter)(statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), offset))
359                                 errData += val
360                         }
361                 }
362                 *stat = adapter.ErrorStat(errData)
363
364         case adapter.SimpleCounterStat:
365                 if dirEntry.unionData == 0 {
366                         debugf("offset invalid for %s", dirEntry.name)
367                         break
368                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
369                         debugf("offset out of range for %s", dirEntry.name)
370                         break
371                 }
372
373                 vecLen := uint32(vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData])))
374                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
375
376                 data := (*stat).(adapter.SimpleCounterStat)
377                 if uint32(len(data)) != vecLen {
378                         return ErrStatDataLenIncorrect
379                 }
380                 for i := uint32(0); i < vecLen; i++ {
381                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
382                         counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
383                         vecLen2 := uint32(vectorLen(counterVec))
384                         simpData := data[i]
385                         if uint32(len(simpData)) != vecLen2 {
386                                 return ErrStatDataLenIncorrect
387                         }
388                         for j := uint32(0); j < vecLen2; j++ {
389                                 offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
390                                 val := *(*adapter.Counter)(statSegPointer(counterVec, offset))
391                                 simpData[j] = val
392                         }
393                 }
394
395         case adapter.CombinedCounterStat:
396                 if dirEntry.unionData == 0 {
397                         debugf("offset invalid for %s", dirEntry.name)
398                         break
399                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
400                         debugf("offset out of range for %s", dirEntry.name)
401                         break
402                 }
403
404                 vecLen := uint32(vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData])))
405                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
406
407                 data := (*stat).(adapter.CombinedCounterStat)
408                 if uint32(len(data)) != vecLen {
409                         return ErrStatDataLenIncorrect
410                 }
411                 for i := uint32(0); i < vecLen; i++ {
412                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
413                         counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
414                         vecLen2 := uint32(vectorLen(counterVec))
415                         combData := data[i]
416                         if uint32(len(combData)) != vecLen2 {
417                                 return ErrStatDataLenIncorrect
418                         }
419                         for j := uint32(0); j < vecLen2; j++ {
420                                 offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
421                                 val := *(*adapter.CombinedCounter)(statSegPointer(counterVec, offset))
422                                 combData[j] = val
423                         }
424                 }
425
426         case adapter.NameStat:
427                 if dirEntry.unionData == 0 {
428                         debugf("offset invalid for %s", dirEntry.name)
429                         break
430                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
431                         debugf("offset out of range for %s", dirEntry.name)
432                         break
433                 }
434
435                 vecLen := uint32(vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData])))
436                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
437
438                 data := (*stat).(adapter.NameStat)
439                 if uint32(len(data)) != vecLen {
440                         return ErrStatDataLenIncorrect
441                 }
442                 for i := uint32(0); i < vecLen; i++ {
443                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
444                         if cb == 0 {
445                                 continue
446                         }
447                         nameVec := unsafe.Pointer(&c.sharedHeader[cb])
448                         vecLen2 := uint32(vectorLen(nameVec))
449
450                         nameData := data[i]
451                         if uint32(len(nameData))+1 != vecLen2 {
452                                 return ErrStatDataLenIncorrect
453                         }
454                         for j := uint32(0); j < vecLen2; j++ {
455                                 offset := uintptr(j) * unsafe.Sizeof(byte(0))
456                                 val := *(*byte)(statSegPointer(nameVec, offset))
457                                 if val == 0 {
458                                         break
459                                 }
460                                 nameData[j] = val
461                         }
462                 }
463
464         default:
465                 if Debug {
466                         Log.Debugf("Unrecognized stat type %T for stat entry: %v", stat, dirEntry.name)
467                 }
468         }
469         return nil
470 }