0d988ba9bed78826dd0211fa39ee009820b7493c
[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                 _, errOffset, _ := c.getOffsets()
204                 offsetVector := unsafe.Pointer(&c.sharedHeader[errOffset])
205
206                 var errData adapter.Counter
207                 if c.legacyVersion {
208                         // error were not vector (per-worker) in VPP 19.04
209                         offset := uintptr(dirEntry.unionData) * unsafe.Sizeof(uint64(0))
210                         val := *(*adapter.Counter)(statSegPointer(offsetVector, offset))
211                         errData = val
212                 } else {
213                         vecLen := uint32(vectorLen(offsetVector))
214
215                         for i := uint32(0); i < vecLen; i++ {
216                                 cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
217                                 offset := uintptr(cb) + uintptr(dirEntry.unionData)*unsafe.Sizeof(adapter.Counter(0))
218                                 debugf("error index, cb: %d, offset: %d", cb, offset)
219                                 val := *(*adapter.Counter)(statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), offset))
220                                 errData += val
221                         }
222                 }
223                 return adapter.ErrorStat(errData)
224
225         case statDirCounterVectorSimple:
226                 if dirEntry.unionData == 0 {
227                         debugf("offset invalid for %s", dirEntry.name)
228                         break
229                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
230                         debugf("offset out of range for %s", dirEntry.name)
231                         break
232                 }
233
234                 vecLen := uint32(vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData])))
235                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
236
237                 data := make([][]adapter.Counter, vecLen)
238                 for i := uint32(0); i < vecLen; i++ {
239                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
240                         counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
241                         vecLen2 := uint32(vectorLen(counterVec))
242                         data[i] = make([]adapter.Counter, vecLen2)
243                         for j := uint32(0); j < vecLen2; j++ {
244                                 offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
245                                 val := *(*adapter.Counter)(statSegPointer(counterVec, offset))
246                                 data[i][j] = val
247                         }
248                 }
249                 return adapter.SimpleCounterStat(data)
250
251         case statDirCounterVectorCombined:
252                 if dirEntry.unionData == 0 {
253                         debugf("offset invalid for %s", dirEntry.name)
254                         break
255                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
256                         debugf("offset out of range for %s", dirEntry.name)
257                         break
258                 }
259
260                 vecLen := uint32(vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData])))
261                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
262
263                 data := make([][]adapter.CombinedCounter, vecLen)
264                 for i := uint32(0); i < vecLen; i++ {
265                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
266                         counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
267                         vecLen2 := uint32(vectorLen(counterVec))
268                         data[i] = make([]adapter.CombinedCounter, vecLen2)
269                         for j := uint32(0); j < vecLen2; j++ {
270                                 offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
271                                 val := *(*adapter.CombinedCounter)(statSegPointer(counterVec, offset))
272                                 data[i][j] = val
273                         }
274                 }
275                 return adapter.CombinedCounterStat(data)
276
277         case statDirNameVector:
278                 if dirEntry.unionData == 0 {
279                         debugf("offset invalid for %s", dirEntry.name)
280                         break
281                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
282                         debugf("offset out of range for %s", dirEntry.name)
283                         break
284                 }
285
286                 vecLen := uint32(vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData])))
287                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
288
289                 data := make([]adapter.Name, vecLen)
290                 for i := uint32(0); i < vecLen; i++ {
291                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
292                         if cb == 0 {
293                                 debugf("name vector out of range for %s (%v)", dirEntry.name, i)
294                                 continue
295                         }
296                         nameVec := unsafe.Pointer(&c.sharedHeader[cb])
297                         vecLen2 := uint32(vectorLen(nameVec))
298
299                         nameStr := make([]byte, 0, vecLen2)
300                         for j := uint32(0); j < vecLen2; j++ {
301                                 offset := uintptr(j) * unsafe.Sizeof(byte(0))
302                                 val := *(*byte)(statSegPointer(nameVec, offset))
303                                 if val > 0 {
304                                         nameStr = append(nameStr, val)
305                                 }
306                         }
307                         data[i] = adapter.Name(nameStr)
308                 }
309                 return adapter.NameStat(data)
310
311         case statDirEmpty:
312                 // no-op
313
314         default:
315                 // TODO: monitor occurrences with metrics
316                 debugf("Unknown type %d for stat entry: %q", dirEntry.directoryType, dirEntry.name)
317         }
318         return nil
319 }
320
321 func (c *statSegment) updateEntryData(dirEntry *statSegDirectoryEntry, stat *adapter.Stat) error {
322         switch (*stat).(type) {
323         case adapter.ScalarStat:
324                 *stat = adapter.ScalarStat(dirEntry.unionData)
325
326         case adapter.ErrorStat:
327                 _, errOffset, _ := c.getOffsets()
328                 offsetVector := unsafe.Pointer(&c.sharedHeader[errOffset])
329
330                 var errData adapter.Counter
331                 if c.legacyVersion {
332                         // error were not vector (per-worker) in VPP 19.04
333                         offset := uintptr(dirEntry.unionData) * unsafe.Sizeof(uint64(0))
334                         val := *(*adapter.Counter)(statSegPointer(offsetVector, offset))
335                         errData = val
336                 } else {
337                         vecLen := vectorLen(offsetVector)
338                         for i := uint64(0); i < vecLen; i++ {
339                                 cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
340                                 offset := uintptr(cb) + uintptr(dirEntry.unionData)*unsafe.Sizeof(adapter.Counter(0))
341                                 val := *(*adapter.Counter)(statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), offset))
342                                 errData += val
343                         }
344                 }
345                 *stat = adapter.ErrorStat(errData)
346
347         case adapter.SimpleCounterStat:
348                 if dirEntry.unionData == 0 {
349                         debugf("offset invalid for %s", dirEntry.name)
350                         break
351                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
352                         debugf("offset out of range for %s", dirEntry.name)
353                         break
354                 }
355
356                 vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]))
357                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
358
359                 data := (*stat).(adapter.SimpleCounterStat)
360                 if uint64(len(data)) != vecLen {
361                         return ErrStatDataLenIncorrect
362                 }
363                 for i := uint64(0); i < vecLen; i++ {
364                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
365                         counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
366                         vecLen2 := vectorLen(counterVec)
367                         simpData := data[i]
368                         if uint64(len(simpData)) != vecLen2 {
369                                 return ErrStatDataLenIncorrect
370                         }
371                         for j := uint64(0); j < vecLen2; j++ {
372                                 offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
373                                 val := *(*adapter.Counter)(statSegPointer(counterVec, offset))
374                                 simpData[j] = val
375                         }
376                 }
377
378         case adapter.CombinedCounterStat:
379                 if dirEntry.unionData == 0 {
380                         debugf("offset invalid for %s", dirEntry.name)
381                         break
382                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
383                         debugf("offset out of range for %s", dirEntry.name)
384                         break
385                 }
386
387                 vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]))
388                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
389
390                 data := (*stat).(adapter.CombinedCounterStat)
391                 if uint64(len(data)) != vecLen {
392                         return ErrStatDataLenIncorrect
393                 }
394                 for i := uint64(0); i < vecLen; i++ {
395                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
396                         counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
397                         vecLen2 := vectorLen(counterVec)
398                         combData := data[i]
399                         if uint64(len(combData)) != vecLen2 {
400                                 return ErrStatDataLenIncorrect
401                         }
402                         for j := uint64(0); j < vecLen2; j++ {
403                                 offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
404                                 val := *(*adapter.CombinedCounter)(statSegPointer(counterVec, offset))
405                                 combData[j] = val
406                         }
407                 }
408
409         case adapter.NameStat:
410                 if dirEntry.unionData == 0 {
411                         debugf("offset invalid for %s", dirEntry.name)
412                         break
413                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
414                         debugf("offset out of range for %s", dirEntry.name)
415                         break
416                 }
417
418                 vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]))
419                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
420
421                 data := (*stat).(adapter.NameStat)
422                 if uint64(len(data)) != vecLen {
423                         return ErrStatDataLenIncorrect
424                 }
425                 for i := uint64(0); i < vecLen; i++ {
426                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
427                         if cb == 0 {
428                                 continue
429                         }
430                         nameVec := unsafe.Pointer(&c.sharedHeader[cb])
431                         vecLen2 := vectorLen(nameVec)
432
433                         nameData := data[i]
434                         if uint64(len(nameData))+1 != vecLen2 {
435                                 return ErrStatDataLenIncorrect
436                         }
437                         for j := uint64(0); j < vecLen2; j++ {
438                                 offset := uintptr(j) * unsafe.Sizeof(byte(0))
439                                 val := *(*byte)(statSegPointer(nameVec, offset))
440                                 if val == 0 {
441                                         break
442                                 }
443                                 nameData[j] = val
444                         }
445                 }
446
447         default:
448                 if Debug {
449                         Log.Debugf("Unrecognized stat type %T for stat entry: %v", stat, dirEntry.name)
450                 }
451         }
452         return nil
453 }