From 1a1f4043dfae2e77de70d6adfbb8d84cdeed658d Mon Sep 17 00:00:00 2001 From: Vladimir Lavor Date: Wed, 2 Sep 2020 15:08:22 +0200 Subject: [PATCH] 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 --- adapter/statsclient/stat_segment.go | 470 -------------------------------- adapter/statsclient/stat_segment_api.go | 113 ++++++++ adapter/statsclient/statsclient.go | 339 ++++++++++++++--------- adapter/statsclient/statseg.go | 104 ------- adapter/statsclient/statseg_v1.go | 365 +++++++++++++++++++++++++ adapter/statsclient/statseg_v2.go | 351 ++++++++++++++++++++++++ core/stats.go | 3 + 7 files changed, 1045 insertions(+), 700 deletions(-) delete mode 100644 adapter/statsclient/stat_segment.go create mode 100644 adapter/statsclient/stat_segment_api.go delete mode 100644 adapter/statsclient/statseg.go create mode 100644 adapter/statsclient/statseg_v1.go create mode 100644 adapter/statsclient/statseg_v2.go 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]) } } -- 2.16.6