summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVladimir Lavor <vlavor@cisco.com>2021-03-17 12:27:10 +0100
committerVladimir Lavor <vlavor@cisco.com>2021-05-05 13:26:19 +0200
commitc380ee6064379258768fdfe4e9d4ad9138980ec0 (patch)
tree28903f9ee23bb3206e21a2c04001ebfc560de944
parenta6607d9c1ba37320984c13580c932076cbff6dd6 (diff)
statsclient: allow index as pattern
* ListStats() returns stats identifiers containing the stat name and index * New method PrepareDirOnIndex(indexes...). Instead of the name filter it does not browse through all available indexes * Stats example shows how to get the last epoch value (added "e" or "epoch" command) Change-Id: Ibb15090fb0dfdb7f9b0ecf8ac07a5eb9a9ace8f8 Signed-off-by: Vladimir Lavor <vlavor@cisco.com>
-rw-r--r--adapter/mock/mock_stats_adapter.go13
-rw-r--r--adapter/stats_api.go23
-rw-r--r--adapter/statsclient/stat_segment_api.go27
-rw-r--r--adapter/statsclient/statsclient.go237
-rw-r--r--adapter/statsclient/statseg_v1.go68
-rw-r--r--adapter/statsclient/statseg_v2.go38
-rw-r--r--adapter/vppapiclient/stat_client.go18
-rw-r--r--adapter/vppapiclient/stat_client_stub.go6
-rw-r--r--examples/stats-client/stats_api.go75
9 files changed, 307 insertions, 198 deletions
diff --git a/adapter/mock/mock_stats_adapter.go b/adapter/mock/mock_stats_adapter.go
index 55b1831..08d18d4 100644
--- a/adapter/mock/mock_stats_adapter.go
+++ b/adapter/mock/mock_stats_adapter.go
@@ -46,10 +46,13 @@ func (a *StatsAdapter) Disconnect() error {
}
// ListStats mocks name listing for all stats.
-func (a *StatsAdapter) ListStats(patterns ...string) ([]string, error) {
- var statNames []string
+func (a *StatsAdapter) ListStats(patterns ...string) ([]adapter.StatIdentifier, error) {
+ var statNames []adapter.StatIdentifier
for _, stat := range a.entries {
- statNames = append(statNames, string(stat.Name))
+ statNames = append(statNames, adapter.StatIdentifier{
+ Name: stat.Name,
+ Index: stat.Index,
+ })
}
return statNames, nil
}
@@ -63,6 +66,10 @@ func (a *StatsAdapter) PrepareDir(prefixes ...string) (*adapter.StatDir, error)
return a.dir, nil
}
+func (a *StatsAdapter) PrepareDirOnIndex(indexes ...uint32) (*adapter.StatDir, error) {
+ return a.dir, nil
+}
+
func (a *StatsAdapter) UpdateDir(dir *adapter.StatDir) error {
*dir = *a.dir
return nil
diff --git a/adapter/stats_api.go b/adapter/stats_api.go
index d15dee8..5b19173 100644
--- a/adapter/stats_api.go
+++ b/adapter/stats_api.go
@@ -38,13 +38,15 @@ type StatsAPI interface {
// Disconnect terminates client connection.
Disconnect() error
- // ListStats lists names for stats matching patterns.
- ListStats(patterns ...string) (names []string, err error)
+ // ListStats lists indexed names for stats matching patterns.
+ ListStats(patterns ...string) (indexes []StatIdentifier, err error)
// DumpStats dumps all stat entries.
DumpStats(patterns ...string) (entries []StatEntry, err error)
// PrepareDir prepares new stat dir for entries that match any of prefixes.
PrepareDir(patterns ...string) (*StatDir, error)
+ // PrepareDirOnIndex prepares new stat dir for entries that match any of indexes.
+ PrepareDirOnIndex(indexes ...uint32) (*StatDir, error)
// UpdateDir updates stat dir and all of their entries.
UpdateDir(dir *StatDir) error
}
@@ -84,14 +86,19 @@ func (d StatType) String() string {
// StatDir defines directory of stats entries created by PrepareDir.
type StatDir struct {
Epoch int64
- Indexes []uint32
Entries []StatEntry
}
+// StatIdentifier holds a stat entry name and index
+type StatIdentifier struct {
+ Index uint32
+ Name []byte
+}
+
// StatEntry represents single stat entry. The type of stat stored in Data
// is defined by Type.
type StatEntry struct {
- Name []byte
+ StatIdentifier
Type StatType
Data Stat
}
@@ -103,11 +110,11 @@ type Counter uint64
type CombinedCounter [2]uint64
func (s CombinedCounter) Packets() uint64 {
- return uint64(s[0])
+ return s[0]
}
func (s CombinedCounter) Bytes() uint64 {
- return uint64(s[1])
+ return s[1]
}
// Name represents string value stored under name vector.
@@ -228,8 +235,8 @@ func ReduceSimpleCounterStatIndex(s SimpleCounterStat, i int) uint64 {
func ReduceCombinedCounterStatIndex(s CombinedCounterStat, i int) [2]uint64 {
var val [2]uint64
for _, w := range s {
- val[0] += uint64(w[i][0])
- val[1] += uint64(w[i][1])
+ val[0] += w[i][0]
+ val[1] += w[i][1]
}
return val
}
diff --git a/adapter/statsclient/stat_segment_api.go b/adapter/statsclient/stat_segment_api.go
index 0ca0c8b..23755a5 100644
--- a/adapter/statsclient/stat_segment_api.go
+++ b/adapter/statsclient/stat_segment_api.go
@@ -48,15 +48,18 @@ const (
statDirEmpty = 6
)
-type statDirectoryType int32
-
-type statDirectoryName []byte
+type (
+ dirVector unsafe.Pointer
+ dirSegment unsafe.Pointer
+ dirName []byte
+ dirType int32
+)
// statSegment represents common API for every stats API version
type statSegment interface {
// GetDirectoryVector returns pointer to memory where the beginning
// of the data directory is located.
- GetDirectoryVector() unsafe.Pointer
+ GetDirectoryVector() dirVector
// GetStatDirOnIndex accepts directory vector and particular index.
// Returns pointer to the beginning of the segment. Also the directory
@@ -65,7 +68,7 @@ type statSegment interface {
//
// Note that if the index is equal to 0, the result pointer points to
// the same memory address as the argument.
- GetStatDirOnIndex(directory unsafe.Pointer, index uint32) (unsafe.Pointer, statDirectoryName, statDirectoryType)
+ GetStatDirOnIndex(v dirVector, index uint32) (dirSegment, dirName, dirType)
// GetEpoch re-loads stats header and returns current epoch
//and 'inProgress' value
@@ -73,11 +76,11 @@ type statSegment interface {
// CopyEntryData accepts pointer to a directory segment and returns adapter.Stat
// based on directory type populated with data
- CopyEntryData(segment unsafe.Pointer) adapter.Stat
+ CopyEntryData(segment dirSegment) adapter.Stat
// UpdateEntryData accepts pointer to a directory segment with data, and stat
// segment to update
- UpdateEntryData(segment unsafe.Pointer, s *adapter.Stat) error
+ UpdateEntryData(segment dirSegment, s *adapter.Stat) error
}
// vecHeader represents a vector header
@@ -86,7 +89,7 @@ type vecHeader struct {
vectorData [0]uint8
}
-func (t statDirectoryType) String() string {
+func (t dirType) String() string {
return adapter.StatType(t).String()
}
@@ -102,12 +105,12 @@ func getVersion(data []byte) uint64 {
return version.value
}
-func vectorLen(v unsafe.Pointer) unsafe.Pointer {
+func vectorLen(v dirVector) dirVector {
vec := *(*vecHeader)(unsafe.Pointer(uintptr(v) - unsafe.Sizeof(uint64(0))))
- return unsafe.Pointer(&vec.length)
+ return dirVector(&vec.length)
}
//go:nosplit
-func statSegPointer(p unsafe.Pointer, offset uintptr) unsafe.Pointer {
- return unsafe.Pointer(uintptr(p) + offset)
+func statSegPointer(v dirVector, offset uintptr) dirVector {
+ return dirVector(uintptr(v) + offset)
}
diff --git a/adapter/statsclient/statsclient.go b/adapter/statsclient/statsclient.go
index b2d91db..16af16a 100644
--- a/adapter/statsclient/statsclient.go
+++ b/adapter/statsclient/statsclient.go
@@ -157,7 +157,7 @@ func (sc *StatsClient) Disconnect() error {
return nil
}
-func (sc *StatsClient) ListStats(patterns ...string) ([]string, error) {
+func (sc *StatsClient) ListStats(patterns ...string) (entries []adapter.StatIdentifier, err error) {
if !sc.isConnected() {
return nil, adapter.ErrStatsDisconnected
}
@@ -166,76 +166,35 @@ func (sc *StatsClient) ListStats(patterns ...string) ([]string, error) {
return nil, adapter.ErrStatsAccessFailed
}
- indexes, err := sc.listIndexes(patterns...)
+ entries, err = sc.getIdentifierEntries(patterns...)
if err != nil {
return nil, err
}
- dirVector := sc.GetDirectoryVector()
- if dirVector == nil {
- return nil, fmt.Errorf("failed to list stats: %v", err)
- }
- vecLen := *(*uint32)(vectorLen(dirVector))
-
- var names []string
- for _, index := range indexes {
- if index >= vecLen {
- return nil, fmt.Errorf("stat entry index %d out of dir vector len (%d)", index, vecLen)
- }
- _, dirName, _ := sc.GetStatDirOnIndex(dirVector, index)
- names = append(names, string(dirName))
- }
-
if !sc.accessEnd(accessEpoch) {
return nil, adapter.ErrStatsDataBusy
}
-
- return names, nil
+ return entries, nil
}
func (sc *StatsClient) DumpStats(patterns ...string) (entries []adapter.StatEntry, err error) {
if !sc.isConnected() {
return nil, adapter.ErrStatsDisconnected
}
+
accessEpoch := sc.accessStart()
if accessEpoch == 0 {
return nil, adapter.ErrStatsAccessFailed
}
- indexes, err := sc.listIndexes(patterns...)
+ entries, err = sc.getStatEntries(patterns...)
if err != nil {
return nil, err
}
- dirVector := sc.GetDirectoryVector()
- if dirVector == nil {
- return nil, err
- }
- dirLen := *(*uint32)(vectorLen(dirVector))
-
- debugf("dumping entries for %d indexes", len(indexes))
-
- entries = make([]adapter.StatEntry, 0, len(indexes))
- for _, index := range indexes {
- if index >= dirLen {
- return nil, fmt.Errorf("stat entry index %d out of dir vector length (%d)", index, dirLen)
- }
- dirPtr, dirName, dirType := sc.GetStatDirOnIndex(dirVector, index)
- if len(dirName) == 0 {
- continue
- }
- entry := adapter.StatEntry{
- Name: append([]byte(nil), dirName...),
- Type: adapter.StatType(dirType),
- Data: sc.CopyEntryData(dirPtr),
- }
- entries = append(entries, entry)
- }
-
if !sc.accessEnd(accessEpoch) {
return nil, adapter.ErrStatsDataBusy
}
-
return entries, nil
}
@@ -243,49 +202,55 @@ func (sc *StatsClient) PrepareDir(patterns ...string) (*adapter.StatDir, error)
if !sc.isConnected() {
return nil, adapter.ErrStatsDisconnected
}
- dir := new(adapter.StatDir)
accessEpoch := sc.accessStart()
if accessEpoch == 0 {
return nil, adapter.ErrStatsAccessFailed
}
- indexes, err := sc.listIndexes(patterns...)
+ entries, err := sc.getStatEntries(patterns...)
if err != nil {
return nil, err
}
- dir.Indexes = indexes
- dirVector := sc.GetDirectoryVector()
- if dirVector == nil {
- return nil, err
+ if !sc.accessEnd(accessEpoch) {
+ return nil, adapter.ErrStatsDataBusy
}
- dirLen := *(*uint32)(vectorLen(dirVector))
- debugf("dumping entries for %d indexes", len(indexes))
+ dir := &adapter.StatDir{
+ Epoch: accessEpoch,
+ Entries: entries,
+ }
- entries := make([]adapter.StatEntry, 0, len(indexes))
- for _, index := range indexes {
- if index >= dirLen {
- return nil, fmt.Errorf("stat entry index %d out of dir vector length (%d)", index, dirLen)
- }
- dirPtr, dirName, dirType := sc.GetStatDirOnIndex(dirVector, index)
- if len(dirName) == 0 {
- continue
- }
- entry := adapter.StatEntry{
- Name: append([]byte(nil), dirName...),
- Type: adapter.StatType(dirType),
- Data: sc.CopyEntryData(dirPtr),
- }
- entries = append(entries, entry)
+ return dir, nil
+}
+
+func (sc *StatsClient) PrepareDirOnIndex(indexes ...uint32) (*adapter.StatDir, error) {
+ if !sc.isConnected() {
+ return nil, adapter.ErrStatsDisconnected
+ }
+
+ accessEpoch := sc.accessStart()
+ if accessEpoch == 0 {
+ return nil, adapter.ErrStatsAccessFailed
+ }
+ vector := sc.GetDirectoryVector()
+ if vector == nil {
+ return nil, fmt.Errorf("failed to prepare dir on index: directory vector is nil")
+ }
+ entries, err := sc.getStatEntriesOnIndex(vector, indexes...)
+ if err != nil {
+ return nil, err
}
- dir.Entries = entries
if !sc.accessEnd(accessEpoch) {
return nil, adapter.ErrStatsDataBusy
}
- dir.Epoch = accessEpoch
+
+ dir := &adapter.StatDir{
+ Epoch: accessEpoch,
+ Entries: entries,
+ }
return dir, nil
}
@@ -304,34 +269,18 @@ func (sc *StatsClient) UpdateDir(dir *adapter.StatDir) (err error) {
if accessEpoch == 0 {
return adapter.ErrStatsAccessFailed
}
-
dirVector := sc.GetDirectoryVector()
if dirVector == nil {
return err
}
- for i, index := range dir.Indexes {
- statSegDir, dirName, dirType := sc.GetStatDirOnIndex(dirVector, index)
- if len(dirName) == 0 {
- continue
- }
- entry := &dir.Entries[i]
- if !bytes.Equal(dirName, entry.Name) {
- continue
- }
- if adapter.StatType(dirType) != entry.Type {
- continue
- }
- if entry.Data == nil {
- continue
- }
- if err := sc.UpdateEntryData(statSegDir, &entry.Data); err != nil {
- return fmt.Errorf("updating stat data for entry %s failed: %v", dirName, err)
+ for i := 0; i < len(dir.Entries); i++ {
+ if err := sc.updateStatOnIndex(&dir.Entries[i], dirVector); err != nil {
+ return err
}
}
if !sc.accessEnd(accessEpoch) {
return adapter.ErrStatsDataBusy
}
-
return nil
}
@@ -518,10 +467,79 @@ func (sc *StatsClient) accessEnd(accessEpoch int64) bool {
return true
}
+// getStatEntries retrieves all stats matching desired patterns, or all stats if no pattern is provided.
+func (sc *StatsClient) getStatEntries(patterns ...string) (entries []adapter.StatEntry, err error) {
+ vector := sc.GetDirectoryVector()
+ if vector == nil {
+ return nil, fmt.Errorf("failed to get stat entries: directory vector is nil")
+ }
+ indexes, err := sc.listIndexes(vector, patterns...)
+ if err != nil {
+ return nil, err
+ }
+ return sc.getStatEntriesOnIndex(vector, indexes...)
+}
+
+// getIdentifierEntries retrieves all identifiers matching desired patterns, or all identifiers
+// if no pattern is provided.
+func (sc *StatsClient) getIdentifierEntries(patterns ...string) (identifiers []adapter.StatIdentifier, err error) {
+ vector := sc.GetDirectoryVector()
+ if vector == nil {
+ return nil, fmt.Errorf("failed to get identifier entries: directory vector is nil")
+ }
+ indexes, err := sc.listIndexes(vector, patterns...)
+ if err != nil {
+ return nil, err
+ }
+ return sc.getIdentifierEntriesOnIndex(vector, indexes...)
+}
+
+// getStatEntriesOnIndex retrieves stats on indexes, or all stats if indexes are not defined.
+func (sc *StatsClient) getStatEntriesOnIndex(vector dirVector, indexes ...uint32) (entries []adapter.StatEntry, err error) {
+ dirLen := *(*uint32)(vectorLen(vector))
+ for _, index := range indexes {
+ if index >= dirLen {
+ return nil, fmt.Errorf("stat entry index %d out of dir vector length (%d)", index, dirLen)
+ }
+ dirPtr, dirName, dirType := sc.GetStatDirOnIndex(vector, index)
+ if len(dirName) == 0 {
+ return
+ }
+ entries = append(entries, adapter.StatEntry{
+ StatIdentifier: adapter.StatIdentifier{
+ Index: index,
+ Name: dirName,
+ },
+ Type: adapter.StatType(dirType),
+ Data: sc.CopyEntryData(dirPtr),
+ })
+ }
+ return entries, nil
+}
+
+// getIdentifierEntriesOnIndex retrieves identifiers on indexes, or all identifiers if indexes are not defined.
+func (sc *StatsClient) getIdentifierEntriesOnIndex(vector dirVector, indexes ...uint32) (identifiers []adapter.StatIdentifier, err error) {
+ dirLen := *(*uint32)(vectorLen(vector))
+ for _, index := range indexes {
+ if index >= dirLen {
+ return nil, fmt.Errorf("stat entry index %d out of dir vector length (%d)", index, dirLen)
+ }
+ _, dirName, _ := sc.GetStatDirOnIndex(vector, index)
+ if len(dirName) == 0 {
+ return
+ }
+ identifiers = append(identifiers, adapter.StatIdentifier{
+ Index: index,
+ Name: dirName,
+ })
+ }
+ return identifiers, nil
+}
+
// listIndexes lists indexes for all stat entries that match any of the regex patterns.
-func (sc *StatsClient) listIndexes(patterns ...string) (indexes []uint32, err error) {
+func (sc *StatsClient) listIndexes(vector dirVector, patterns ...string) (indexes []uint32, err error) {
if len(patterns) == 0 {
- return sc.listIndexesFunc(nil)
+ return sc.listIndexesFunc(vector, nil)
}
var regexes = make([]*regexp.Regexp, len(patterns))
for i, pattern := range patterns {
@@ -531,7 +549,7 @@ func (sc *StatsClient) listIndexes(patterns ...string) (indexes []uint32, err er
}
regexes[i] = r
}
- nameMatches := func(name []byte) bool {
+ nameMatches := func(name dirName) bool {
for _, r := range regexes {
if r.Match(name) {
return true
@@ -539,26 +557,20 @@ func (sc *StatsClient) listIndexes(patterns ...string) (indexes []uint32, err er
}
return false
}
- return sc.listIndexesFunc(nameMatches)
+ return sc.listIndexesFunc(vector, nameMatches)
}
// listIndexesFunc lists stats indexes. The optional function
// argument filters returned values or returns all if empty
-func (sc *StatsClient) listIndexesFunc(f func(name []byte) bool) (indexes []uint32, err error) {
+func (sc *StatsClient) listIndexesFunc(vector dirVector, f func(name dirName) bool) (indexes []uint32, err error) {
if f == nil {
// there is around ~3157 stats, so to avoid too many allocations
// we set capacity to 3200 when listing all stats
indexes = make([]uint32, 0, 3200)
}
-
- dirVector := sc.GetDirectoryVector()
- if dirVector == nil {
- return nil, err
- }
- vecLen := *(*uint32)(vectorLen(dirVector))
-
+ vecLen := *(*uint32)(vectorLen(vector))
for i := uint32(0); i < vecLen; i++ {
- _, dirName, _ := sc.GetStatDirOnIndex(dirVector, i)
+ _, dirName, _ := sc.GetStatDirOnIndex(vector, i)
if f != nil {
if len(dirName) == 0 || !f(dirName) {
continue
@@ -573,3 +585,22 @@ func (sc *StatsClient) listIndexesFunc(f func(name []byte) bool) (indexes []uint
func (sc *StatsClient) isConnected() bool {
return atomic.LoadUint32(&sc.connected) == 1
}
+
+// updateStatOnIndex refreshes the entry data.
+func (sc *StatsClient) updateStatOnIndex(entry *adapter.StatEntry, vector dirVector) (err error) {
+ dirLen := *(*uint32)(vectorLen(vector))
+ if entry.Index >= dirLen {
+ return fmt.Errorf("stat entry index %d out of dir vector length (%d)", entry.Index, dirLen)
+ }
+ dirPtr, dirName, dirType := sc.GetStatDirOnIndex(vector, entry.Index)
+ if len(dirName) == 0 ||
+ !bytes.Equal(dirName, entry.Name) ||
+ adapter.StatType(dirType) != entry.Type ||
+ entry.Data == nil {
+ return nil
+ }
+ if err := sc.UpdateEntryData(dirPtr, &entry.Data); err != nil {
+ err = fmt.Errorf("updating stat data for entry %s failed: %v", dirName, err)
+ }
+ return
+}
diff --git a/adapter/statsclient/statseg_v1.go b/adapter/statsclient/statseg_v1.go
index 38f51bd..e9e8331 100644
--- a/adapter/statsclient/statseg_v1.go
+++ b/adapter/statsclient/statseg_v1.go
@@ -37,7 +37,7 @@ type sharedHeaderV1 struct {
}
type statSegDirectoryEntryV1 struct {
- directoryType statDirectoryType
+ directoryType dirType
// unionData can represent:
// - offset
// - index
@@ -66,17 +66,17 @@ func (ss *statSegmentV1) loadSharedHeader(b []byte) (header sharedHeaderV1) {
}
}
-func (ss *statSegmentV1) GetDirectoryVector() unsafe.Pointer {
+func (ss *statSegmentV1) GetDirectoryVector() dirVector {
dirOffset, _, _ := ss.getOffsets()
- return unsafe.Pointer(&ss.sharedHeader[dirOffset])
+ return dirVector(&ss.sharedHeader[dirOffset])
}
-func (ss *statSegmentV1) GetErrorVector() (unsafe.Pointer, error) {
+func (ss *statSegmentV1) getErrorVector() (unsafe.Pointer, error) {
return nil, fmt.Errorf("error vector is not defined for stats API v1")
}
-func (ss *statSegmentV1) GetStatDirOnIndex(p unsafe.Pointer, index uint32) (unsafe.Pointer, statDirectoryName, statDirectoryType) {
- statSegDir := unsafe.Pointer(uintptr(p) + uintptr(index)*unsafe.Sizeof(statSegDirectoryEntryV1{}))
+func (ss *statSegmentV1) GetStatDirOnIndex(v dirVector, index uint32) (dirSegment, dirName, dirType) {
+ statSegDir := dirSegment(uintptr(v) + uintptr(index)*unsafe.Sizeof(statSegDirectoryEntryV1{}))
dir := (*statSegDirectoryEntryV1)(statSegDir)
var name []byte
for n := 0; n < len(dir.name); n++ {
@@ -85,7 +85,7 @@ func (ss *statSegmentV1) GetStatDirOnIndex(p unsafe.Pointer, index uint32) (unsa
break
}
}
- return statSegDir, name, dir.directoryType
+ return statSegDir, dirName(name), dir.directoryType
}
func (ss *statSegmentV1) GetEpoch() (int64, bool) {
@@ -93,8 +93,8 @@ func (ss *statSegmentV1) GetEpoch() (int64, bool) {
return sh.epoch, sh.inProgress != 0
}
-func (ss *statSegmentV1) CopyEntryData(statSegDir unsafe.Pointer) adapter.Stat {
- dirEntry := (*statSegDirectoryEntryV1)(statSegDir)
+func (ss *statSegmentV1) CopyEntryData(segment dirSegment) adapter.Stat {
+ dirEntry := (*statSegDirectoryEntryV1)(segment)
dirType := adapter.StatType(dirEntry.directoryType)
switch dirType {
@@ -111,7 +111,7 @@ func (ss *statSegmentV1) CopyEntryData(statSegDir unsafe.Pointer) adapter.Stat {
}
_, errOffset, _ := ss.getOffsets()
- offsetVector := unsafe.Pointer(&ss.sharedHeader[errOffset])
+ offsetVector := dirVector(&ss.sharedHeader[errOffset])
var errData []adapter.Counter
@@ -120,7 +120,7 @@ func (ss *statSegmentV1) CopyEntryData(statSegDir unsafe.Pointer) adapter.Stat {
cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
offset := uintptr(cb) + uintptr(dirEntry.unionData)*unsafe.Sizeof(adapter.Counter(0))
debugf("error index, cb: %d, offset: %d", cb, offset)
- val := *(*adapter.Counter)(statSegPointer(unsafe.Pointer(&ss.sharedHeader[0]), offset))
+ val := *(*adapter.Counter)(statSegPointer(dirVector(&ss.sharedHeader[0]), offset))
errData = append(errData, val)
}
return adapter.ErrorStat(errData)
@@ -134,13 +134,13 @@ func (ss *statSegmentV1) CopyEntryData(statSegDir unsafe.Pointer) adapter.Stat {
break
}
- vecLen := *(*uint32)(vectorLen(unsafe.Pointer(&ss.sharedHeader[dirEntry.unionData])))
- offsetVector := statSegPointer(unsafe.Pointer(&ss.sharedHeader[0]), uintptr(dirEntry.offsetVector))
+ vecLen := *(*uint32)(vectorLen(dirVector(&ss.sharedHeader[dirEntry.unionData])))
+ offsetVector := statSegPointer(dirVector(&ss.sharedHeader[0]), uintptr(dirEntry.offsetVector))
data := make([][]adapter.Counter, vecLen)
for i := uint32(0); i < vecLen; i++ {
cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
- counterVec := unsafe.Pointer(&ss.sharedHeader[uintptr(cb)])
+ counterVec := dirVector(&ss.sharedHeader[uintptr(cb)])
vecLen2 := *(*uint32)(vectorLen(counterVec))
data[i] = make([]adapter.Counter, vecLen2)
for j := uint32(0); j < vecLen2; j++ {
@@ -160,13 +160,13 @@ func (ss *statSegmentV1) CopyEntryData(statSegDir unsafe.Pointer) adapter.Stat {
break
}
- vecLen := *(*uint32)(vectorLen(unsafe.Pointer(&ss.sharedHeader[dirEntry.unionData])))
- offsetVector := statSegPointer(unsafe.Pointer(&ss.sharedHeader[0]), uintptr(dirEntry.offsetVector))
+ vecLen := *(*uint32)(vectorLen(dirVector(&ss.sharedHeader[dirEntry.unionData])))
+ offsetVector := statSegPointer(dirVector(&ss.sharedHeader[0]), uintptr(dirEntry.offsetVector))
data := make([][]adapter.CombinedCounter, vecLen)
for i := uint32(0); i < vecLen; i++ {
cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
- counterVec := unsafe.Pointer(&ss.sharedHeader[uintptr(cb)])
+ counterVec := dirVector(&ss.sharedHeader[uintptr(cb)])
vecLen2 := *(*uint32)(vectorLen(counterVec))
data[i] = make([]adapter.CombinedCounter, vecLen2)
for j := uint32(0); j < vecLen2; j++ {
@@ -186,8 +186,8 @@ func (ss *statSegmentV1) CopyEntryData(statSegDir unsafe.Pointer) adapter.Stat {
break
}
- vecLen := *(*uint32)(vectorLen(unsafe.Pointer(&ss.sharedHeader[dirEntry.unionData])))
- offsetVector := statSegPointer(unsafe.Pointer(&ss.sharedHeader[0]), uintptr(dirEntry.offsetVector))
+ vecLen := *(*uint32)(vectorLen(dirVector(&ss.sharedHeader[dirEntry.unionData])))
+ offsetVector := statSegPointer(dirVector(&ss.sharedHeader[0]), uintptr(dirEntry.offsetVector))
data := make([]adapter.Name, vecLen)
for i := uint32(0); i < vecLen; i++ {
@@ -196,7 +196,7 @@ func (ss *statSegmentV1) CopyEntryData(statSegDir unsafe.Pointer) adapter.Stat {
debugf("name vector out of range for %s (%v)", dirEntry.name, i)
continue
}
- nameVec := unsafe.Pointer(&ss.sharedHeader[cb])
+ nameVec := dirVector(&ss.sharedHeader[cb])
vecLen2 := *(*uint32)(vectorLen(nameVec))
nameStr := make([]byte, 0, vecLen2)
@@ -221,8 +221,8 @@ func (ss *statSegmentV1) CopyEntryData(statSegDir unsafe.Pointer) adapter.Stat {
return nil
}
-func (ss *statSegmentV1) UpdateEntryData(statSegDir unsafe.Pointer, stat *adapter.Stat) error {
- dirEntry := (*statSegDirectoryEntryV1)(statSegDir)
+func (ss *statSegmentV1) UpdateEntryData(segment dirSegment, stat *adapter.Stat) error {
+ dirEntry := (*statSegDirectoryEntryV1)(segment)
switch (*stat).(type) {
case adapter.ScalarStat:
*stat = adapter.ScalarStat(dirEntry.unionData)
@@ -237,15 +237,15 @@ func (ss *statSegmentV1) UpdateEntryData(statSegDir unsafe.Pointer, stat *adapte
}
_, errOffset, _ := ss.getOffsets()
- offsetVector := unsafe.Pointer(&ss.sharedHeader[errOffset])
+ offsetVector := dirVector(&ss.sharedHeader[errOffset])
var errData []adapter.Counter
- vecLen := *(*uint32)(vectorLen(unsafe.Pointer(&ss.sharedHeader[errOffset])))
+ vecLen := *(*uint32)(vectorLen(dirVector(&ss.sharedHeader[errOffset])))
for i := uint32(0); i < vecLen; i++ {
cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
offset := uintptr(cb) + uintptr(dirEntry.unionData)*unsafe.Sizeof(adapter.Counter(0))
- val := *(*adapter.Counter)(statSegPointer(unsafe.Pointer(&ss.sharedHeader[0]), offset))
+ val := *(*adapter.Counter)(statSegPointer(dirVector(&ss.sharedHeader[0]), offset))
errData = append(errData, val)
}
*stat = adapter.ErrorStat(errData)
@@ -259,8 +259,8 @@ func (ss *statSegmentV1) UpdateEntryData(statSegDir unsafe.Pointer, stat *adapte
break
}
- vecLen := *(*uint32)(vectorLen(unsafe.Pointer(&ss.sharedHeader[dirEntry.unionData])))
- offsetVector := statSegPointer(unsafe.Pointer(&ss.sharedHeader[0]), uintptr(dirEntry.offsetVector))
+ vecLen := *(*uint32)(vectorLen(dirVector(&ss.sharedHeader[dirEntry.unionData])))
+ offsetVector := statSegPointer(dirVector(&ss.sharedHeader[0]), uintptr(dirEntry.offsetVector))
data := (*stat).(adapter.SimpleCounterStat)
if uint32(len(data)) != vecLen {
@@ -268,7 +268,7 @@ func (ss *statSegmentV1) UpdateEntryData(statSegDir unsafe.Pointer, stat *adapte
}
for i := uint32(0); i < vecLen; i++ {
cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
- counterVec := unsafe.Pointer(&ss.sharedHeader[uintptr(cb)])
+ counterVec := dirVector(&ss.sharedHeader[uintptr(cb)])
vecLen2 := *(*uint32)(vectorLen(counterVec))
simpleData := data[i]
if uint32(len(simpleData)) != vecLen2 {
@@ -290,8 +290,8 @@ func (ss *statSegmentV1) UpdateEntryData(statSegDir unsafe.Pointer, stat *adapte
break
}
- vecLen := *(*uint32)(vectorLen(unsafe.Pointer(&ss.sharedHeader[dirEntry.unionData])))
- offsetVector := statSegPointer(unsafe.Pointer(&ss.sharedHeader[0]), uintptr(dirEntry.offsetVector))
+ vecLen := *(*uint32)(vectorLen(dirVector(&ss.sharedHeader[dirEntry.unionData])))
+ offsetVector := statSegPointer(dirVector(&ss.sharedHeader[0]), uintptr(dirEntry.offsetVector))
data := (*stat).(adapter.CombinedCounterStat)
if uint32(len(data)) != vecLen {
@@ -299,7 +299,7 @@ func (ss *statSegmentV1) UpdateEntryData(statSegDir unsafe.Pointer, stat *adapte
}
for i := uint32(0); i < vecLen; i++ {
cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
- counterVec := unsafe.Pointer(&ss.sharedHeader[uintptr(cb)])
+ counterVec := dirVector(&ss.sharedHeader[uintptr(cb)])
vecLen2 := *(*uint32)(vectorLen(counterVec))
combData := data[i]
if uint32(len(combData)) != vecLen2 {
@@ -321,8 +321,8 @@ func (ss *statSegmentV1) UpdateEntryData(statSegDir unsafe.Pointer, stat *adapte
break
}
- vecLen := *(*uint32)(vectorLen(unsafe.Pointer(&ss.sharedHeader[dirEntry.unionData])))
- offsetVector := statSegPointer(unsafe.Pointer(&ss.sharedHeader[0]), uintptr(dirEntry.offsetVector))
+ vecLen := *(*uint32)(vectorLen(dirVector(&ss.sharedHeader[dirEntry.unionData])))
+ offsetVector := statSegPointer(dirVector(&ss.sharedHeader[0]), uintptr(dirEntry.offsetVector))
data := (*stat).(adapter.NameStat)
if uint32(len(data)) != vecLen {
@@ -333,7 +333,7 @@ func (ss *statSegmentV1) UpdateEntryData(statSegDir unsafe.Pointer, stat *adapte
if cb == 0 {
continue
}
- nameVec := unsafe.Pointer(&ss.sharedHeader[cb])
+ nameVec := dirVector(&ss.sharedHeader[cb])
vecLen2 := *(*uint32)(vectorLen(nameVec))
nameData := data[i]
diff --git a/adapter/statsclient/statseg_v2.go b/adapter/statsclient/statseg_v2.go
index 08467c1..10bc5f5 100644
--- a/adapter/statsclient/statseg_v2.go
+++ b/adapter/statsclient/statseg_v2.go
@@ -36,7 +36,7 @@ type sharedHeaderV2 struct {
}
type statSegDirectoryEntryV2 struct {
- directoryType statDirectoryType
+ directoryType dirType
// unionData can represent:
// - index
// - value
@@ -64,13 +64,13 @@ func (ss *statSegmentV2) loadSharedHeader(b []byte) (header sharedHeaderV2) {
}
}
-func (ss *statSegmentV2) GetDirectoryVector() unsafe.Pointer {
+func (ss *statSegmentV2) GetDirectoryVector() dirVector {
header := ss.loadSharedHeader(ss.sharedHeader)
- return ss.adjust(unsafe.Pointer(&header.dirVector))
+ return ss.adjust(dirVector(&header.dirVector))
}
-func (ss *statSegmentV2) GetStatDirOnIndex(p unsafe.Pointer, index uint32) (unsafe.Pointer, statDirectoryName, statDirectoryType) {
- statSegDir := unsafe.Pointer(uintptr(p) + uintptr(index)*unsafe.Sizeof(statSegDirectoryEntryV2{}))
+func (ss *statSegmentV2) GetStatDirOnIndex(v dirVector, index uint32) (dirSegment, dirName, dirType) {
+ statSegDir := dirSegment(uintptr(v) + uintptr(index)*unsafe.Sizeof(statSegDirectoryEntryV2{}))
dir := (*statSegDirectoryEntryV2)(statSegDir)
var name []byte
for n := 0; n < len(dir.name); n++ {
@@ -87,8 +87,8 @@ func (ss *statSegmentV2) GetEpoch() (int64, bool) {
return sh.epoch, sh.inProgress != 0
}
-func (ss *statSegmentV2) CopyEntryData(statSegDir unsafe.Pointer) adapter.Stat {
- dirEntry := (*statSegDirectoryEntryV2)(statSegDir)
+func (ss *statSegmentV2) CopyEntryData(segment dirSegment) adapter.Stat {
+ dirEntry := (*statSegDirectoryEntryV2)(segment)
if dirEntry.unionData == 0 {
debugf("data value or pointer not defined for %s", dirEntry.name)
return nil
@@ -120,7 +120,7 @@ func (ss *statSegmentV2) CopyEntryData(statSegDir unsafe.Pointer) adapter.Stat {
return adapter.ErrorStat(errData)
case statDirCounterVectorSimple:
- dirVector := ss.adjust(unsafe.Pointer(&dirEntry.unionData))
+ dirVector := ss.adjust(dirVector(&dirEntry.unionData))
if dirVector == nil {
debugf("data vector pointer is out of range for %s", dirEntry.name)
return nil
@@ -145,7 +145,7 @@ func (ss *statSegmentV2) CopyEntryData(statSegDir unsafe.Pointer) adapter.Stat {
return adapter.SimpleCounterStat(data)
case statDirCounterVectorCombined:
- dirVector := ss.adjust(unsafe.Pointer(&dirEntry.unionData))
+ dirVector := ss.adjust(dirVector(&dirEntry.unionData))
if dirVector == nil {
debugf("data vector pointer is out of range for %s", dirEntry.name)
return nil
@@ -170,7 +170,7 @@ func (ss *statSegmentV2) CopyEntryData(statSegDir unsafe.Pointer) adapter.Stat {
return adapter.CombinedCounterStat(data)
case statDirNameVector:
- dirVector := ss.adjust(unsafe.Pointer(&dirEntry.unionData))
+ dirVector := ss.adjust(dirVector(&dirEntry.unionData))
if dirVector == nil {
debugf("data vector pointer is out of range for %s", dirEntry.name)
return nil
@@ -212,8 +212,8 @@ func (ss *statSegmentV2) CopyEntryData(statSegDir unsafe.Pointer) adapter.Stat {
return nil
}
-func (ss *statSegmentV2) UpdateEntryData(statSegDir unsafe.Pointer, stat *adapter.Stat) error {
- dirEntry := (*statSegDirectoryEntryV2)(statSegDir)
+func (ss *statSegmentV2) UpdateEntryData(segment dirSegment, stat *adapter.Stat) error {
+ dirEntry := (*statSegDirectoryEntryV2)(segment)
switch (*stat).(type) {
case adapter.ScalarStat:
*stat = adapter.ScalarStat(dirEntry.unionData)
@@ -240,7 +240,7 @@ func (ss *statSegmentV2) UpdateEntryData(statSegDir unsafe.Pointer, stat *adapte
*stat = adapter.ErrorStat(errData)
case adapter.SimpleCounterStat:
- dirVector := ss.adjust(unsafe.Pointer(&dirEntry.unionData))
+ dirVector := ss.adjust(dirVector(&dirEntry.unionData))
if dirVector == nil {
debugf("data vector pointer is out of range for %s", dirEntry.name)
return nil
@@ -267,7 +267,7 @@ func (ss *statSegmentV2) UpdateEntryData(statSegDir unsafe.Pointer, stat *adapte
}
case adapter.CombinedCounterStat:
- dirVector := ss.adjust(unsafe.Pointer(&dirEntry.unionData))
+ dirVector := ss.adjust(dirVector(&dirEntry.unionData))
if dirVector == nil {
debugf("data vector pointer is out of range for %s", dirEntry.name)
return nil
@@ -291,7 +291,7 @@ func (ss *statSegmentV2) UpdateEntryData(statSegDir unsafe.Pointer, stat *adapte
}
case adapter.NameStat:
- dirVector := ss.adjust(unsafe.Pointer(&dirEntry.unionData))
+ dirVector := ss.adjust(dirVector(&dirEntry.unionData))
if dirVector == nil {
debugf("data vector pointer is out of range for %s", dirEntry.name)
return nil
@@ -334,9 +334,9 @@ func (ss *statSegmentV2) UpdateEntryData(statSegDir unsafe.Pointer, stat *adapte
// Adjust data pointer using shared header and base and return
// the pointer to a data segment
-func (ss *statSegmentV2) adjust(data unsafe.Pointer) unsafe.Pointer {
+func (ss *statSegmentV2) adjust(data dirVector) dirVector {
header := ss.loadSharedHeader(ss.sharedHeader)
- adjusted := unsafe.Pointer(uintptr(unsafe.Pointer(&ss.sharedHeader[0])) +
+ adjusted := dirVector(uintptr(unsafe.Pointer(&ss.sharedHeader[0])) +
uintptr(*(*uint64)(data)) - uintptr(*(*uint64)(unsafe.Pointer(&header.base))))
if uintptr(unsafe.Pointer(&ss.sharedHeader[len(ss.sharedHeader)-1])) <= uintptr(adjusted) ||
uintptr(unsafe.Pointer(&ss.sharedHeader[0])) >= uintptr(adjusted) {
@@ -345,7 +345,7 @@ func (ss *statSegmentV2) adjust(data unsafe.Pointer) unsafe.Pointer {
return adjusted
}
-func (ss *statSegmentV2) getErrorVector() unsafe.Pointer {
+func (ss *statSegmentV2) getErrorVector() dirVector {
header := ss.loadSharedHeader(ss.sharedHeader)
- return ss.adjust(unsafe.Pointer(&header.errorVector))
+ return ss.adjust(dirVector(&header.errorVector))
}
diff --git a/adapter/vppapiclient/stat_client.go b/adapter/vppapiclient/stat_client.go
index a124f59..734c158 100644
--- a/adapter/vppapiclient/stat_client.go
+++ b/adapter/vppapiclient/stat_client.go
@@ -83,7 +83,7 @@ func (c *statClient) Disconnect() error {
return nil
}
-func (c *statClient) ListStats(patterns ...string) (stats []string, err error) {
+func (c *statClient) ListStats(patterns ...string) (indexes []adapter.StatIdentifier, err error) {
dir := C.govpp_stat_segment_ls(convertStringSlice(patterns))
if dir == nil {
return nil, adapter.ErrStatsDataBusy
@@ -93,11 +93,14 @@ func (c *statClient) ListStats(patterns ...string) (stats []string, err error) {
l := C.govpp_stat_segment_vec_len(unsafe.Pointer(dir))
for i := 0; i < int(l); i++ {
nameChar := C.govpp_stat_segment_dir_index_to_name(dir, C.uint32_t(i))
- stats = append(stats, C.GoString(nameChar))
+ indexes = append(indexes, adapter.StatIdentifier{
+ Name: []byte(C.GoString(nameChar)),
+ Index: uint32(i),
+ })
C.free(unsafe.Pointer(nameChar))
}
- return stats, nil
+ return indexes, nil
}
func (c *statClient) DumpStats(patterns ...string) (stats []adapter.StatEntry, err error) {
@@ -121,7 +124,10 @@ func (c *statClient) DumpStats(patterns ...string) (stats []adapter.StatEntry, e
typ := adapter.StatType(C.govpp_stat_segment_data_type(&v))
stat := adapter.StatEntry{
- Name: []byte(name),
+ StatIdentifier: adapter.StatIdentifier{
+ Name: []byte(name),
+ Index: uint32(i),
+ },
Type: typ,
}
@@ -184,6 +190,10 @@ func (c *statClient) PrepareDir(prefixes ...string) (*adapter.StatDir, error) {
return nil, adapter.ErrNotImplemented
}
+func (c *statClient) PrepareDirOnIndex(indexes ...uint32) (*adapter.StatDir, error) {
+ return nil, adapter.ErrNotImplemented
+}
+
func (c *statClient) UpdateDir(dir *adapter.StatDir) error {
return adapter.ErrNotImplemented
}
diff --git a/adapter/vppapiclient/stat_client_stub.go b/adapter/vppapiclient/stat_client_stub.go
index c764391..856a1e3 100644
--- a/adapter/vppapiclient/stat_client_stub.go
+++ b/adapter/vppapiclient/stat_client_stub.go
@@ -36,7 +36,7 @@ func (*stubStatClient) Disconnect() error {
return nil
}
-func (*stubStatClient) ListStats(patterns ...string) (statNames []string, err error) {
+func (*stubStatClient) ListStats(patterns ...string) (indexes []adapter.StatIdentifier, err error) {
return nil, adapter.ErrNotImplemented
}
@@ -48,6 +48,10 @@ func (*stubStatClient) PrepareDir(prefixes ...string) (*adapter.StatDir, error)
return nil, adapter.ErrNotImplemented
}
+func (*stubStatClient) PrepareDirOnIndex(indexes ...uint32) (*adapter.StatDir, error) {
+ return nil, adapter.ErrNotImplemented
+}
+
func (*stubStatClient) UpdateDir(dir *adapter.StatDir) error {
return adapter.ErrNotImplemented
}
diff --git a/examples/stats-client/stats_api.go b/examples/stats-client/stats_api.go
index fa39b54..63ee55d 100644
--- a/examples/stats-client/stats_api.go
+++ b/examples/stats-client/stats_api.go
@@ -19,6 +19,7 @@ import (
"fmt"
"log"
"os"
+ "strconv"
"strings"
"time"
@@ -44,7 +45,7 @@ var (
func init() {
flag.Usage = func() {
- fmt.Fprintf(os.Stderr, "%s: usage [ls|dump|poll|errors|interfaces|nodes|system|buffers|memory] <patterns>...\n", os.Args[0])
+ fmt.Fprintf(os.Stderr, "%s: usage [ls|dump|poll|errors|interfaces|nodes|system|buffers|memory|epoch] <patterns/index>...\n", os.Args[0])
flag.PrintDefaults()
os.Exit(1)
}
@@ -54,9 +55,16 @@ func main() {
flag.Parse()
skipZeros := !*dumpAll
- var patterns []string
+ patterns := make([]string, 0)
+ indexes := make([]uint32, 0)
if flag.NArg() > 0 {
- patterns = flag.Args()[1:]
+ for _, arg := range flag.Args()[1:] {
+ if index, err := strconv.Atoi(arg); err == nil {
+ indexes = append(indexes, uint32(index))
+ continue
+ }
+ patterns = append(patterns, arg)
+ }
}
var (
@@ -168,7 +176,7 @@ func main() {
case "dump":
fmt.Printf("Dumping stats.. %s\n", strings.Join(patterns, " "))
- dumpStats(client, patterns, skipZeros)
+ dumpStats(client, patterns, indexes, skipZeros)
case "poll":
fmt.Printf("Polling stats.. %s\n", strings.Join(patterns, " "))
@@ -178,30 +186,69 @@ func main() {
case "list", "ls", "":
fmt.Printf("Listing stats.. %s\n", strings.Join(patterns, " "))
- listStats(client, patterns)
+ listStats(client, patterns, indexes)
+
+ case "epoch", "e":
+ fmt.Printf("Getting epoch..\n")
+
+ getEpoch(client)
default:
fmt.Printf("invalid command: %q\n", cmd)
}
}
-func listStats(client adapter.StatsAPI, patterns []string) {
- list, err := client.ListStats(patterns...)
- if err != nil {
- log.Fatalln("listing stats failed:", err)
+func listStats(client adapter.StatsAPI, patterns []string, indexes []uint32) {
+ var err error
+ list := make([]adapter.StatIdentifier, 0)
+ if (len(patterns) == 0 && len(indexes) == 0) || len(patterns) != 0 {
+ list, err = client.ListStats(patterns...)
+ if err != nil {
+ log.Fatalln("listing stats failed:", err)
+ }
+ }
+ if len(indexes) != 0 {
+ dir, err := client.PrepareDirOnIndex(indexes...)
+ if err != nil {
+ log.Fatalln("listing stats failed:", err)
+ }
+ for _, onIndexSi := range dir.Entries {
+ list = append(list, onIndexSi.StatIdentifier)
+ }
}
-
for _, stat := range list {
- fmt.Printf(" - %v\n", stat)
+ fmt.Printf(" - %d\t %v\n", stat.Index, string(stat.Name))
}
fmt.Printf("Listed %d stats\n", len(list))
}
-func dumpStats(client adapter.StatsAPI, patterns []string, skipZeros bool) {
- stats, err := client.DumpStats(patterns...)
+func getEpoch(client adapter.StatsAPI) {
+ dir, err := client.PrepareDir()
if err != nil {
- log.Fatalln("dumping stats failed:", err)
+ log.Fatalln("failed to prepare dir in order to read epoch:", err)
+ }
+ d := *dir
+ fmt.Printf("Epoch %d\n", d.Epoch)
+}
+
+func dumpStats(client adapter.StatsAPI, patterns []string, indexes []uint32, skipZeros bool) {
+ var err error
+ stats := make([]adapter.StatEntry, 0)
+ if (len(patterns) == 0 && len(indexes) == 0) || len(patterns) != 0 {
+ stats, err = client.DumpStats(patterns...)
+ if err != nil {
+ log.Fatalln("dumping stats failed:", err)
+ }
+ }
+ if len(indexes) != 0 {
+ dir, err := client.PrepareDirOnIndex(indexes...)
+ if err != nil {
+ log.Fatalln("dumping stats failed:", err)
+ }
+ for _, onIndexSi := range dir.Entries {
+ stats = append(stats, onIndexSi)
+ }
}
n := 0