aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVladimir Lavor <vlavor@cisco.com>2020-09-02 15:08:22 +0200
committerOndrej Fabry <ofabry@cisco.com>2020-09-04 10:38:25 +0000
commit1a1f4043dfae2e77de70d6adfbb8d84cdeed658d (patch)
tree611bd02e7fd6761f17ea046d7b6444de53fa7f0f
parentc94a962279858fb13eaacc689f47aed358373e44 (diff)
Stats APIv2
* Compatible with stats v2 API * Compatibility with stats v1 was persisted * 19.04 (legacy) dropped Change-Id: I91a3ab0c007fed6d972eee01d7caf69af29305d1 Signed-off-by: Vladimir Lavor <vlavor@cisco.com>
-rw-r--r--adapter/statsclient/stat_segment.go470
-rw-r--r--adapter/statsclient/stat_segment_api.go113
-rw-r--r--adapter/statsclient/statsclient.go339
-rw-r--r--adapter/statsclient/statseg.go104
-rw-r--r--adapter/statsclient/statseg_v1.go365
-rw-r--r--adapter/statsclient/statseg_v2.go351
-rw-r--r--core/stats.go3
7 files changed, 1045 insertions, 700 deletions
diff --git a/adapter/statsclient/stat_segment.go b/adapter/statsclient/stat_segment.go
deleted file mode 100644
index 2a97fd4..0000000
--- a/adapter/statsclient/stat_segment.go
+++ /dev/null
@@ -1,470 +0,0 @@
-// Copyright (c) 2019 Cisco and/or its affiliates.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at:
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package statsclient
-
-import (
- "fmt"
- "net"
- "syscall"
- "unsafe"
-
- "github.com/ftrvxmtrx/fd"
-
- "git.fd.io/govpp.git/adapter"
-)
-
-var (
- ErrStatDataLenIncorrect = fmt.Errorf("stat data length incorrect")
-)
-
-const (
- minVersion = 0
- maxVersion = 1
-)
-
-func checkVersion(ver uint64) error {
- if ver < minVersion {
- return fmt.Errorf("stat segment version is too old: %v (minimal version: %v)", ver, minVersion)
- } else if ver > maxVersion {
- return fmt.Errorf("stat segment version is not supported: %v (minimal version: %v)", ver, maxVersion)
- }
- return nil
-}
-
-type statSegment struct {
- sharedHeader []byte
- memorySize int64
-
- // legacyVersion represents stat segment version 0
- // and is used as fallback for VPP 19.04
- legacyVersion bool
-}
-
-func (c *statSegment) getHeader() (header sharedHeader) {
- if c.legacyVersion {
- return loadSharedHeaderLegacy(c.sharedHeader)
- }
- return loadSharedHeader(c.sharedHeader)
-}
-
-func (c *statSegment) getEpoch() (int64, bool) {
- h := c.getHeader()
- return h.epoch, h.inProgress != 0
-}
-
-func (c *statSegment) getOffsets() (dir, err, stat int64) {
- h := c.getHeader()
- return h.directoryOffset, h.errorOffset, h.statsOffset
-}
-
-func (c *statSegment) connect(sockName string) error {
- if c.sharedHeader != nil {
- return fmt.Errorf("already connected")
- }
-
- addr := net.UnixAddr{
- Net: "unixpacket",
- Name: sockName,
- }
- Log.Debugf("connecting to: %v", addr)
-
- conn, err := net.DialUnix(addr.Net, nil, &addr)
- if err != nil {
- Log.Warnf("connecting to socket %s failed: %s", addr, err)
- return err
- }
- defer func() {
- if err := conn.Close(); err != nil {
- Log.Warnf("closing socket failed: %v", err)
- }
- }()
-
- Log.Debugf("connected to socket")
-
- files, err := fd.Get(conn, 1, nil)
- if err != nil {
- return fmt.Errorf("getting file descriptor over socket failed: %v", err)
- }
- if len(files) == 0 {
- return fmt.Errorf("no files received over socket")
- }
-
- file := files[0]
- defer func() {
- if err := file.Close(); err != nil {
- Log.Warnf("closing file failed: %v", err)
- }
- }()
-
- info, err := file.Stat()
- if err != nil {
- return err
- }
- size := info.Size()
-
- data, err := syscall.Mmap(int(file.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
- if err != nil {
- Log.Debugf("mapping shared memory failed: %v", err)
- return fmt.Errorf("mapping shared memory failed: %v", err)
- }
-
- Log.Debugf("successfuly mmapped shared memory segment (size: %v) %v", size, len(data))
-
- c.sharedHeader = data
- c.memorySize = size
-
- hdr := loadSharedHeader(c.sharedHeader)
- Log.Debugf("stat segment header: %+v", hdr)
-
- if hdr.legacyVersion() {
- c.legacyVersion = true
- hdr = loadSharedHeaderLegacy(c.sharedHeader)
- Log.Debugf("falling back to legacy version (VPP <=19.04) of stat segment (header: %+v)", hdr)
- }
-
- if err := checkVersion(hdr.version); err != nil {
- return err
- }
-
- return nil
-}
-
-func (c *statSegment) disconnect() error {
- if c.sharedHeader == nil {
- return nil
- }
-
- if err := syscall.Munmap(c.sharedHeader); err != nil {
- Log.Debugf("unmapping shared memory failed: %v", err)
- return fmt.Errorf("unmapping shared memory failed: %v", err)
- }
- c.sharedHeader = nil
-
- Log.Debugf("successfuly unmapped shared memory")
- return nil
-}
-
-type statDirectoryType int32
-
-const (
- statDirIllegal = 0
- statDirScalarIndex = 1
- statDirCounterVectorSimple = 2
- statDirCounterVectorCombined = 3
- statDirErrorIndex = 4
- statDirNameVector = 5
- statDirEmpty = 6
-)
-
-func (t statDirectoryType) String() string {
- return adapter.StatType(t).String()
-}
-
-type statSegDirectoryEntry struct {
- directoryType statDirectoryType
- // unionData can represent:
- // - offset
- // - index
- // - value
- unionData uint64
- offsetVector uint64
- name [128]byte
-}
-
-func (c *statSegment) getStatDirVector() unsafe.Pointer {
- dirOffset, _, _ := c.getOffsets()
- return unsafe.Pointer(&c.sharedHeader[dirOffset])
-}
-
-func (c *statSegment) getStatDirIndex(p unsafe.Pointer, index uint32) *statSegDirectoryEntry {
- return (*statSegDirectoryEntry)(unsafe.Pointer(uintptr(p) + uintptr(index)*unsafe.Sizeof(statSegDirectoryEntry{})))
-}
-
-func (c *statSegment) copyEntryData(dirEntry *statSegDirectoryEntry) adapter.Stat {
- dirType := adapter.StatType(dirEntry.directoryType)
-
- switch dirType {
- case statDirScalarIndex:
- return adapter.ScalarStat(dirEntry.unionData)
-
- case statDirErrorIndex:
- if dirEntry.unionData == 0 {
- debugf("offset invalid for %s", dirEntry.name)
- break
- } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
- debugf("offset out of range for %s", dirEntry.name)
- break
- }
-
- _, errOffset, _ := c.getOffsets()
- offsetVector := unsafe.Pointer(&c.sharedHeader[errOffset])
-
- var errData adapter.Counter
- if c.legacyVersion {
- // error were not vector (per-worker) in VPP 19.04
- offset := uintptr(dirEntry.unionData) * unsafe.Sizeof(uint64(0))
- val := *(*adapter.Counter)(statSegPointer(offsetVector, offset))
- errData = val
- } else {
- vecLen := uint32(vectorLen(offsetVector))
-
- 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))
- debugf("error index, cb: %d, offset: %d", cb, offset)
- val := *(*adapter.Counter)(statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), offset))
- errData += val
- }
- }
- return adapter.ErrorStat(errData)
-
- case statDirCounterVectorSimple:
- if dirEntry.unionData == 0 {
- debugf("offset invalid for %s", dirEntry.name)
- break
- } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
- debugf("offset out of range for %s", dirEntry.name)
- break
- }
-
- vecLen := uint32(vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData])))
- offsetVector := statSegPointer(unsafe.Pointer(&c.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(&c.sharedHeader[uintptr(cb)])
- vecLen2 := uint32(vectorLen(counterVec))
- data[i] = make([]adapter.Counter, vecLen2)
- for j := uint32(0); j < vecLen2; j++ {
- offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
- val := *(*adapter.Counter)(statSegPointer(counterVec, offset))
- data[i][j] = val
- }
- }
- return adapter.SimpleCounterStat(data)
-
- case statDirCounterVectorCombined:
- if dirEntry.unionData == 0 {
- debugf("offset invalid for %s", dirEntry.name)
- break
- } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
- debugf("offset out of range for %s", dirEntry.name)
- break
- }
-
- vecLen := uint32(vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData])))
- offsetVector := statSegPointer(unsafe.Pointer(&c.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(&c.sharedHeader[uintptr(cb)])
- vecLen2 := uint32(vectorLen(counterVec))
- data[i] = make([]adapter.CombinedCounter, vecLen2)
- for j := uint32(0); j < vecLen2; j++ {
- offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
- val := *(*adapter.CombinedCounter)(statSegPointer(counterVec, offset))
- data[i][j] = val
- }
- }
- return adapter.CombinedCounterStat(data)
-
- case statDirNameVector:
- if dirEntry.unionData == 0 {
- debugf("offset invalid for %s", dirEntry.name)
- break
- } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
- debugf("offset out of range for %s", dirEntry.name)
- break
- }
-
- vecLen := uint32(vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData])))
- offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
-
- data := make([]adapter.Name, vecLen)
- for i := uint32(0); i < vecLen; i++ {
- cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
- if cb == 0 {
- debugf("name vector out of range for %s (%v)", dirEntry.name, i)
- continue
- }
- nameVec := unsafe.Pointer(&c.sharedHeader[cb])
- vecLen2 := uint32(vectorLen(nameVec))
-
- nameStr := make([]byte, 0, vecLen2)
- for j := uint32(0); j < vecLen2; j++ {
- offset := uintptr(j) * unsafe.Sizeof(byte(0))
- val := *(*byte)(statSegPointer(nameVec, offset))
- if val > 0 {
- nameStr = append(nameStr, val)
- }
- }
- data[i] = adapter.Name(nameStr)
- }
- return adapter.NameStat(data)
-
- case statDirEmpty:
- // no-op
-
- default:
- // TODO: monitor occurrences with metrics
- debugf("Unknown type %d for stat entry: %q", dirEntry.directoryType, dirEntry.name)
- }
- return nil
-}
-
-func (c *statSegment) updateEntryData(dirEntry *statSegDirectoryEntry, stat *adapter.Stat) error {
- switch (*stat).(type) {
- case adapter.ScalarStat:
- *stat = adapter.ScalarStat(dirEntry.unionData)
-
- case adapter.ErrorStat:
- if dirEntry.unionData == 0 {
- debugf("offset invalid for %s", dirEntry.name)
- break
- } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
- debugf("offset out of range for %s", dirEntry.name)
- break
- }
-
- _, errOffset, _ := c.getOffsets()
- offsetVector := unsafe.Pointer(&c.sharedHeader[errOffset])
-
- var errData adapter.Counter
- if c.legacyVersion {
- // error were not vector (per-worker) in VPP 19.04
- offset := uintptr(dirEntry.unionData) * unsafe.Sizeof(uint64(0))
- val := *(*adapter.Counter)(statSegPointer(offsetVector, offset))
- errData = val
- } else {
- vecLen := uint32(vectorLen(unsafe.Pointer(&c.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(&c.sharedHeader[0]), offset))
- errData += val
- }
- }
- *stat = adapter.ErrorStat(errData)
-
- case adapter.SimpleCounterStat:
- if dirEntry.unionData == 0 {
- debugf("offset invalid for %s", dirEntry.name)
- break
- } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
- debugf("offset out of range for %s", dirEntry.name)
- break
- }
-
- vecLen := uint32(vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData])))
- offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
-
- data := (*stat).(adapter.SimpleCounterStat)
- if uint32(len(data)) != vecLen {
- return ErrStatDataLenIncorrect
- }
- for i := uint32(0); i < vecLen; i++ {
- cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
- counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
- vecLen2 := uint32(vectorLen(counterVec))
- simpData := data[i]
- if uint32(len(simpData)) != vecLen2 {
- return ErrStatDataLenIncorrect
- }
- for j := uint32(0); j < vecLen2; j++ {
- offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
- val := *(*adapter.Counter)(statSegPointer(counterVec, offset))
- simpData[j] = val
- }
- }
-
- case adapter.CombinedCounterStat:
- if dirEntry.unionData == 0 {
- debugf("offset invalid for %s", dirEntry.name)
- break
- } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
- debugf("offset out of range for %s", dirEntry.name)
- break
- }
-
- vecLen := uint32(vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData])))
- offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
-
- data := (*stat).(adapter.CombinedCounterStat)
- if uint32(len(data)) != vecLen {
- return ErrStatDataLenIncorrect
- }
- for i := uint32(0); i < vecLen; i++ {
- cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
- counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)])
- vecLen2 := uint32(vectorLen(counterVec))
- combData := data[i]
- if uint32(len(combData)) != vecLen2 {
- return ErrStatDataLenIncorrect
- }
- for j := uint32(0); j < vecLen2; j++ {
- offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
- val := *(*adapter.CombinedCounter)(statSegPointer(counterVec, offset))
- combData[j] = val
- }
- }
-
- case adapter.NameStat:
- if dirEntry.unionData == 0 {
- debugf("offset invalid for %s", dirEntry.name)
- break
- } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) {
- debugf("offset out of range for %s", dirEntry.name)
- break
- }
-
- vecLen := uint32(vectorLen(unsafe.Pointer(&c.sharedHeader[dirEntry.unionData])))
- offsetVector := statSegPointer(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector))
-
- data := (*stat).(adapter.NameStat)
- if uint32(len(data)) != vecLen {
- return ErrStatDataLenIncorrect
- }
- for i := uint32(0); i < vecLen; i++ {
- cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
- if cb == 0 {
- continue
- }
- nameVec := unsafe.Pointer(&c.sharedHeader[cb])
- vecLen2 := uint32(vectorLen(nameVec))
-
- nameData := data[i]
- if uint32(len(nameData))+1 != vecLen2 {
- return ErrStatDataLenIncorrect
- }
- for j := uint32(0); j < vecLen2; j++ {
- offset := uintptr(j) * unsafe.Sizeof(byte(0))
- val := *(*byte)(statSegPointer(nameVec, offset))
- if val == 0 {
- break
- }
- nameData[j] = val
- }
- }
-
- default:
- if Debug {
- Log.Debugf("Unrecognized stat type %T for stat entry: %v", stat, dirEntry.name)
- }
- }
- return nil
-}
diff --git a/adapter/statsclient/stat_segment_api.go b/adapter/statsclient/stat_segment_api.go
new file mode 100644
index 0000000..f2e4219
--- /dev/null
+++ b/adapter/statsclient/stat_segment_api.go
@@ -0,0 +1,113 @@
+// Copyright (c) 2020 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package statsclient
+
+import (
+ "fmt"
+ "git.fd.io/govpp.git/adapter"
+ "sync/atomic"
+ "time"
+ "unsafe"
+)
+
+var (
+ // ErrStatDataLenIncorrect is returned when stat data does not match vector
+ // length of a respective data directory
+ ErrStatDataLenIncorrect = fmt.Errorf("stat data length incorrect")
+)
+
+var (
+ MaxWaitInProgress = time.Millisecond * 100
+ CheckDelayInProgress = time.Microsecond * 10
+)
+
+const (
+ minVersion = 1
+ maxVersion = 2
+)
+
+const (
+ statDirIllegal = 0
+ statDirScalarIndex = 1
+ statDirCounterVectorSimple = 2
+ statDirCounterVectorCombined = 3
+ statDirErrorIndex = 4
+ statDirNameVector = 5
+ statDirEmpty = 6
+)
+
+type statDirectoryType int32
+
+type statDirectoryName []byte
+
+// 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, error)
+
+ // GetStatDirOnIndex accepts directory vector and particular index.
+ // Returns pointer to the beginning of the segment. Also the directory
+ // name as [128]byte and the directory type is returned for easy use
+ // without needing to know the exact segment version.
+ //
+ // 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)
+
+ // GetEpoch re-loads stats header and returns current epoch
+ //and 'inProgress' value
+ GetEpoch() (int64, bool)
+
+ // CopyEntryData accepts pointer to a directory segment and returns adapter.Stat
+ // based on directory type populated with data
+ CopyEntryData(segment unsafe.Pointer) adapter.Stat
+
+ // UpdateEntryData accepts pointer to a directory segment with data, and stat
+ // segment to update
+ UpdateEntryData(segment unsafe.Pointer, s *adapter.Stat) error
+}
+
+// vecHeader represents a vector header
+type vecHeader struct {
+ length uint64
+ vectorData [0]uint8
+}
+
+func (t statDirectoryType) String() string {
+ return adapter.StatType(t).String()
+}
+
+func getVersion(data []byte) uint64 {
+ type apiVersion struct {
+ value uint64
+ }
+ header := (*apiVersion)(unsafe.Pointer(&data[0]))
+ version := &apiVersion{
+ value: atomic.LoadUint64(&header.value),
+ }
+ debugf("stats API version loaded: %d", version.value)
+ return version.value
+}
+
+func vectorLen(v unsafe.Pointer) unsafe.Pointer {
+ vec := *(*vecHeader)(unsafe.Pointer(uintptr(v) - unsafe.Sizeof(uint64(0))))
+ return unsafe.Pointer(&vec.length)
+}
+
+//go:nosplit
+func statSegPointer(p unsafe.Pointer, offset uintptr) unsafe.Pointer {
+ return unsafe.Pointer(uintptr(p) + offset)
+}
diff --git a/adapter/statsclient/statsclient.go b/adapter/statsclient/statsclient.go
index 9110275..f3be4e0 100644
--- a/adapter/statsclient/statsclient.go
+++ b/adapter/statsclient/statsclient.go
@@ -18,12 +18,15 @@ package statsclient
import (
"bytes"
"fmt"
+ "net"
"os"
"regexp"
-
- logger "github.com/sirupsen/logrus"
+ "syscall"
+ "time"
"git.fd.io/govpp.git/adapter"
+ "github.com/ftrvxmtrx/fd"
+ logger "github.com/sirupsen/logrus"
)
const (
@@ -73,7 +76,9 @@ var _ adapter.StatsAPI = (*StatsClient)(nil)
// StatsClient is the pure Go implementation for VPP stats API.
type StatsClient struct {
- sockAddr string
+ sockAddr string
+ headerData []byte
+ isConnected bool
statSegment
}
@@ -87,170 +92,297 @@ func NewStatsClient(sockAddr string) *StatsClient {
sockAddr: sockAddr,
}
}
-
-func (c *StatsClient) Connect() error {
+// Connect to the VPP stats socket
+func (sc *StatsClient) Connect() (err error) {
// check if socket exists
- if _, err := os.Stat(c.sockAddr); os.IsNotExist(err) {
- fmt.Fprintf(os.Stderr, socketMissing, c.sockAddr)
- return fmt.Errorf("stats socket file %s does not exist", c.sockAddr)
+ if _, err := os.Stat(sc.sockAddr); os.IsNotExist(err) {
+ fmt.Fprintf(os.Stderr, socketMissing, sc.sockAddr)
+ return fmt.Errorf("stats socket file %s does not exist", sc.sockAddr)
} else if err != nil {
return fmt.Errorf("stats socket error: %v", err)
}
-
- if err := c.statSegment.connect(c.sockAddr); err != nil {
+ if sc.isConnected {
+ return fmt.Errorf("already connected")
+ }
+ if sc.statSegment, err = sc.connect(); err != nil {
return err
}
-
+ sc.isConnected = true
return nil
}
-func (c *StatsClient) Disconnect() error {
- if err := c.statSegment.disconnect(); err != nil {
- return err
+// Disconnect from the socket and unmap shared memory
+func (sc *StatsClient) Disconnect() error {
+ sc.isConnected = false
+ if sc.headerData == nil {
+ return nil
}
+ if err := syscall.Munmap(sc.headerData); err != nil {
+ Log.Debugf("unmapping shared memory failed: %v", err)
+ return fmt.Errorf("unmapping shared memory failed: %v", err)
+ }
+ sc.headerData = nil
+
+ Log.Debugf("successfully unmapped shared memory")
return nil
}
-func (c *StatsClient) ListStats(patterns ...string) (names []string, err error) {
- sa := c.accessStart()
- if sa.epoch == 0 {
+func (sc *StatsClient) ListStats(patterns ...string) ([]string, error) {
+ accessEpoch := sc.accessStart()
+ if accessEpoch == 0 {
return nil, adapter.ErrStatsAccessFailed
}
- indexes, err := c.listIndexes(patterns...)
+ indexes, err := sc.listIndexes(patterns...)
if err != nil {
return nil, err
}
- dirVector := c.getStatDirVector()
- vecLen := uint32(vectorLen(dirVector))
+ dirVector, err := sc.GetDirectoryVector()
+ if err != 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)
}
-
- dirEntry := c.getStatDirIndex(dirVector, index)
- var name []byte
- for n := 0; n < len(dirEntry.name); n++ {
- if dirEntry.name[n] == 0 {
- name = dirEntry.name[:n]
- break
- }
- }
- names = append(names, string(name))
+ _, dirName, _ := sc.GetStatDirOnIndex(dirVector, index)
+ names = append(names, string(dirName))
}
- if !c.accessEnd(&sa) {
+ if !sc.accessEnd(accessEpoch) {
return nil, adapter.ErrStatsDataBusy
}
return names, nil
}
-func (c *StatsClient) DumpStats(patterns ...string) (entries []adapter.StatEntry, err error) {
- sa := c.accessStart()
- if sa.epoch == 0 {
+func (sc *StatsClient) DumpStats(patterns ...string) (entries []adapter.StatEntry, err error) {
+ accessEpoch := sc.accessStart()
+ if accessEpoch == 0 {
return nil, adapter.ErrStatsAccessFailed
}
- indexes, err := c.listIndexes(patterns...)
+ indexes, err := sc.listIndexes(patterns...)
if err != nil {
return nil, err
}
- if entries, err = c.dumpEntries(indexes); err != nil {
+
+ dirVector, err := sc.GetDirectoryVector()
+ if err != nil {
return nil, err
}
+ dirLen := *(*uint32)(vectorLen(dirVector))
- if !c.accessEnd(&sa) {
+ 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
}
-func (c *StatsClient) PrepareDir(patterns ...string) (*adapter.StatDir, error) {
+func (sc *StatsClient) PrepareDir(patterns ...string) (*adapter.StatDir, error) {
dir := new(adapter.StatDir)
- sa := c.accessStart()
- if sa.epoch == 0 {
+ accessEpoch := sc.accessStart()
+ if accessEpoch == 0 {
return nil, adapter.ErrStatsAccessFailed
}
- indexes, err := c.listIndexes(patterns...)
+ indexes, err := sc.listIndexes(patterns...)
if err != nil {
return nil, err
}
dir.Indexes = indexes
- entries, err := c.dumpEntries(indexes)
+ dirVector, err := sc.GetDirectoryVector()
if err != 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)
+ }
dir.Entries = entries
- if !c.accessEnd(&sa) {
+ if !sc.accessEnd(accessEpoch) {
return nil, adapter.ErrStatsDataBusy
}
- dir.Epoch = sa.epoch
+ dir.Epoch = accessEpoch
return dir, nil
}
-func (c *StatsClient) UpdateDir(dir *adapter.StatDir) (err error) {
- epoch, _ := c.getEpoch()
+// UpdateDir refreshes directory data for all counters
+func (sc *StatsClient) UpdateDir(dir *adapter.StatDir) (err error) {
+ epoch, _ := sc.GetEpoch()
if dir.Epoch != epoch {
return adapter.ErrStatsDirStale
}
- sa := c.accessStart()
- if sa.epoch == 0 {
+ accessEpoch := sc.accessStart()
+ if accessEpoch == 0 {
return adapter.ErrStatsAccessFailed
}
- dirVector := c.getStatDirVector()
-
+ dirVector, err := sc.GetDirectoryVector()
+ if err != nil {
+ return err
+ }
for i, index := range dir.Indexes {
- dirEntry := c.getStatDirIndex(dirVector, index)
-
- var name []byte
- for n := 0; n < len(dirEntry.name); n++ {
- if dirEntry.name[n] == 0 {
- name = dirEntry.name[:n]
- break
- }
- }
- if len(name) == 0 {
+ statSegDir, dirName, dirType := sc.GetStatDirOnIndex(dirVector, index)
+ if len(dirName) == 0 {
continue
}
-
entry := &dir.Entries[i]
- if !bytes.Equal(name, entry.Name) {
+ if !bytes.Equal(dirName, entry.Name) {
continue
}
- if adapter.StatType(dirEntry.directoryType) != entry.Type {
+ if adapter.StatType(dirType) != entry.Type {
continue
}
if entry.Data == nil {
continue
}
- if err := c.updateEntryData(dirEntry, &entry.Data); err != nil {
- return fmt.Errorf("updating stat data for entry %s failed: %v", name, err)
+ if err := sc.UpdateEntryData(statSegDir, &entry.Data); err != nil {
+ return fmt.Errorf("updating stat data for entry %s failed: %v", dirName, err)
}
-
}
-
- if !c.accessEnd(&sa) {
+ if !sc.accessEnd(accessEpoch) {
return adapter.ErrStatsDataBusy
}
return nil
}
+func (sc *StatsClient) connect() (statSegment, error) {
+ addr := net.UnixAddr{
+ Net: "unixpacket",
+ Name: sc.sockAddr,
+ }
+ Log.Debugf("connecting to: %v", addr)
+
+ conn, err := net.DialUnix(addr.Net, nil, &addr)
+ if err != nil {
+ Log.Warnf("connecting to socket %s failed: %s", addr, err)
+ return nil, err
+ }
+ defer func() {
+ if err := conn.Close(); err != nil {
+ Log.Warnf("closing socket failed: %v", err)
+ }
+ }()
+ Log.Debugf("connected to socket")
+
+ files, err := fd.Get(conn, 1, nil)
+ if err != nil {
+ return nil, fmt.Errorf("getting file descriptor over socket failed: %v", err)
+ }
+ if len(files) == 0 {
+ return nil, fmt.Errorf("no files received over socket")
+ }
+
+ file := files[0]
+ defer func() {
+ if err := file.Close(); err != nil {
+ Log.Warnf("closing file failed: %v", err)
+ }
+ }()
+
+ info, err := file.Stat()
+ if err != nil {
+ return nil, err
+ }
+ size := info.Size()
+
+ sc.headerData, err = syscall.Mmap(int(file.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
+ if err != nil {
+ Log.Debugf("mapping shared memory failed: %v", err)
+ return nil, fmt.Errorf("mapping shared memory failed: %v", err)
+ }
+ Log.Debugf("successfully mmapped shared memory segment (size: %v) %v", size, len(sc.headerData))
+
+ version := getVersion(sc.headerData)
+ switch version {
+ case 1:
+ return newStatSegmentV1(sc.headerData, size), nil
+ case 2:
+ return newStatSegmentV2(sc.headerData, size), nil
+ default:
+ return nil, fmt.Errorf("stat segment version is not supported: %v (min: %v, max: %v)",
+ version, minVersion, maxVersion)
+ }
+}
+
+// Starts monitoring 'inProgress' field. Returns stats segment
+// access epoch when completed, or zero value if not finished
+// within MaxWaitInProgress
+func (sc *StatsClient) accessStart() (epoch int64) {
+ t := time.Now()
+
+ epoch, inProg := sc.GetEpoch()
+ for inProg {
+ if time.Since(t) > MaxWaitInProgress {
+ return int64(0)
+ }
+ time.Sleep(CheckDelayInProgress)
+ epoch, inProg = sc.GetEpoch()
+ }
+ return epoch
+}
+
+// AccessEnd returns true if stats data reading was finished, false
+// otherwise
+func (sc *StatsClient) accessEnd(accessEpoch int64) bool {
+ epoch, inProgress := sc.GetEpoch()
+ if accessEpoch != epoch || inProgress {
+ return false
+ }
+ return true
+}
+
// listIndexes lists indexes for all stat entries that match any of the regex patterns.
-func (c *StatsClient) listIndexes(patterns ...string) (indexes []uint32, err error) {
+func (sc *StatsClient) listIndexes(patterns ...string) (indexes []uint32, err error) {
if len(patterns) == 0 {
- return c.listIndexesFunc(nil)
+ return sc.listIndexesFunc(nil)
}
var regexes = make([]*regexp.Regexp, len(patterns))
for i, pattern := range patterns {
@@ -268,31 +400,28 @@ func (c *StatsClient) listIndexes(patterns ...string) (indexes []uint32, err err
}
return false
}
- return c.listIndexesFunc(nameMatches)
+ return sc.listIndexesFunc(nameMatches)
}
-func (c *StatsClient) listIndexesFunc(f func(name []byte) bool) (indexes []uint32, err error) {
+// 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) {
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 := c.getStatDirVector()
- vecLen := uint32(vectorLen(dirVector))
+ dirVector, err := sc.GetDirectoryVector()
+ if err != nil {
+ return nil, err
+ }
+ vecLen := *(*uint32)(vectorLen(dirVector))
for i := uint32(0); i < vecLen; i++ {
- dirEntry := c.getStatDirIndex(dirVector, i)
-
+ _, dirName, _ := sc.GetStatDirOnIndex(dirVector, i)
if f != nil {
- var name []byte
- for n := 0; n < len(dirEntry.name); n++ {
- if dirEntry.name[n] == 0 {
- name = dirEntry.name[:n]
- break
- }
- }
- if len(name) == 0 || !f(name) {
+ if len(dirName) == 0 || !f(dirName) {
continue
}
}
@@ -301,45 +430,3 @@ func (c *StatsClient) listIndexesFunc(f func(name []byte) bool) (indexes []uint3
return indexes, nil
}
-
-func (c *StatsClient) dumpEntries(indexes []uint32) (entries []adapter.StatEntry, err error) {
- dirVector := c.getStatDirVector()
- dirLen := uint32(vectorLen(dirVector))
-
- debugf("dumping entres 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)
- }
-
- dirEntry := c.getStatDirIndex(dirVector, index)
-
- var name []byte
- for n := 0; n < len(dirEntry.name); n++ {
- if dirEntry.name[n] == 0 {
- name = dirEntry.name[:n]
- break
- }
- }
-
- if Debug {
- debugf(" - %3d. dir: %q type: %v offset: %d union: %d", index, name,
- adapter.StatType(dirEntry.directoryType), dirEntry.offsetVector, dirEntry.unionData)
- }
-
- if len(name) == 0 {
- continue
- }
-
- entry := adapter.StatEntry{
- Name: append([]byte(nil), name...),
- Type: adapter.StatType(dirEntry.directoryType),
- Data: c.copyEntryData(dirEntry),
- }
- entries = append(entries, entry)
- }
-
- return entries, nil
-}
diff --git a/adapter/statsclient/statseg.go b/adapter/statsclient/statseg.go
deleted file mode 100644
index 42ab3de..0000000
--- a/adapter/statsclient/statseg.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package statsclient
-
-import (
- "sync/atomic"
- "time"
- "unsafe"
-)
-
-var (
- MaxWaitInProgress = time.Millisecond * 100
- CheckDelayInProgress = time.Microsecond * 10
-)
-
-type sharedHeaderBase struct {
- epoch int64
- inProgress int64
- directoryOffset int64
- errorOffset int64
- statsOffset int64
-}
-
-type sharedHeaderV0 struct {
- sharedHeaderBase
-}
-
-type sharedHeader struct {
- version uint64
- sharedHeaderBase
-}
-
-func (h *sharedHeader) legacyVersion() bool {
- // older VPP (<=19.04) did not have version in stat segment header
- // we try to provide fallback support by skipping it in header
- if h.version > maxVersion && h.inProgress > 1 && h.epoch == 0 {
- return true
- }
- return false
-}
-
-func loadSharedHeader(b []byte) (header sharedHeader) {
- h := (*sharedHeader)(unsafe.Pointer(&b[0]))
- header.version = atomic.LoadUint64(&h.version)
- header.epoch = atomic.LoadInt64(&h.epoch)
- header.inProgress = atomic.LoadInt64(&h.inProgress)
- header.directoryOffset = atomic.LoadInt64(&h.directoryOffset)
- header.errorOffset = atomic.LoadInt64(&h.errorOffset)
- header.statsOffset = atomic.LoadInt64(&h.statsOffset)
- return
-}
-
-func loadSharedHeaderLegacy(b []byte) (header sharedHeader) {
- h := (*sharedHeaderV0)(unsafe.Pointer(&b[0]))
- header.version = 0
- header.epoch = atomic.LoadInt64(&h.epoch)
- header.inProgress = atomic.LoadInt64(&h.inProgress)
- header.directoryOffset = atomic.LoadInt64(&h.directoryOffset)
- header.errorOffset = atomic.LoadInt64(&h.errorOffset)
- header.statsOffset = atomic.LoadInt64(&h.statsOffset)
- return
-}
-
-type statSegAccess struct {
- epoch int64
-}
-
-func (c *statSegment) accessStart() statSegAccess {
- t := time.Now()
-
- epoch, inprog := c.getEpoch()
- for inprog {
- if time.Since(t) > MaxWaitInProgress {
- return statSegAccess{}
- } else {
- time.Sleep(CheckDelayInProgress)
- }
- epoch, inprog = c.getEpoch()
- }
- return statSegAccess{
- epoch: epoch,
- }
-}
-
-func (c *statSegment) accessEnd(acc *statSegAccess) bool {
- epoch, inprog := c.getEpoch()
- if acc.epoch != epoch || inprog {
- return false
- }
- return true
-}
-
-type vecHeader struct {
- length uint64
- vectorData [0]uint8
-}
-
-func vectorLen(v unsafe.Pointer) uint64 {
- vec := *(*vecHeader)(unsafe.Pointer(uintptr(v) - unsafe.Sizeof(uint64(0))))
- return vec.length
-}
-
-//go:nosplit
-func statSegPointer(p unsafe.Pointer, offset uintptr) unsafe.Pointer {
- return unsafe.Pointer(uintptr(p) + offset)
-}
diff --git a/adapter/statsclient/statseg_v1.go b/adapter/statsclient/statseg_v1.go
new file mode 100644
index 0000000..a02c7ac
--- /dev/null
+++ b/adapter/statsclient/statseg_v1.go
@@ -0,0 +1,365 @@
+// Copyright (c) 2019 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package statsclient
+
+import (
+ "fmt"
+ "sync/atomic"
+ "unsafe"
+
+ "git.fd.io/govpp.git/adapter"
+)
+
+type statSegmentV1 struct {
+ sharedHeader []byte
+ memorySize int64
+}
+
+type sharedHeaderV1 struct {
+ version uint64
+ epoch int64
+ inProgress int64
+ directoryOffset int64
+ errorOffset int64
+ statsOffset int64
+}
+
+type statSegDirectoryEntryV1 struct {
+ directoryType statDirectoryType
+ // unionData can represent:
+ // - offset
+ // - index
+ // - value
+ unionData uint64
+ offsetVector uint64
+ name [128]byte
+}
+
+func newStatSegmentV1(data []byte, size int64) *statSegmentV1 {
+ return &statSegmentV1{
+ sharedHeader: data,
+ memorySize: size,
+ }
+}
+
+func (ss *statSegmentV1) loadSharedHeader(b []byte) (header sharedHeaderV1) {
+ h := (*sharedHeaderV1)(unsafe.Pointer(&b[0]))
+ return sharedHeaderV1{
+ version: atomic.LoadUint64(&h.version),
+ epoch: atomic.LoadInt64(&h.epoch),
+ inProgress: atomic.LoadInt64(&h.inProgress),
+ directoryOffset: atomic.LoadInt64(&h.directoryOffset),
+ errorOffset: atomic.LoadInt64(&h.errorOffset),
+ statsOffset: atomic.LoadInt64(&h.statsOffset),
+ }
+}
+
+func (ss *statSegmentV1) GetDirectoryVector() (unsafe.Pointer, error) {
+ dirOffset, _, _ := ss.getOffsets()
+ return unsafe.Pointer(&ss.sharedHeader[dirOffset]), nil
+}
+
+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{}))
+ dir := (*statSegDirectoryEntryV1)(statSegDir)
+ var name []byte
+ for n := 0; n < len(dir.name); n++ {
+ if dir.name[n] == 0 {
+ name = dir.name[:n]
+ break
+ }
+ }
+ return statSegDir, name, dir.directoryType
+}
+
+func (ss *statSegmentV1) GetEpoch() (int64, bool) {
+ sh := ss.loadSharedHeader(ss.sharedHeader)
+ return sh.epoch, sh.inProgress != 0
+}
+
+func (ss *statSegmentV1) CopyEntryData(statSegDir unsafe.Pointer) adapter.Stat {
+ dirEntry := (*statSegDirectoryEntryV1)(statSegDir)
+ dirType := adapter.StatType(dirEntry.directoryType)
+
+ switch dirType {
+ case statDirScalarIndex:
+ return adapter.ScalarStat(dirEntry.unionData)
+
+ case statDirErrorIndex:
+ if dirEntry.unionData == 0 {
+ debugf("offset invalid for %s", dirEntry.name)
+ break
+ } else if dirEntry.unionData >= uint64(len(ss.sharedHeader)) {
+ debugf("offset out of range for %s", dirEntry.name)
+ break
+ }
+
+ _, errOffset, _ := ss.getOffsets()
+ offsetVector := unsafe.Pointer(&ss.sharedHeader[errOffset])
+
+ var errData adapter.Counter
+
+ vecLen := *(*uint32)(vectorLen(offsetVector))
+ 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))
+ debugf("error index, cb: %d, offset: %d", cb, offset)
+ val := *(*adapter.Counter)(statSegPointer(unsafe.Pointer(&ss.sharedHeader[0]), offset))
+ errData += val
+ }
+ return adapter.ErrorStat(errData)
+
+ case statDirCounterVectorSimple:
+ if dirEntry.unionData == 0 {
+ debugf("offset invalid for %s", dirEntry.name)
+ break
+ } else if dirEntry.unionData >= uint64(len(ss.sharedHeader)) {
+ debugf("offset out of range for %s", dirEntry.name)
+ break
+ }
+
+ vecLen := *(*uint32)(vectorLen(unsafe.Pointer(&ss.sharedHeader[dirEntry.unionData])))
+ offsetVector := statSegPointer(unsafe.Pointer(&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)])
+ vecLen2 := *(*uint32)(vectorLen(counterVec))
+ data[i] = make([]adapter.Counter, vecLen2)
+ for j := uint32(0); j < vecLen2; j++ {
+ offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
+ val := *(*adapter.Counter)(statSegPointer(counterVec, offset))
+ data[i][j] = val
+ }
+ }
+ return adapter.SimpleCounterStat(data)
+
+ case statDirCounterVectorCombined:
+ if dirEntry.unionData == 0 {
+ debugf("offset invalid for %s", dirEntry.name)
+ break
+ } else if dirEntry.unionData >= uint64(len(ss.sharedHeader)) {
+ debugf("offset out of range for %s", dirEntry.name)
+ break
+ }
+
+ vecLen := *(*uint32)(vectorLen(unsafe.Pointer(&ss.sharedHeader[dirEntry.unionData])))
+ offsetVector := statSegPointer(unsafe.Pointer(&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)])
+ vecLen2 := *(*uint32)(vectorLen(counterVec))
+ data[i] = make([]adapter.CombinedCounter, vecLen2)
+ for j := uint32(0); j < vecLen2; j++ {
+ offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
+ val := *(*adapter.CombinedCounter)(statSegPointer(counterVec, offset))
+ data[i][j] = val
+ }
+ }
+ return adapter.CombinedCounterStat(data)
+
+ case statDirNameVector:
+ if dirEntry.unionData == 0 {
+ debugf("offset invalid for %s", dirEntry.name)
+ break
+ } else if dirEntry.unionData >= uint64(len(ss.sharedHeader)) {
+ debugf("offset out of range for %s", dirEntry.name)
+ break
+ }
+
+ vecLen := *(*uint32)(vectorLen(unsafe.Pointer(&ss.sharedHeader[dirEntry.unionData])))
+ offsetVector := statSegPointer(unsafe.Pointer(&ss.sharedHeader[0]), uintptr(dirEntry.offsetVector))
+
+ data := make([]adapter.Name, vecLen)
+ for i := uint32(0); i < vecLen; i++ {
+ cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
+ if cb == 0 {
+ debugf("name vector out of range for %s (%v)", dirEntry.name, i)
+ continue
+ }
+ nameVec := unsafe.Pointer(&ss.sharedHeader[cb])
+ vecLen2 := *(*uint32)(vectorLen(nameVec))
+
+ nameStr := make([]byte, 0, vecLen2)
+ for j := uint32(0); j < vecLen2; j++ {
+ offset := uintptr(j) * unsafe.Sizeof(byte(0))
+ val := *(*byte)(statSegPointer(nameVec, offset))
+ if val > 0 {
+ nameStr = append(nameStr, val)
+ }
+ }
+ data[i] = nameStr
+ }
+ return adapter.NameStat(data)
+
+ case statDirEmpty:
+ // no-op
+
+ default:
+ // TODO: monitor occurrences with metrics
+ debugf("Unknown type %d for stat entry: %q", dirEntry.directoryType, dirEntry.name)
+ }
+ return nil
+}
+
+func (ss *statSegmentV1) UpdateEntryData(statSegDir unsafe.Pointer, stat *adapter.Stat) error {
+ dirEntry := (*statSegDirectoryEntryV1)(statSegDir)
+ switch (*stat).(type) {
+ case adapter.ScalarStat:
+ *stat = adapter.ScalarStat(dirEntry.unionData)
+
+ case adapter.ErrorStat:
+ if dirEntry.unionData == 0 {
+ debugf("offset invalid for %s", dirEntry.name)
+ break
+ } else if dirEntry.unionData >= uint64(len(ss.sharedHeader)) {
+ debugf("offset out of range for %s", dirEntry.name)
+ break
+ }
+
+ _, errOffset, _ := ss.getOffsets()
+ offsetVector := unsafe.Pointer(&ss.sharedHeader[errOffset])
+
+ var errData adapter.Counter
+
+ vecLen := *(*uint32)(vectorLen(unsafe.Pointer(&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))
+ errData += val
+ }
+ *stat = adapter.ErrorStat(errData)
+
+ case adapter.SimpleCounterStat:
+ if dirEntry.unionData == 0 {
+ debugf("offset invalid for %s", dirEntry.name)
+ break
+ } else if dirEntry.unionData >= uint64(len(ss.sharedHeader)) {
+ debugf("offset out of range for %s", dirEntry.name)
+ break
+ }
+
+ vecLen := *(*uint32)(vectorLen(unsafe.Pointer(&ss.sharedHeader[dirEntry.unionData])))
+ offsetVector := statSegPointer(unsafe.Pointer(&ss.sharedHeader[0]), uintptr(dirEntry.offsetVector))
+
+ data := (*stat).(adapter.SimpleCounterStat)
+ if uint32(len(data)) != vecLen {
+ return ErrStatDataLenIncorrect
+ }
+ for i := uint32(0); i < vecLen; i++ {
+ cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
+ counterVec := unsafe.Pointer(&ss.sharedHeader[uintptr(cb)])
+ vecLen2 := *(*uint32)(vectorLen(counterVec))
+ simpleData := data[i]
+ if uint32(len(simpleData)) != vecLen2 {
+ return ErrStatDataLenIncorrect
+ }
+ for j := uint32(0); j < vecLen2; j++ {
+ offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0))
+ val := *(*adapter.Counter)(statSegPointer(counterVec, offset))
+ simpleData[j] = val
+ }
+ }
+
+ case adapter.CombinedCounterStat:
+ if dirEntry.unionData == 0 {
+ debugf("offset invalid for %s", dirEntry.name)
+ break
+ } else if dirEntry.unionData >= uint64(len(ss.sharedHeader)) {
+ debugf("offset out of range for %s", dirEntry.name)
+ break
+ }
+
+ vecLen := *(*uint32)(vectorLen(unsafe.Pointer(&ss.sharedHeader[dirEntry.unionData])))
+ offsetVector := statSegPointer(unsafe.Pointer(&ss.sharedHeader[0]), uintptr(dirEntry.offsetVector))
+
+ data := (*stat).(adapter.CombinedCounterStat)
+ if uint32(len(data)) != vecLen {
+ return ErrStatDataLenIncorrect
+ }
+ for i := uint32(0); i < vecLen; i++ {
+ cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
+ counterVec := unsafe.Pointer(&ss.sharedHeader[uintptr(cb)])
+ vecLen2 := *(*uint32)(vectorLen(counterVec))
+ combData := data[i]
+ if uint32(len(combData)) != vecLen2 {
+ return ErrStatDataLenIncorrect
+ }
+ for j := uint32(0); j < vecLen2; j++ {
+ offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{})
+ val := *(*adapter.CombinedCounter)(statSegPointer(counterVec, offset))
+ combData[j] = val
+ }
+ }
+
+ case adapter.NameStat:
+ if dirEntry.unionData == 0 {
+ debugf("offset invalid for %s", dirEntry.name)
+ break
+ } else if dirEntry.unionData >= uint64(len(ss.sharedHeader)) {
+ debugf("offset out of range for %s", dirEntry.name)
+ break
+ }
+
+ vecLen := *(*uint32)(vectorLen(unsafe.Pointer(&ss.sharedHeader[dirEntry.unionData])))
+ offsetVector := statSegPointer(unsafe.Pointer(&ss.sharedHeader[0]), uintptr(dirEntry.offsetVector))
+
+ data := (*stat).(adapter.NameStat)
+ if uint32(len(data)) != vecLen {
+ return ErrStatDataLenIncorrect
+ }
+ for i := uint32(0); i < vecLen; i++ {
+ cb := *(*uint64)(statSegPointer(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0))))
+ if cb == 0 {
+ continue
+ }
+ nameVec := unsafe.Pointer(&ss.sharedHeader[cb])
+ vecLen2 := *(*uint32)(vectorLen(nameVec))
+
+ nameData := data[i]
+ if uint32(len(nameData))+1 != vecLen2 {
+ return ErrStatDataLenIncorrect
+ }
+ for j := uint32(0); j < vecLen2; j++ {
+ offset := uintptr(j) * unsafe.Sizeof(byte(0))
+ val := *(*byte)(statSegPointer(nameVec, offset))
+ if val == 0 {
+ break
+ }
+ nameData[j] = val
+ }
+ }
+
+ default:
+ if Debug {
+ Log.Debugf("Unrecognized stat type %T for stat entry: %v", stat, dirEntry.name)
+ }
+ }
+ return nil
+}
+
+// Get offsets for various types of data
+func (ss *statSegmentV1) getOffsets() (dir, err, stat int64) {
+ sh := ss.loadSharedHeader(ss.sharedHeader)
+ return sh.directoryOffset, sh.errorOffset, sh.statsOffset
+}
diff --git a/adapter/statsclient/statseg_v2.go b/adapter/statsclient/statseg_v2.go
new file mode 100644
index 0000000..7091ff9
--- /dev/null
+++ b/adapter/statsclient/statseg_v2.go
@@ -0,0 +1,351 @@
+// Copyright (c) 2020 Cisco and/or its affiliates.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package statsclient
+
+import (
+ "fmt"
+ "sync/atomic"
+ "unsafe"
+
+ "git.fd.io/govpp.git/adapter"
+)
+
+type statSegmentV2 struct {
+ sharedHeader []byte
+ memorySize int64
+}
+
+type sharedHeaderV2 struct {
+ version uint64
+ base unsafe.Pointer
+ epoch int64
+ inProgress int64
+ dirVector unsafe.Pointer
+ errorVector unsafe.Pointer
+}
+
+type statSegDirectoryEntryV2 struct {
+ directoryType statDirectoryType
+ // unionData can represent:
+ // - index
+ // - value
+ // - pointer to data
+ unionData uint64
+ name [128]byte
+}
+
+func newStatSegmentV2(data []byte, size int64) *statSegmentV2 {
+ return &statSegmentV2{
+ sharedHeader: data,
+ memorySize: size,
+ }
+}
+
+func (ss *statSegmentV2) loadSharedHeader(b []byte) (header sharedHeaderV2) {
+ h := (*sharedHeaderV2)(unsafe.Pointer(&b[0]))
+ return sharedHeaderV2{
+ version: atomic.LoadUint64(&h.version),
+ base: atomic.LoadPointer(&h.base),
+ epoch: atomic.LoadInt64(&h.epoch),
+ inProgress: atomic.LoadInt64(&h.inProgress),
+ dirVector: atomic.LoadPointer(&h.dirVector),
+ errorVector: atomic.LoadPointer(&h.errorVector),
+ }
+}
+
+func (ss *statSegmentV2) GetDirectoryVector() (unsafe.Pointer, error) {
+ header := ss.loadSharedHeader(ss.sharedHeader)
+ return ss.adjust(unsafe.Pointer(&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{}))
+ dir := (*statSegDirectoryEntryV2)(statSegDir)
+ var name []byte
+ for n := 0; n < len(dir.name); n++ {
+ if dir.name[n] == 0 {
+ name = dir.name[:n]
+ break
+ }
+ }
+ return statSegDir, name, dir.directoryType
+}
+
+func (ss *statSegmentV2) GetEpoch() (int64, bool) {
+ sh := ss.loadSharedHeader(ss.sharedHeader)
+ return sh.epoch, sh.inProgress != 0
+}
+
+func (ss *statSegmentV2) CopyEntryData(statSegDir unsafe.Pointer) adapter.Stat {
+ dirEntry := (*statSegDirectoryEntryV2)(statSegDir)
+ if dirEntry.unionData == 0 {
+ debugf("data value or pointer not defined for %s", dirEntry.name)
+ return nil
+ }
+
+ switch adapter.StatType(dirEntry.directoryType) {
+ case statDirScalarIndex:
+ return adapter.ScalarStat(dirEntry.unionData)
+
+ case statDirErrorIndex:
+ dirVector, err := ss.getErrorVector()
+ if err != nil {
+ debugf("error vector pointer is out of range for %s", dirEntry.name)
+ return nil
+ }
+ vecLen := *(*uint32)(vectorLen(dirVector))
+ var errData adapter.Counter
+ for i := uint32(0); i < vecLen; i++ {
+ cb := statSegPointer(dirVector, uintptr(i+1)*unsafe.Sizeof(uint64(0)))
+ cbVal, err := ss.adjust(vectorLen(cb))
+ if err != nil {
+ debugf("error counter pointer out of range")
+ continue
+ }
+ offset := uintptr(dirEntry.unionData) * unsafe.Sizeof(adapter.Counter(0))
+ val := *(*adapter.Counter)(statSegPointer(cbVal, offset))
+ errData += val
+ }
+ return adapter.ErrorStat(errData)
+
+ case statDirCounterVectorSimple:
+ dirVector, err := ss.adjust(unsafe.Pointer(&dirEntry.unionData))
+ if err != nil {
+ debugf("data vector pointer is out of range for %s", dirEntry.name)
+ return nil
+ }
+ vecLen := *(*uint32)(vectorLen(dirVector))
+ data := make([][]adapter.Counter, vecLen)
+ for i := uint32(0); i < vecLen; i++ {
+ counterVectorOffset := statSegPointer(dirVector, uintptr(i+1)*unsafe.Sizeof(uint64(0)))
+ counterVector, err := ss.adjust(vectorLen(counterVectorOffset))
+ if err != nil {
+ debugf("counter (vector simple) pointer out of range")
+ 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
+ }
+ }
+ return adapter.SimpleCounterStat(data)
+
+ case statDirCounterVectorCombined:
+ dirVector, err := ss.adjust(unsafe.Pointer(&dirEntry.unionData))
+ if err != nil {
+ debugf("data vector pointer is out of range for %s", dirEntry.name)
+ return nil
+ }
+ vecLen := *(*uint32)(vectorLen(dirVector))
+ data := make([][]adapter.CombinedCounter, vecLen)
+ for i := uint32(0); i < vecLen; i++ {
+ counterVectorOffset := statSegPointer(dirVector, uintptr(i+1)*unsafe.Sizeof(uint64(0)))
+ counterVector, err := ss.adjust(vectorLen(counterVectorOffset))
+ if err != nil {
+ debugf("counter (vector combined) pointer out of range")
+ 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
+ }
+ }
+ return adapter.CombinedCounterStat(data)
+
+ case statDirNameVector:
+ dirVector, err := ss.adjust(unsafe.Pointer(&dirEntry.unionData))
+ if err != nil {
+ debugf("data vector pointer is out of range for %s", dirEntry.name)
+ return nil
+ }
+ vecLen := *(*uint32)(vectorLen(dirVector))
+ data := make([]adapter.Name, vecLen)
+ for i := uint32(0); i < vecLen; i++ {
+ nameVectorOffset := statSegPointer(dirVector, uintptr(i+1)*unsafe.Sizeof(uint64(0)))
+ if uintptr(nameVectorOffset) == 0 {
+ debugf("name vector out of range for %s (%v)", dirEntry.name, i)
+ continue
+ }
+ nameVector, err := ss.adjust(vectorLen(nameVectorOffset))
+ if err != nil {
+ debugf("name data pointer out of range")
+ continue
+ }
+ nameVectorLen := *(*uint32)(vectorLen(nameVector))
+ name := make([]byte, 0, nameVectorLen)
+ for j := uint32(0); j < nameVectorLen; j++ {
+ offset := uintptr(j) * unsafe.Sizeof(byte(0))
+ value := *(*byte)(statSegPointer(nameVector, offset))
+ if value > 0 {
+ name = append(name, value)
+ }
+ }
+ data[i] = name
+ }
+ return adapter.NameStat(data)
+
+ case statDirEmpty:
+ // no-op
+
+ default:
+ // TODO: monitor occurrences with metrics
+ debugf("Unknown type %d for stat entry: %q", dirEntry.directoryType, dirEntry.name)
+ }
+ return nil
+}
+
+func (ss *statSegmentV2) UpdateEntryData(statSegDir unsafe.Pointer, stat *adapter.Stat) error {
+ dirEntry := (*statSegDirectoryEntryV2)(statSegDir)
+ switch (*stat).(type) {
+ case adapter.ScalarStat:
+ *stat = adapter.ScalarStat(dirEntry.unionData)
+
+ case adapter.ErrorStat:
+ dirVector, err := ss.getErrorVector()
+ if err != nil {
+ debugf("error vector pointer is out of range for %s", dirEntry.name)
+ return nil
+ }
+ vecLen := *(*uint32)(vectorLen(dirVector))
+ var errData adapter.Counter
+ for i := uint32(0); i < vecLen; i++ {
+ cb := statSegPointer(dirVector, uintptr(i+1)*unsafe.Sizeof(uint64(0)))
+ cbVal, err := ss.adjust(vectorLen(cb))
+ if err != nil {
+ debugf("error counter pointer out of range")
+ continue
+ }
+ offset := uintptr(dirEntry.unionData) * unsafe.Sizeof(adapter.Counter(0))
+ val := *(*adapter.Counter)(statSegPointer(cbVal, offset))
+ errData += val
+ }
+ *stat = adapter.ErrorStat(errData)
+
+ case adapter.SimpleCounterStat:
+ dirVector, err := ss.adjust(unsafe.Pointer(&dirEntry.unionData))
+ if err != nil {
+ debugf("data vector pointer is out of range for %s", dirEntry.name)
+ return nil
+ }
+ vecLen := *(*uint32)(vectorLen(dirVector))
+ data := (*stat).(adapter.SimpleCounterStat)
+ if uint32(len(data)) != vecLen {
+ return ErrStatDataLenIncorrect
+ }
+ for i := uint32(0); i < vecLen; i++ {
+ counterVectorOffset := statSegPointer(dirVector, uintptr(i+1)*unsafe.Sizeof(uint64(0)))
+ counterVector, err := ss.adjust(vectorLen(counterVectorOffset))
+ if err != nil {
+ debugf("counter (vector simple) pointer out of range")
+ 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
+ }
+ }
+
+ case adapter.CombinedCounterStat:
+ dirVector, err := ss.adjust(unsafe.Pointer(&dirEntry.unionData))
+ if err != nil {
+ debugf("data vector pointer is out of range for %s", dirEntry.name)
+ return nil
+ }
+ vecLen := *(*uint32)(vectorLen(dirVector))
+ data := (*stat).(adapter.CombinedCounterStat)
+ for i := uint32(0); i < vecLen; i++ {
+ counterVectorOffset := statSegPointer(dirVector, uintptr(i+1)*unsafe.Sizeof(uint64(0)))
+ counterVector, err := ss.adjust(vectorLen(counterVectorOffset))
+ if err != nil {
+ debugf("counter (vector combined) pointer out of range")
+ 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
+ }
+ }
+
+ case adapter.NameStat:
+ dirVector, err := ss.adjust(unsafe.Pointer(&dirEntry.unionData))
+ if err != nil {
+ debugf("data vector pointer is out of range for %s", dirEntry.name)
+ return nil
+ }
+ vecLen := *(*uint32)(vectorLen(dirVector))
+ data := (*stat).(adapter.NameStat)
+ for i := uint32(0); i < vecLen; i++ {
+ nameVectorOffset := statSegPointer(dirVector, uintptr(i+1)*unsafe.Sizeof(uint64(0)))
+ if uintptr(nameVectorOffset) == 0 {
+ debugf("name vector out of range for %s (%v)", dirEntry.name, i)
+ continue
+ }
+ nameVector, err := ss.adjust(vectorLen(nameVectorOffset))
+ if err != nil {
+ debugf("name data pointer out of range")
+ continue
+ }
+ nameVectorLen := *(*uint32)(vectorLen(nameVector))
+ nameData := data[i]
+ if uint32(len(nameData))+1 != nameVectorLen {
+ return ErrStatDataLenIncorrect
+ }
+ for j := uint32(0); j < nameVectorLen; j++ {
+ offset := uintptr(j) * unsafe.Sizeof(byte(0))
+ value := *(*byte)(statSegPointer(nameVector, offset))
+ if value == 0 {
+ break
+ }
+ nameData[j] = value
+ }
+ }
+
+ default:
+ if Debug {
+ Log.Debugf("Unrecognized stat type %T for stat entry: %v", stat, dirEntry.name)
+ }
+ }
+ return nil
+}
+
+// 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, error) {
+ header := ss.loadSharedHeader(ss.sharedHeader)
+ adjusted := unsafe.Pointer(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) {
+ return nil, fmt.Errorf("adjusted data out of range for %v", data)
+ }
+ return adjusted, nil
+}
+
+func (ss *statSegmentV2) getErrorVector() (unsafe.Pointer, error) {
+ header := ss.loadSharedHeader(ss.sharedHeader)
+ return ss.adjust(unsafe.Pointer(&header.errorVector))
+}
diff --git a/core/stats.go b/core/stats.go
index 0717be6..cd93cc1 100644
--- a/core/stats.go
+++ b/core/stats.go
@@ -198,6 +198,9 @@ func (c *StatsConnection) GetSystemStats(sysStats *api.SystemStats) (err error)
if ss, ok := stat.Data.(adapter.SimpleCounterStat); ok {
vals = make([]uint64, len(ss))
for w := range ss {
+ if ss[w] == nil {
+ continue
+ }
vals[w] = uint64(ss[w][0])
}
}