From ba6e92d715c59dc71c4e18e66b262d07578d524b Mon Sep 17 00:00:00 2001 From: Vladimir Lavor Date: Thu, 13 May 2021 11:15:48 +0200 Subject: [PATCH] statsclient: added symlinks 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 --- adapter/stats_api.go | 44 ++++++++++++++++-- adapter/statsclient/stat_segment_api.go | 7 ++- adapter/statsclient/statsclient.go | 6 ++- adapter/statsclient/statseg_v1.go | 5 ++- adapter/statsclient/statseg_v2.go | 80 ++++++++++++++++++++++++++++----- adapter/vppapiclient/stat_client.go | 10 ++--- 6 files changed, 127 insertions(+), 25 deletions(-) diff --git a/adapter/stats_api.go b/adapter/stats_api.go index 5b19173..1319d71 100644 --- a/adapter/stats_api.go +++ b/adapter/stats_api.go @@ -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 diff --git a/adapter/statsclient/stat_segment_api.go b/adapter/statsclient/stat_segment_api.go index 23755a5..2161e6e 100644 --- a/adapter/statsclient/stat_segment_api.go +++ b/adapter/statsclient/stat_segment_api.go @@ -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 diff --git a/adapter/statsclient/statsclient.go b/adapter/statsclient/statsclient.go index 16af16a..0b16a77 100644 --- a/adapter/statsclient/statsclient.go +++ b/adapter/statsclient/statsclient.go @@ -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 diff --git a/adapter/statsclient/statseg_v1.go b/adapter/statsclient/statseg_v1.go index e9e8331..efd487e 100644 --- a/adapter/statsclient/statseg_v1.go +++ b/adapter/statsclient/statseg_v1.go @@ -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) diff --git a/adapter/statsclient/statseg_v2.go b/adapter/statsclient/statseg_v2.go index 10bc5f5..16f1729 100644 --- a/adapter/statsclient/statseg_v2.go +++ b/adapter/statsclient/statseg_v2.go @@ -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("") // 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 +} diff --git a/adapter/vppapiclient/stat_client.go b/adapter/vppapiclient/stat_client.go index 734c158..9de2028 100644 --- a/adapter/vppapiclient/stat_client.go +++ b/adapter/vppapiclient/stat_client.go @@ -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 } -- 2.16.6