statsclient: added symlinks
[govpp.git] / adapter / statsclient / statseg_v2.go
1 //  Copyright (c) 2020 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         "bytes"
19         "encoding/binary"
20         "sync/atomic"
21         "unsafe"
22
23         "git.fd.io/govpp.git/adapter"
24 )
25
26 type statSegmentV2 struct {
27         sharedHeader []byte
28         memorySize   int64
29 }
30
31 type sharedHeaderV2 struct {
32         version     uint64
33         base        unsafe.Pointer
34         epoch       int64
35         inProgress  int64
36         dirVector   unsafe.Pointer
37         errorVector unsafe.Pointer
38 }
39
40 type statSegDirectoryEntryV2 struct {
41         directoryType dirType
42         // unionData can represent:
43         // - symlink indexes
44         // - index
45         // - value
46         // - pointer to data
47         unionData uint64
48         name      [128]byte
49 }
50
51 func newStatSegmentV2(data []byte, size int64) *statSegmentV2 {
52         return &statSegmentV2{
53                 sharedHeader: data,
54                 memorySize:   size,
55         }
56 }
57
58 func (ss *statSegmentV2) loadSharedHeader(b []byte) (header sharedHeaderV2) {
59         h := (*sharedHeaderV2)(unsafe.Pointer(&b[0]))
60         return sharedHeaderV2{
61                 version:     atomic.LoadUint64(&h.version),
62                 base:        atomic.LoadPointer(&h.base),
63                 epoch:       atomic.LoadInt64(&h.epoch),
64                 inProgress:  atomic.LoadInt64(&h.inProgress),
65                 dirVector:   atomic.LoadPointer(&h.dirVector),
66                 errorVector: atomic.LoadPointer(&h.errorVector),
67         }
68 }
69
70 func (ss *statSegmentV2) GetDirectoryVector() dirVector {
71         header := ss.loadSharedHeader(ss.sharedHeader)
72         return ss.adjust(dirVector(&header.dirVector))
73 }
74
75 func (ss *statSegmentV2) GetStatDirOnIndex(v dirVector, index uint32) (dirSegment, dirName, dirType) {
76         statSegDir := dirSegment(uintptr(v) + uintptr(index)*unsafe.Sizeof(statSegDirectoryEntryV2{}))
77         dir := (*statSegDirectoryEntryV2)(statSegDir)
78         var name []byte
79         for n := 0; n < len(dir.name); n++ {
80                 if dir.name[n] == 0 {
81                         name = dir.name[:n]
82                         break
83                 }
84         }
85         return statSegDir, name, dir.directoryType
86 }
87
88 func (ss *statSegmentV2) GetEpoch() (int64, bool) {
89         sh := ss.loadSharedHeader(ss.sharedHeader)
90         return sh.epoch, sh.inProgress != 0
91 }
92
93 func (ss *statSegmentV2) CopyEntryData(segment dirSegment, index uint32) adapter.Stat {
94         dirEntry := (*statSegDirectoryEntryV2)(segment)
95         if dirEntry.unionData == 0 {
96                 debugf("data value or pointer not defined for %s", dirEntry.name)
97                 return nil
98         }
99
100         switch adapter.StatType(dirEntry.directoryType) {
101         case statDirScalarIndex:
102                 return adapter.ScalarStat(dirEntry.unionData)
103
104         case statDirErrorIndex:
105                 dirVector := ss.getErrorVector()
106                 if dirVector == nil {
107                         debugf("error vector pointer is out of range for %s", dirEntry.name)
108                         return nil
109                 }
110                 vecLen := *(*uint32)(vectorLen(dirVector))
111                 var errData []adapter.Counter
112                 for i := uint32(0); i < vecLen; i++ {
113                         cb := statSegPointer(dirVector, uintptr(i+1)*unsafe.Sizeof(uint64(0)))
114                         cbVal := ss.adjust(vectorLen(cb))
115                         if cbVal == nil {
116                                 debugf("error counter pointer out of range")
117                                 continue
118                         }
119                         offset := uintptr(dirEntry.unionData) * unsafe.Sizeof(adapter.Counter(0))
120                         val := *(*adapter.Counter)(statSegPointer(cbVal, offset))
121                         errData = append(errData, val)
122                 }
123                 return adapter.ErrorStat(errData)
124
125         case statDirCounterVectorSimple:
126                 dirVector := ss.adjust(dirVector(&dirEntry.unionData))
127                 if dirVector == nil {
128                         debugf("data vector pointer is out of range for %s", dirEntry.name)
129                         return nil
130                 }
131                 vecLen := *(*uint32)(vectorLen(dirVector))
132                 data := make([][]adapter.Counter, vecLen)
133                 for i := uint32(0); i < vecLen; i++ {
134                         counterVectorOffset := statSegPointer(dirVector, uintptr(i+1)*unsafe.Sizeof(uint64(0)))
135                         counterVector := ss.adjust(vectorLen(counterVectorOffset))
136                         if counterVector == nil {
137                                 debugf("counter (vector simple) pointer out of range")
138                                 continue
139                         }
140                         counterVectorLength := *(*uint32)(vectorLen(counterVector))
141                         if index == ^uint32(0) {
142                                 data[i] = make([]adapter.Counter, counterVectorLength)
143                                 for j := uint32(0); j < counterVectorLength; j++ {
144                                         offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
145                                         data[i][j] = *(*adapter.Counter)(statSegPointer(counterVector, offset))
146                                 }
147                         } else {
148                                 data[i] = make([]adapter.Counter, 1) // expect single value
149                                 for j := uint32(0); j < counterVectorLength; j++ {
150                                         offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
151                                         if index == j {
152                                                 data[i][0] = *(*adapter.Counter)(statSegPointer(counterVector, offset))
153                                                 break
154                                         }
155                                 }
156                         }
157                 }
158                 return adapter.SimpleCounterStat(data)
159
160         case statDirCounterVectorCombined:
161                 dirVector := ss.adjust(dirVector(&dirEntry.unionData))
162                 if dirVector == nil {
163                         debugf("data vector pointer is out of range for %s", dirEntry.name)
164                         return nil
165                 }
166                 vecLen := *(*uint32)(vectorLen(dirVector))
167                 data := make([][]adapter.CombinedCounter, vecLen)
168                 for i := uint32(0); i < vecLen; i++ {
169                         counterVectorOffset := statSegPointer(dirVector, uintptr(i+1)*unsafe.Sizeof(uint64(0)))
170                         counterVector := ss.adjust(vectorLen(counterVectorOffset))
171                         if counterVector == nil {
172                                 debugf("counter (vector combined) pointer out of range")
173                                 continue
174                         }
175                         counterVectorLength := *(*uint32)(vectorLen(counterVector))
176                         if index == ^uint32(0) {
177                                 data[i] = make([]adapter.CombinedCounter, counterVectorLength)
178                                 for j := uint32(0); j < counterVectorLength; j++ {
179                                         offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
180                                         data[i][j] = *(*adapter.CombinedCounter)(statSegPointer(counterVector, offset))
181                                 }
182                         } else {
183                                 data[i] = make([]adapter.CombinedCounter, 1) // expect single value pair
184                                 for j := uint32(0); j < counterVectorLength; j++ {
185                                         offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
186                                         if index == j {
187                                                 data[i][0] = *(*adapter.CombinedCounter)(statSegPointer(counterVector, offset))
188                                                 break
189                                         }
190                                 }
191                         }
192                 }
193                 return adapter.CombinedCounterStat(data)
194
195         case statDirNameVector:
196                 dirVector := ss.adjust(dirVector(&dirEntry.unionData))
197                 if dirVector == nil {
198                         debugf("data vector pointer is out of range for %s", dirEntry.name)
199                         return nil
200                 }
201                 vecLen := *(*uint32)(vectorLen(dirVector))
202                 data := make([]adapter.Name, vecLen)
203                 for i := uint32(0); i < vecLen; i++ {
204                         nameVectorOffset := statSegPointer(dirVector, uintptr(i+1)*unsafe.Sizeof(uint64(0)))
205                         if uintptr(nameVectorOffset) == 0 {
206                                 debugf("name vector out of range for %s (%v)", dirEntry.name, i)
207                                 continue
208                         }
209                         nameVector := ss.adjust(vectorLen(nameVectorOffset))
210                         if nameVector == nil {
211                                 debugf("name data pointer out of range")
212                                 continue
213                         }
214                         nameVectorLen := *(*uint32)(vectorLen(nameVector))
215                         name := make([]byte, 0, nameVectorLen)
216                         for j := uint32(0); j < nameVectorLen; j++ {
217                                 offset := uintptr(j) * unsafe.Sizeof(byte(0))
218                                 value := *(*byte)(statSegPointer(nameVector, offset))
219                                 if value > 0 {
220                                         name = append(name, value)
221                                 }
222                         }
223                         data[i] = name
224                 }
225                 return adapter.NameStat(data)
226
227         case statDirEmpty:
228                 return adapter.EmptyStat("<none>")
229                 // no-op
230
231         case statDirSymlink:
232                 // prevent recursion loops
233                 if index != ^uint32(0) {
234                         debugf("received symlink with defined item index")
235                         return nil
236                 }
237                 i1, i2 := ss.getSymlinkIndexes(dirEntry)
238                 // use first index to get the stats directory the symlink points to
239                 header := ss.loadSharedHeader(ss.sharedHeader)
240                 dirVector := ss.adjust(dirVector(&header.dirVector))
241                 statSegDir2 := dirSegment(uintptr(dirVector) + uintptr(i1)*unsafe.Sizeof(statSegDirectoryEntryV2{}))
242
243                 // retry with actual stats segment and use second index to get
244                 // stats for the required item
245                 return ss.CopyEntryData(statSegDir2, i2)
246
247         default:
248                 // TODO: monitor occurrences with metrics
249                 debugf("Unknown type %d for stat entry: %q", dirEntry.directoryType, dirEntry.name)
250         }
251         return nil
252 }
253
254 func (ss *statSegmentV2) UpdateEntryData(segment dirSegment, stat *adapter.Stat) error {
255         dirEntry := (*statSegDirectoryEntryV2)(segment)
256         switch (*stat).(type) {
257         case adapter.ScalarStat:
258                 *stat = adapter.ScalarStat(dirEntry.unionData)
259
260         case adapter.ErrorStat:
261                 dirVector := ss.getErrorVector()
262                 if dirVector == nil {
263                         debugf("error vector pointer is out of range for %s", dirEntry.name)
264                         return nil
265                 }
266                 vecLen := *(*uint32)(vectorLen(dirVector))
267                 var errData []adapter.Counter
268                 for i := uint32(0); i < vecLen; i++ {
269                         cb := statSegPointer(dirVector, uintptr(i+1)*unsafe.Sizeof(uint64(0)))
270                         cbVal := ss.adjust(vectorLen(cb))
271                         if cbVal == nil {
272                                 debugf("error counter pointer out of range")
273                                 continue
274                         }
275                         offset := uintptr(dirEntry.unionData) * unsafe.Sizeof(adapter.Counter(0))
276                         val := *(*adapter.Counter)(statSegPointer(cbVal, offset))
277                         errData = append(errData, val)
278                 }
279                 *stat = adapter.ErrorStat(errData)
280
281         case adapter.SimpleCounterStat:
282                 dirVector := ss.adjust(dirVector(&dirEntry.unionData))
283                 if dirVector == nil {
284                         debugf("data vector pointer is out of range for %s", dirEntry.name)
285                         return nil
286                 }
287                 vecLen := *(*uint32)(vectorLen(dirVector))
288                 data := (*stat).(adapter.SimpleCounterStat)
289                 if uint32(len(data)) != vecLen {
290                         return ErrStatDataLenIncorrect
291                 }
292                 for i := uint32(0); i < vecLen; i++ {
293                         counterVectorOffset := statSegPointer(dirVector, uintptr(i+1)*unsafe.Sizeof(uint64(0)))
294                         counterVector := ss.adjust(vectorLen(counterVectorOffset))
295                         if counterVector == nil {
296                                 debugf("counter (vector simple) pointer out of range")
297                                 continue
298                         }
299                         counterVectorLength := *(*uint32)(vectorLen(counterVector))
300                         data[i] = make([]adapter.Counter, counterVectorLength)
301                         for j := uint32(0); j < counterVectorLength; j++ {
302                                 offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
303                                 val := *(*adapter.Counter)(statSegPointer(counterVector, offset))
304                                 data[i][j] = val
305                         }
306                 }
307
308         case adapter.CombinedCounterStat:
309                 dirVector := ss.adjust(dirVector(&dirEntry.unionData))
310                 if dirVector == nil {
311                         debugf("data vector pointer is out of range for %s", dirEntry.name)
312                         return nil
313                 }
314                 vecLen := *(*uint32)(vectorLen(dirVector))
315                 data := (*stat).(adapter.CombinedCounterStat)
316                 for i := uint32(0); i < vecLen; i++ {
317                         counterVectorOffset := statSegPointer(dirVector, uintptr(i+1)*unsafe.Sizeof(uint64(0)))
318                         counterVector := ss.adjust(vectorLen(counterVectorOffset))
319                         if counterVector == nil {
320                                 debugf("counter (vector combined) pointer out of range")
321                                 continue
322                         }
323                         counterVectorLength := *(*uint32)(vectorLen(counterVector))
324                         data[i] = make([]adapter.CombinedCounter, counterVectorLength)
325                         for j := uint32(0); j < counterVectorLength; j++ {
326                                 offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
327                                 val := *(*adapter.CombinedCounter)(statSegPointer(counterVector, offset))
328                                 data[i][j] = val
329                         }
330                 }
331
332         case adapter.NameStat:
333                 dirVector := ss.adjust(dirVector(&dirEntry.unionData))
334                 if dirVector == nil {
335                         debugf("data vector pointer is out of range for %s", dirEntry.name)
336                         return nil
337                 }
338                 vecLen := *(*uint32)(vectorLen(dirVector))
339                 data := (*stat).(adapter.NameStat)
340                 for i := uint32(0); i < vecLen; i++ {
341                         nameVectorOffset := statSegPointer(dirVector, uintptr(i+1)*unsafe.Sizeof(uint64(0)))
342                         if uintptr(nameVectorOffset) == 0 {
343                                 debugf("name vector out of range for %s (%v)", dirEntry.name, i)
344                                 continue
345                         }
346                         nameVector := ss.adjust(vectorLen(nameVectorOffset))
347                         if nameVector == nil {
348                                 debugf("name data pointer out of range")
349                                 continue
350                         }
351                         nameVectorLen := *(*uint32)(vectorLen(nameVector))
352                         nameData := data[i]
353                         if uint32(len(nameData))+1 != nameVectorLen {
354                                 return ErrStatDataLenIncorrect
355                         }
356                         for j := uint32(0); j < nameVectorLen; j++ {
357                                 offset := uintptr(j) * unsafe.Sizeof(byte(0))
358                                 value := *(*byte)(statSegPointer(nameVector, offset))
359                                 if value == 0 {
360                                         break
361                                 }
362                                 nameData[j] = value
363                         }
364                 }
365
366         default:
367                 if Debug {
368                         Log.Debugf("Unrecognized stat type %T for stat entry: %v", stat, dirEntry.name)
369                 }
370         }
371         return nil
372 }
373
374 // Adjust data pointer using shared header and base and return
375 // the pointer to a data segment
376 func (ss *statSegmentV2) adjust(data dirVector) dirVector {
377         header := ss.loadSharedHeader(ss.sharedHeader)
378         adjusted := dirVector(uintptr(unsafe.Pointer(&ss.sharedHeader[0])) +
379                 uintptr(*(*uint64)(data)) - uintptr(*(*uint64)(unsafe.Pointer(&header.base))))
380         if uintptr(unsafe.Pointer(&ss.sharedHeader[len(ss.sharedHeader)-1])) <= uintptr(adjusted) ||
381                 uintptr(unsafe.Pointer(&ss.sharedHeader[0])) >= uintptr(adjusted) {
382                 return nil
383         }
384         return adjusted
385 }
386
387 func (ss *statSegmentV2) getErrorVector() dirVector {
388         header := ss.loadSharedHeader(ss.sharedHeader)
389         return ss.adjust(dirVector(&header.errorVector))
390 }
391
392 func (ss *statSegmentV2) getSymlinkIndexes(dirEntry *statSegDirectoryEntryV2) (index1, index2 uint32) {
393         var b bytes.Buffer
394         if err := binary.Write(&b, binary.LittleEndian, dirEntry.unionData); err != nil {
395                 debugf("error getting symlink indexes for %s: %v", dirEntry.name, err)
396                 return
397         }
398         if len(b.Bytes()) != 8 {
399                 debugf("incorrect symlink union data length for %s: expected 8, got %d", dirEntry.name, len(b.Bytes()))
400                 return
401         }
402         for i := range b.Bytes()[:4] {
403                 index1 += uint32(b.Bytes()[i]) << (uint32(i) * 8)
404         }
405         for i := range b.Bytes()[4:] {
406                 index2 += uint32(b.Bytes()[i+4]) << (uint32(i) * 8)
407         }
408         return
409 }