statsclient: added symlinks 95/32295/7
authorVladimir Lavor <vlavor@cisco.com>
Thu, 13 May 2021 09:15:48 +0000 (11:15 +0200)
committerVladimir Lavor <vlavor@cisco.com>
Wed, 26 May 2021 07:53:08 +0000 (09:53 +0200)
Symlink support based on https://gerrit.fd.io/r/c/vpp/+/31636

Added new stat types `CounterStat` and `CombinedCounterStat` which
represent simple/combined value of a single item (interface, node)
as a array of values by workers.

Example:
/if/names                      NameVector [local0 tap0 tap1]
/if/ip6               SimpleCounterVector [[0 9 0] [0 25 3] [0 0 60] [0 0 0]]
/interfaces/tap0/ip6  SimpleCounterVector [[9] [25] [0] [0]]
/interfaces/tap1/ip6  SimpleCounterVector [[0] [3] [60] [0]]

Field `Symlink` added to StatEntry to mark symlink stats.

For stats v2 only

Change-Id: Iadc825f3c42b05bfc9a91462dba8a0b0068c7cd6
Signed-off-by: Vladimir Lavor <vlavor@cisco.com>
adapter/stats_api.go
adapter/statsclient/stat_segment_api.go
adapter/statsclient/statsclient.go
adapter/statsclient/statseg_v1.go
adapter/statsclient/statseg_v2.go
adapter/vppapiclient/stat_client.go

index 5b19173..1319d71 100644 (file)
@@ -63,6 +63,7 @@ const (
        ErrorIndex            StatType = 4
        NameVector            StatType = 5
        Empty                 StatType = 6
+       Symlink               StatType = 7
 )
 
 func (d StatType) String() string {
@@ -79,6 +80,8 @@ func (d StatType) String() string {
                return "NameVector"
        case Empty:
                return "Empty"
+       case Symlink:
+               return "Symlink"
        }
        return fmt.Sprintf("UnknownStatType(%d)", d)
 }
@@ -99,8 +102,9 @@ type StatIdentifier struct {
 // is defined by Type.
 type StatEntry struct {
        StatIdentifier
-       Type StatType
-       Data Stat
+       Type    StatType
+       Data    Stat
+       Symlink bool
 }
 
 // Counter represents simple counter with single value, which is usually packet count.
@@ -129,6 +133,9 @@ type Stat interface {
        // IsZero returns true if all of its values equal to zero.
        IsZero() bool
 
+       // Type returns underlying type of a stat
+       Type() StatType
+
        // isStat is intentionally  unexported to limit implementations of interface to this package,
        isStat()
 }
@@ -139,13 +146,13 @@ type ScalarStat float64
 // ErrorStat represents stat for ErrorIndex. The array represents workers.
 type ErrorStat []Counter
 
-// SimpleCounterStat represents stat for SimpleCounterVector.
+// SimpleCounterStat represents indexed stat for SimpleCounterVector.
 // The outer array represents workers and the inner array represents interface/node/.. indexes.
 // Values should be aggregated per interface/node for every worker.
 // ReduceSimpleCounterStatIndex can be used to reduce specific index.
 type SimpleCounterStat [][]Counter
 
-// CombinedCounterStat represents stat for CombinedCounterVector.
+// CombinedCounterStat represents indexed stat for CombinedCounterVector.
 // The outer array represents workers and the inner array represents interface/node/.. indexes.
 // Values should be aggregated per interface/node for every worker.
 // ReduceCombinedCounterStatIndex can be used to reduce specific index.
@@ -167,6 +174,11 @@ func (EmptyStat) isStat()           {}
 func (s ScalarStat) IsZero() bool {
        return s == 0
 }
+
+func (s ScalarStat) Type() StatType {
+       return ScalarIndex
+}
+
 func (s ErrorStat) IsZero() bool {
        if s == nil {
                return true
@@ -178,6 +190,11 @@ func (s ErrorStat) IsZero() bool {
        }
        return true
 }
+
+func (s ErrorStat) Type() StatType {
+       return ErrorIndex
+}
+
 func (s SimpleCounterStat) IsZero() bool {
        if s == nil {
                return true
@@ -191,6 +208,11 @@ func (s SimpleCounterStat) IsZero() bool {
        }
        return true
 }
+
+func (s SimpleCounterStat) Type() StatType {
+       return SimpleCounterVector
+}
+
 func (s CombinedCounterStat) IsZero() bool {
        if s == nil {
                return true
@@ -207,6 +229,11 @@ func (s CombinedCounterStat) IsZero() bool {
        }
        return true
 }
+
+func (s CombinedCounterStat) Type() StatType {
+       return CombinedCounterVector
+}
+
 func (s NameStat) IsZero() bool {
        if s == nil {
                return true
@@ -218,10 +245,19 @@ func (s NameStat) IsZero() bool {
        }
        return true
 }
+
+func (s NameStat) Type() StatType {
+       return NameVector
+}
+
 func (s EmptyStat) IsZero() bool {
        return true
 }
 
+func (s EmptyStat) Type() StatType {
+       return Empty
+}
+
 // ReduceSimpleCounterStatIndex returns reduced SimpleCounterStat s for index i.
 func ReduceSimpleCounterStatIndex(s SimpleCounterStat, i int) uint64 {
        var val uint64
index 23755a5..2161e6e 100644 (file)
@@ -46,6 +46,7 @@ const (
        statDirErrorIndex            = 4
        statDirNameVector            = 5
        statDirEmpty                 = 6
+       statDirSymlink               = 7
 )
 
 type (
@@ -75,8 +76,10 @@ type statSegment interface {
        GetEpoch() (int64, bool)
 
        // CopyEntryData accepts pointer to a directory segment and returns adapter.Stat
-       // based on directory type populated with data
-       CopyEntryData(segment dirSegment) adapter.Stat
+       // based on directory type populated with data. The index is an optional parameter
+       // (used by symlinks) returning stats for item on the given index only.
+       // Use ^uint32(0) as an empty index (since 0 is a valid value).
+       CopyEntryData(segment dirSegment, index uint32) adapter.Stat
 
        // UpdateEntryData accepts pointer to a directory segment with data, and stat
        // segment to update
index 16af16a..0b16a77 100644 (file)
@@ -505,13 +505,15 @@ func (sc *StatsClient) getStatEntriesOnIndex(vector dirVector, indexes ...uint32
                if len(dirName) == 0 {
                        return
                }
+               d := sc.CopyEntryData(dirPtr, ^uint32(0))
                entries = append(entries, adapter.StatEntry{
                        StatIdentifier: adapter.StatIdentifier{
                                Index: index,
                                Name:  dirName,
                        },
-                       Type: adapter.StatType(dirType),
-                       Data: sc.CopyEntryData(dirPtr),
+                       Type:    d.Type(),
+                       Data:    d,
+                       Symlink: adapter.StatType(dirType) == adapter.Symlink,
                })
        }
        return entries, nil
index e9e8331..efd487e 100644 (file)
@@ -93,7 +93,7 @@ func (ss *statSegmentV1) GetEpoch() (int64, bool) {
        return sh.epoch, sh.inProgress != 0
 }
 
-func (ss *statSegmentV1) CopyEntryData(segment dirSegment) adapter.Stat {
+func (ss *statSegmentV1) CopyEntryData(segment dirSegment, _ uint32) adapter.Stat {
        dirEntry := (*statSegDirectoryEntryV1)(segment)
        dirType := adapter.StatType(dirEntry.directoryType)
 
@@ -214,6 +214,9 @@ func (ss *statSegmentV1) CopyEntryData(segment dirSegment) adapter.Stat {
        case statDirEmpty:
                // no-op
 
+       case statDirSymlink:
+               debugf("Symlinks are not supported for stats v1")
+
        default:
                // TODO: monitor occurrences with metrics
                debugf("Unknown type %d for stat entry: %q", dirEntry.directoryType, dirEntry.name)
index 10bc5f5..16f1729 100644 (file)
@@ -15,6 +15,8 @@
 package statsclient
 
 import (
+       "bytes"
+       "encoding/binary"
        "sync/atomic"
        "unsafe"
 
@@ -38,6 +40,7 @@ type sharedHeaderV2 struct {
 type statSegDirectoryEntryV2 struct {
        directoryType dirType
        // unionData can represent:
+       // - symlink indexes
        // - index
        // - value
        // - pointer to data
@@ -87,7 +90,7 @@ func (ss *statSegmentV2) GetEpoch() (int64, bool) {
        return sh.epoch, sh.inProgress != 0
 }
 
-func (ss *statSegmentV2) CopyEntryData(segment dirSegment) adapter.Stat {
+func (ss *statSegmentV2) CopyEntryData(segment dirSegment, index uint32) adapter.Stat {
        dirEntry := (*statSegDirectoryEntryV2)(segment)
        if dirEntry.unionData == 0 {
                debugf("data value or pointer not defined for %s", dirEntry.name)
@@ -135,11 +138,21 @@ func (ss *statSegmentV2) CopyEntryData(segment dirSegment) adapter.Stat {
                                continue
                        }
                        counterVectorLength := *(*uint32)(vectorLen(counterVector))
-                       data[i] = make([]adapter.Counter, counterVectorLength)
-                       for j := uint32(0); j < counterVectorLength; j++ {
-                               offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
-                               val := *(*adapter.Counter)(statSegPointer(counterVector, offset))
-                               data[i][j] = val
+                       if index == ^uint32(0) {
+                               data[i] = make([]adapter.Counter, counterVectorLength)
+                               for j := uint32(0); j < counterVectorLength; j++ {
+                                       offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
+                                       data[i][j] = *(*adapter.Counter)(statSegPointer(counterVector, offset))
+                               }
+                       } else {
+                               data[i] = make([]adapter.Counter, 1) // expect single value
+                               for j := uint32(0); j < counterVectorLength; j++ {
+                                       offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
+                                       if index == j {
+                                               data[i][0] = *(*adapter.Counter)(statSegPointer(counterVector, offset))
+                                               break
+                                       }
+                               }
                        }
                }
                return adapter.SimpleCounterStat(data)
@@ -160,11 +173,21 @@ func (ss *statSegmentV2) CopyEntryData(segment dirSegment) adapter.Stat {
                                continue
                        }
                        counterVectorLength := *(*uint32)(vectorLen(counterVector))
-                       data[i] = make([]adapter.CombinedCounter, counterVectorLength)
-                       for j := uint32(0); j < counterVectorLength; j++ {
-                               offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
-                               val := *(*adapter.CombinedCounter)(statSegPointer(counterVector, offset))
-                               data[i][j] = val
+                       if index == ^uint32(0) {
+                               data[i] = make([]adapter.CombinedCounter, counterVectorLength)
+                               for j := uint32(0); j < counterVectorLength; j++ {
+                                       offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
+                                       data[i][j] = *(*adapter.CombinedCounter)(statSegPointer(counterVector, offset))
+                               }
+                       } else {
+                               data[i] = make([]adapter.CombinedCounter, 1) // expect single value pair
+                               for j := uint32(0); j < counterVectorLength; j++ {
+                                       offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
+                                       if index == j {
+                                               data[i][0] = *(*adapter.CombinedCounter)(statSegPointer(counterVector, offset))
+                                               break
+                                       }
+                               }
                        }
                }
                return adapter.CombinedCounterStat(data)
@@ -205,6 +228,22 @@ func (ss *statSegmentV2) CopyEntryData(segment dirSegment) adapter.Stat {
                return adapter.EmptyStat("<none>")
                // no-op
 
+       case statDirSymlink:
+               // prevent recursion loops
+               if index != ^uint32(0) {
+                       debugf("received symlink with defined item index")
+                       return nil
+               }
+               i1, i2 := ss.getSymlinkIndexes(dirEntry)
+               // use first index to get the stats directory the symlink points to
+               header := ss.loadSharedHeader(ss.sharedHeader)
+               dirVector := ss.adjust(dirVector(&header.dirVector))
+               statSegDir2 := dirSegment(uintptr(dirVector) + uintptr(i1)*unsafe.Sizeof(statSegDirectoryEntryV2{}))
+
+               // retry with actual stats segment and use second index to get
+               // stats for the required item
+               return ss.CopyEntryData(statSegDir2, i2)
+
        default:
                // TODO: monitor occurrences with metrics
                debugf("Unknown type %d for stat entry: %q", dirEntry.directoryType, dirEntry.name)
@@ -349,3 +388,22 @@ func (ss *statSegmentV2) getErrorVector() dirVector {
        header := ss.loadSharedHeader(ss.sharedHeader)
        return ss.adjust(dirVector(&header.errorVector))
 }
+
+func (ss *statSegmentV2) getSymlinkIndexes(dirEntry *statSegDirectoryEntryV2) (index1, index2 uint32) {
+       var b bytes.Buffer
+       if err := binary.Write(&b, binary.LittleEndian, dirEntry.unionData); err != nil {
+               debugf("error getting symlink indexes for %s: %v", dirEntry.name, err)
+               return
+       }
+       if len(b.Bytes()) != 8 {
+               debugf("incorrect symlink union data length for %s: expected 8, got %d", dirEntry.name, len(b.Bytes()))
+               return
+       }
+       for i := range b.Bytes()[:4] {
+               index1 += uint32(b.Bytes()[i]) << (uint32(i) * 8)
+       }
+       for i := range b.Bytes()[4:] {
+               index2 += uint32(b.Bytes()[i+4]) << (uint32(i) * 8)
+       }
+       return
+}
index 734c158..9de2028 100644 (file)
@@ -153,10 +153,10 @@ func (c *statClient) DumpStats(patterns ...string) (stats []adapter.StatEntry, e
                        vector := make([][]adapter.CombinedCounter, length)
                        for k := 0; k < length; k++ {
                                for j := 0; j < int(C.govpp_stat_segment_vec_len(unsafe.Pointer(C.govpp_stat_segment_data_get_combined_counter_index(&v, C.int(k))))); j++ {
-                                       vector[k] = append(vector[k], adapter.CombinedCounter([2]uint64{
+                                       vector[k] = append(vector[k], [2]uint64{
                                                uint64(C.govpp_stat_segment_data_get_combined_counter_index_packets(&v, C.int(k), C.int(j))),
                                                uint64(C.govpp_stat_segment_data_get_combined_counter_index_bytes(&v, C.int(k), C.int(j))),
-                                       }))
+                                       })
                                }
                        }
                        stat.Data = adapter.CombinedCounterStat(vector)
@@ -186,15 +186,15 @@ func (c *statClient) DumpStats(patterns ...string) (stats []adapter.StatEntry, e
        return stats, nil
 }
 
-func (c *statClient) PrepareDir(prefixes ...string) (*adapter.StatDir, error) {
+func (c *statClient) PrepareDir(_ ...string) (*adapter.StatDir, error) {
        return nil, adapter.ErrNotImplemented
 }
 
-func (c *statClient) PrepareDirOnIndex(indexes ...uint32) (*adapter.StatDir, error) {
+func (c *statClient) PrepareDirOnIndex(_ ...uint32) (*adapter.StatDir, error) {
        return nil, adapter.ErrNotImplemented
 }
 
-func (c *statClient) UpdateDir(dir *adapter.StatDir) error {
+func (c *statClient) UpdateDir(_ *adapter.StatDir) error {
        return adapter.ErrNotImplemented
 }