statsclient: allow index as pattern 70/31670/6
authorVladimir Lavor <vlavor@cisco.com>
Wed, 17 Mar 2021 11:27:10 +0000 (12:27 +0100)
committerVladimir Lavor <vlavor@cisco.com>
Wed, 5 May 2021 11:26:19 +0000 (13:26 +0200)
* 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>
adapter/mock/mock_stats_adapter.go
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
adapter/vppapiclient/stat_client_stub.go
examples/stats-client/stats_api.go

index 55b1831..08d18d4 100644 (file)
@@ -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
index d15dee8..5b19173 100644 (file)
@@ -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
 }
index 0ca0c8b..23755a5 100644 (file)
@@ -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)
 }
index b2d91db..16af16a 100644 (file)
@@ -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
+}
index 38f51bd..e9e8331 100644 (file)
@@ -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]
index 08467c1..10bc5f5 100644 (file)
@@ -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))
 }
index a124f59..734c158 100644 (file)
@@ -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
 }
index c764391..856a1e3 100644 (file)
@@ -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
 }
index fa39b54..63ee55d 100644 (file)
@@ -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