9f028eba4999fe4d0857c8938530e853a7095598
[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) getStatDirVector() unsafe.Pointer {
56         dirOffset, _, _ := c.getOffsets()
57         return unsafe.Pointer(&c.sharedHeader[dirOffset])
58 }
59
60 func (c *statSegment) getStatDirIndex(p unsafe.Pointer, index uint32) *statSegDirectoryEntry {
61         return (*statSegDirectoryEntry)(unsafe.Pointer(uintptr(p) + uintptr(index)*unsafe.Sizeof(statSegDirectoryEntry{})))
62 }
63
64 func (c *statSegment) getHeader() (header statSegSharedHeader) {
65         if c.legacyVersion {
66                 return statSegHeaderLegacy(c.sharedHeader)
67         }
68         return statSegHeader(c.sharedHeader)
69 }
70
71 func (c *statSegment) getEpoch() (int64, bool) {
72         h := c.getHeader()
73         return h.epoch, h.inProgress != 0
74 }
75
76 func (c *statSegment) getOffsets() (dir, err, stat int64) {
77         h := c.getHeader()
78         return h.directoryOffset, h.errorOffset, h.statsOffset
79 }
80
81 func (c *statSegment) connect(sockName string) error {
82         if c.sharedHeader != nil {
83                 return fmt.Errorf("already connected")
84         }
85
86         addr := net.UnixAddr{
87                 Net:  "unixpacket",
88                 Name: sockName,
89         }
90         Log.Debugf("connecting to: %v", addr)
91
92         conn, err := net.DialUnix(addr.Net, nil, &addr)
93         if err != nil {
94                 Log.Warnf("connecting to socket %s failed: %s", addr, err)
95                 return err
96         }
97         defer func() {
98                 if err := conn.Close(); err != nil {
99                         Log.Warnf("closing socket failed: %v", err)
100                 }
101         }()
102
103         Log.Debugf("connected to socket")
104
105         files, err := fd.Get(conn, 1, nil)
106         if err != nil {
107                 return fmt.Errorf("getting file descriptor over socket failed: %v", err)
108         }
109         if len(files) == 0 {
110                 return fmt.Errorf("no files received over socket")
111         }
112
113         file := files[0]
114         defer func() {
115                 if err := file.Close(); err != nil {
116                         Log.Warnf("closing file failed: %v", err)
117                 }
118         }()
119
120         info, err := file.Stat()
121         if err != nil {
122                 return err
123         }
124         size := info.Size()
125
126         data, err := syscall.Mmap(int(file.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
127         if err != nil {
128                 Log.Debugf("mapping shared memory failed: %v", err)
129                 return fmt.Errorf("mapping shared memory failed: %v", err)
130         }
131
132         c.sharedHeader = data
133         c.memorySize = size
134
135         Log.Debugf("successfuly mmapped shared memory segment (size: %v)", size)
136
137         hdr := statSegHeader(c.sharedHeader)
138         Log.Debugf("stat segment header: %+v", hdr)
139
140         if hdr.legacyVersion() {
141                 c.legacyVersion = true
142                 hdr = statSegHeaderLegacy(c.sharedHeader)
143                 Log.Debugf("falling back to legacy version (VPP <=19.04) of stat segment (header: %+v)", hdr)
144         }
145
146         if err := checkVersion(hdr.version); err != nil {
147                 return err
148         }
149
150         return nil
151 }
152
153 func (c *statSegment) disconnect() error {
154         if c.sharedHeader == nil {
155                 return nil
156         }
157
158         if err := syscall.Munmap(c.sharedHeader); err != nil {
159                 Log.Debugf("unmapping shared memory failed: %v", err)
160                 return fmt.Errorf("unmapping shared memory failed: %v", err)
161         }
162         c.sharedHeader = nil
163
164         Log.Debugf("successfuly unmapped shared memory")
165         return nil
166 }
167
168 type statDirectoryType int32
169
170 func (t statDirectoryType) String() string {
171         return adapter.StatType(t).String()
172 }
173
174 type statSegDirectoryEntry struct {
175         directoryType statDirectoryType
176         // unionData can represent:
177         // - offset
178         // - index
179         // - value
180         unionData    uint64
181         offsetVector uint64
182         name         [128]byte
183 }
184
185 func (c *statSegment) copyEntryData(dirEntry *statSegDirectoryEntry) adapter.Stat {
186         dirType := adapter.StatType(dirEntry.directoryType)
187
188         switch dirType {
189         case adapter.ScalarIndex:
190                 return adapter.ScalarStat(dirEntry.unionData)
191
192         case adapter.ErrorIndex:
193                 _, errOffset, _ := c.getOffsets()
194                 offsetVector := unsafe.Pointer(&c.sharedHeader[errOffset])
195
196                 var errData adapter.Counter
197                 if c.legacyVersion {
198                         // error were not vector (per-worker) in VPP 19.04
199                         offset := uintptr(dirEntry.unionData) * unsafe.Sizeof(uint64(0))
200                         val := *(*adapter.Counter)(statSegPointer(offsetVector, offset))
201                         errData = val
202                 } else {
203                         vecLen := vectorLen(offsetVector)
204                         for i := uint64(0); i < vecLen; i++ {
205                                 cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
206                                 offset := uintptr(cb) + uintptr(dirEntry.unionData)*unsafe.Sizeof(adapter.Counter(0))
207                                 val := *(*adapter.Counter)(statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), offset))
208                                 errData += val
209                         }
210                 }
211                 return adapter.ErrorStat(errData)
212
213         case adapter.SimpleCounterVector:
214                 if dirEntry.unionData == 0 {
215                         debugf("offset invalid for %s", dirEntry.name)
216                         break
217                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
218                         debugf("offset out of range for %s", dirEntry.name)
219                         break
220                 }
221
222                 vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]))
223                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
224
225                 data := make([][]adapter.Counter, vecLen)
226                 for i := uint64(0); i < vecLen; i++ {
227                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
228                         counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
229                         vecLen2 := vectorLen(counterVec)
230                         data[i] = make([]adapter.Counter, vecLen2)
231                         for j := uint64(0); j < vecLen2; j++ {
232                                 offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
233                                 val := *(*adapter.Counter)(statSegPointer(counterVec, offset))
234                                 data[i][j] = val
235                         }
236                 }
237                 return adapter.SimpleCounterStat(data)
238
239         case adapter.CombinedCounterVector:
240                 if dirEntry.unionData == 0 {
241                         debugf("offset invalid for %s", dirEntry.name)
242                         break
243                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
244                         debugf("offset out of range for %s", dirEntry.name)
245                         break
246                 }
247
248                 vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]))
249                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
250
251                 data := make([][]adapter.CombinedCounter, vecLen)
252                 for i := uint64(0); i < vecLen; i++ {
253                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
254                         counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
255                         vecLen2 := vectorLen(counterVec)
256                         data[i] = make([]adapter.CombinedCounter, vecLen2)
257                         for j := uint64(0); j < vecLen2; j++ {
258                                 offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
259                                 val := *(*adapter.CombinedCounter)(statSegPointer(counterVec, offset))
260                                 data[i][j] = val
261                         }
262                 }
263                 return adapter.CombinedCounterStat(data)
264
265         case adapter.NameVector:
266                 if dirEntry.unionData == 0 {
267                         debugf("offset invalid for %s", dirEntry.name)
268                         break
269                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
270                         debugf("offset out of range for %s", dirEntry.name)
271                         break
272                 }
273
274                 vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]))
275                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
276
277                 data := make([]adapter.Name, vecLen)
278                 for i := uint64(0); i < vecLen; i++ {
279                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
280                         if cb == 0 {
281                                 debugf("name vector out of range for %s (%v)", dirEntry.name, i)
282                                 continue
283                         }
284                         nameVec := unsafe.Pointer(&c.sharedHeader[cb])
285                         vecLen2 := vectorLen(nameVec)
286
287                         nameStr := make([]byte, 0, vecLen2)
288                         for j := uint64(0); j < vecLen2; j++ {
289                                 offset := uintptr(j) * unsafe.Sizeof(byte(0))
290                                 val := *(*byte)(statSegPointer(nameVec, offset))
291                                 if val > 0 {
292                                         nameStr = append(nameStr, val)
293                                 }
294                         }
295                         data[i] = adapter.Name(nameStr)
296                 }
297                 return adapter.NameStat(data)
298
299         default:
300                 // TODO: monitor occurrences with metrics
301                 debugf("Unknown type %d for stat entry: %q", dirEntry.directoryType, dirEntry.name)
302         }
303
304         return nil
305 }
306
307 func (c *statSegment) updateEntryData(dirEntry *statSegDirectoryEntry, stat *adapter.Stat) error {
308         switch (*stat).(type) {
309         case adapter.ScalarStat:
310                 *stat = adapter.ScalarStat(dirEntry.unionData)
311
312         case adapter.ErrorStat:
313                 _, errOffset, _ := c.getOffsets()
314                 offsetVector := unsafe.Pointer(&c.sharedHeader[errOffset])
315
316                 var errData adapter.Counter
317                 if c.legacyVersion {
318                         // error were not vector (per-worker) in VPP 19.04
319                         offset := uintptr(dirEntry.unionData) * unsafe.Sizeof(uint64(0))
320                         val := *(*adapter.Counter)(statSegPointer(offsetVector, offset))
321                         errData = val
322                 } else {
323                         vecLen := vectorLen(offsetVector)
324                         for i := uint64(0); i < vecLen; i++ {
325                                 cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
326                                 offset := uintptr(cb) + uintptr(dirEntry.unionData)*unsafe.Sizeof(adapter.Counter(0))
327                                 val := *(*adapter.Counter)(statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), offset))
328                                 errData += val
329                         }
330                 }
331                 *stat = adapter.ErrorStat(errData)
332
333         case adapter.SimpleCounterStat:
334                 if dirEntry.unionData == 0 {
335                         debugf("offset invalid for %s", dirEntry.name)
336                         break
337                 } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
338                         debugf("offset out of range for %s", dirEntry.name)
339                         break
340                 }
341
342                 vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]))
343                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
344
345                 data := (*stat).(adapter.SimpleCounterStat)
346                 if uint64(len(data)) != vecLen {
347                         return ErrStatDataLenIncorrect
348                 }
349                 for i := uint64(0); i < vecLen; i++ {
350                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
351                         counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
352                         vecLen2 := vectorLen(counterVec)
353                         simpData := data[i]
354                         if uint64(len(simpData)) != vecLen2 {
355                                 return ErrStatDataLenIncorrect
356                         }
357                         for j := uint64(0); j < vecLen2; j++ {
358                                 offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
359                                 val := *(*adapter.Counter)(statSegPointer(counterVec, offset))
360                                 simpData[j] = val
361                         }
362                 }
363
364         case adapter.CombinedCounterStat:
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 := vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]))
374                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
375
376                 data := (*stat).(adapter.CombinedCounterStat)
377                 if uint64(len(data)) != vecLen {
378                         return ErrStatDataLenIncorrect
379                 }
380                 for i := uint64(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 := vectorLen(counterVec)
384                         combData := data[i]
385                         if uint64(len(combData)) != vecLen2 {
386                                 return ErrStatDataLenIncorrect
387                         }
388                         for j := uint64(0); j < vecLen2; j++ {
389                                 offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
390                                 val := *(*adapter.CombinedCounter)(statSegPointer(counterVec, offset))
391                                 combData[j] = val
392                         }
393                 }
394
395         case adapter.NameStat:
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 := vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]))
405                 offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
406
407                 data := (*stat).(adapter.NameStat)
408                 if uint64(len(data)) != vecLen {
409                         return ErrStatDataLenIncorrect
410                 }
411                 for i := uint64(0); i < vecLen; i++ {
412                         cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
413                         if cb == 0 {
414                                 continue
415                         }
416                         nameVec := unsafe.Pointer(&c.sharedHeader[cb])
417                         vecLen2 := vectorLen(nameVec)
418
419                         nameData := data[i]
420                         if uint64(len(nameData))+1 != vecLen2 {
421                                 return ErrStatDataLenIncorrect
422                         }
423                         for j := uint64(0); j < vecLen2; j++ {
424                                 offset := uintptr(j) * unsafe.Sizeof(byte(0))
425                                 val := *(*byte)(statSegPointer(nameVec, offset))
426                                 if val == 0 {
427                                         break
428                                 }
429                                 nameData[j] = val
430                         }
431                 }
432
433         default:
434                 if Debug {
435                         Log.Debugf("Unrecognized stat type %T for stat entry: %v", stat, dirEntry.name)
436                 }
437         }
438         return nil
439 }