X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=adapter%2Fstatsclient%2Fstatsclient.go;h=91102758353ce48636d97d8fca70419608889c6b;hb=db87efa2ec1e91e81517236b164b279e57b8daa8;hp=40227405a92605aac3e8ade07ae59121ffe15500;hpb=ef471318d66dd2832df4dc929d312f7cd5f7009a;p=govpp.git diff --git a/adapter/statsclient/statsclient.go b/adapter/statsclient/statsclient.go index 4022740..9110275 100644 --- a/adapter/statsclient/statsclient.go +++ b/adapter/statsclient/statsclient.go @@ -20,13 +20,31 @@ import ( "fmt" "os" "regexp" - "unsafe" logger "github.com/sirupsen/logrus" "git.fd.io/govpp.git/adapter" ) +const ( + // DefaultSocketName is default VPP stats socket file path. + DefaultSocketName = adapter.DefaultStatsSocket +) + +const socketMissing = ` +------------------------------------------------------------ + VPP stats socket file %s is missing! + + - is VPP running with stats segment enabled? + - is the correct socket name configured? + + To enable it add following section to your VPP config: + statseg { + default + } +------------------------------------------------------------ +` + var ( // Debug is global variable that determines debug mode Debug = os.Getenv("DEBUG_GOVPP_STATS") != "" @@ -40,40 +58,40 @@ func init() { Log.Out = os.Stdout if Debug { Log.Level = logger.DebugLevel - Log.Debug("enabled debug mode") + Log.Debug("govpp/statsclient: enabled debug mode") } } +func debugf(f string, a ...interface{}) { + if Debug { + Log.Debugf(f, a...) + } +} + +// implements StatsAPI +var _ adapter.StatsAPI = (*StatsClient)(nil) + // StatsClient is the pure Go implementation for VPP stats API. type StatsClient struct { sockAddr string - currentEpoch int64 statSegment } // NewStatsClient returns new VPP stats API client. func NewStatsClient(sockAddr string) *StatsClient { if sockAddr == "" { - sockAddr = adapter.DefaultStatsSocket + sockAddr = DefaultSocketName } return &StatsClient{ sockAddr: sockAddr, } } -const sockNotFoundWarn = `VPP stats socket not found at: %s! - Check if VPP is running with stats segment enabled. - To enable it include following section in VPP config: - statseg { - default - } -` - func (c *StatsClient) Connect() error { // check if socket exists if _, err := os.Stat(c.sockAddr); os.IsNotExist(err) { - Log.Warnf(sockNotFoundWarn, c.sockAddr) + fmt.Fprintf(os.Stderr, socketMissing, c.sockAddr) return fmt.Errorf("stats socket file %s does not exist", c.sockAddr) } else if err != nil { return fmt.Errorf("stats socket error: %v", err) @@ -83,13 +101,6 @@ func (c *StatsClient) Connect() error { return err } - ver := c.readVersion() - Log.Debugf("stat segment version: %v", ver) - - if err := checkVersion(ver); err != nil { - return err - } - return nil } @@ -97,221 +108,238 @@ func (c *StatsClient) Disconnect() error { if err := c.statSegment.disconnect(); err != nil { return err } - return nil } -func (c *StatsClient) ListStats(patterns ...string) (statNames []string, err error) { +func (c *StatsClient) ListStats(patterns ...string) (names []string, err error) { sa := c.accessStart() - if sa == nil { - return nil, fmt.Errorf("access failed") + if sa.epoch == 0 { + return nil, adapter.ErrStatsAccessFailed } - dirOffset, _, _ := c.readOffsets() - Log.Debugf("dirOffset: %v", dirOffset) + indexes, err := c.listIndexes(patterns...) + if err != nil { + return nil, err + } - vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirOffset])) - Log.Debugf("vecLen: %v", vecLen) - Log.Debugf("unsafe.Sizeof(statSegDirectoryEntry{}): %v", unsafe.Sizeof(statSegDirectoryEntry{})) + dirVector := c.getStatDirVector() + vecLen := uint32(vectorLen(dirVector)) - for i := uint64(0); i < vecLen; i++ { - offset := uintptr(i) * unsafe.Sizeof(statSegDirectoryEntry{}) - dirEntry := (*statSegDirectoryEntry)(add(unsafe.Pointer(&c.sharedHeader[dirOffset]), offset)) + for _, index := range indexes { + if index >= vecLen { + return nil, fmt.Errorf("stat entry index %d out of dir vector len (%d)", index, vecLen) + } - nul := bytes.IndexByte(dirEntry.name[:], '\x00') - if nul < 0 { - Log.Warnf("no zero byte found for: %q", dirEntry.name[:]) - continue + 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 + } } - name := string(dirEntry.name[:nul]) + names = append(names, string(name)) + } - Log.Debugf(" %80q (type: %v, data: %d, offset: %d) ", name, dirEntry.directoryType, dirEntry.unionData, dirEntry.offsetVector) + if !c.accessEnd(&sa) { + return nil, adapter.ErrStatsDataBusy + } - if nameMatches(name, patterns) { - statNames = append(statNames, name) - } + return names, nil +} - // TODO: copy the listed entries elsewhere +func (c *StatsClient) DumpStats(patterns ...string) (entries []adapter.StatEntry, err error) { + sa := c.accessStart() + if sa.epoch == 0 { + return nil, adapter.ErrStatsAccessFailed } - if !c.accessEnd(sa) { - return nil, adapter.ErrStatDirBusy + indexes, err := c.listIndexes(patterns...) + if err != nil { + return nil, err + } + if entries, err = c.dumpEntries(indexes); err != nil { + return nil, err } - c.currentEpoch = sa.epoch + if !c.accessEnd(&sa) { + return nil, adapter.ErrStatsDataBusy + } - return statNames, nil + return entries, nil } -func (c *StatsClient) DumpStats(patterns ...string) (entries []*adapter.StatEntry, err error) { - epoch, _ := c.readEpoch() - if c.currentEpoch > 0 && c.currentEpoch != epoch { // TODO: do list stats before dump - return nil, fmt.Errorf("old data") - } +func (c *StatsClient) PrepareDir(patterns ...string) (*adapter.StatDir, error) { + dir := new(adapter.StatDir) sa := c.accessStart() - if sa == nil { - return nil, fmt.Errorf("access failed") + if sa.epoch == 0 { + return nil, adapter.ErrStatsAccessFailed } - dirOffset, _, _ := c.readOffsets() - vecLen := vectorLen(unsafe.Pointer(&c.sharedHeader[dirOffset])) - - for i := uint64(0); i < vecLen; i++ { - offset := uintptr(i) * unsafe.Sizeof(statSegDirectoryEntry{}) - dirEntry := (*statSegDirectoryEntry)(add(unsafe.Pointer(&c.sharedHeader[dirOffset]), offset)) + indexes, err := c.listIndexes(patterns...) + if err != nil { + return nil, err + } + dir.Indexes = indexes - entry := c.copyData(dirEntry) - if nameMatches(entry.Name, patterns) { - entries = append(entries, &entry) - } + entries, err := c.dumpEntries(indexes) + if err != nil { + return nil, err } + dir.Entries = entries - if !c.accessEnd(sa) { - return nil, adapter.ErrStatDumpBusy + if !c.accessEnd(&sa) { + return nil, adapter.ErrStatsDataBusy } + dir.Epoch = sa.epoch - return entries, nil + return dir, nil } -func (c *StatsClient) copyData(dirEntry *statSegDirectoryEntry) (statEntry adapter.StatEntry) { - name := dirEntry.name[:] - if nul := bytes.IndexByte(name, '\x00'); nul < 0 { - Log.Warnf("no zero byte found for: %q", dirEntry.name[:]) - } else { - name = dirEntry.name[:nul] +func (c *StatsClient) UpdateDir(dir *adapter.StatDir) (err error) { + epoch, _ := c.getEpoch() + if dir.Epoch != epoch { + return adapter.ErrStatsDirStale } - statEntry.Name = string(name) - statEntry.Type = adapter.StatType(dirEntry.directoryType) - - Log.Debugf(" - %s (type: %v, data: %v, offset: %v) ", statEntry.Name, statEntry.Type, dirEntry.unionData, dirEntry.offsetVector) + sa := c.accessStart() + if sa.epoch == 0 { + return adapter.ErrStatsAccessFailed + } - switch statEntry.Type { - case adapter.ScalarIndex: - statEntry.Data = adapter.ScalarStat(dirEntry.unionData) + dirVector := c.getStatDirVector() - case adapter.ErrorIndex: - _, errOffset, _ := c.readOffsets() - offsetVector := unsafe.Pointer(&c.sharedHeader[errOffset]) - vecLen := vectorLen(offsetVector) + for i, index := range dir.Indexes { + dirEntry := c.getStatDirIndex(dirVector, index) - var errData adapter.Counter - for i := uint64(0); i < vecLen; i++ { - cb := *(*uint64)(add(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0)))) - offset := uintptr(cb) + uintptr(dirEntry.unionData)*unsafe.Sizeof(adapter.Counter(0)) - val := *(*adapter.Counter)(add(unsafe.Pointer(&c.sharedHeader[0]), offset)) - errData += val + var name []byte + for n := 0; n < len(dirEntry.name); n++ { + if dirEntry.name[n] == 0 { + name = dirEntry.name[:n] + break + } } - statEntry.Data = adapter.ErrorStat(errData) - - case adapter.SimpleCounterVector: - if dirEntry.unionData == 0 { - Log.Debugf("\toffset is not valid") - break - } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) { - Log.Debugf("\toffset out of range") - break + if len(name) == 0 { + continue } - simpleCounter := unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]) // offset - vecLen := vectorLen(simpleCounter) - offsetVector := add(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector)) - - data := make([][]adapter.Counter, vecLen) - for i := uint64(0); i < vecLen; i++ { - cb := *(*uint64)(add(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0)))) - counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)]) - vecLen2 := vectorLen(counterVec) - for j := uint64(0); j < vecLen2; j++ { - offset := uintptr(j) * unsafe.Sizeof(adapter.Counter(0)) - val := *(*adapter.Counter)(add(counterVec, offset)) - data[i] = append(data[i], val) - } + entry := &dir.Entries[i] + if !bytes.Equal(name, entry.Name) { + continue + } + if adapter.StatType(dirEntry.directoryType) != entry.Type { + continue + } + if entry.Data == nil { + continue } - statEntry.Data = adapter.SimpleCounterStat(data) - - case adapter.CombinedCounterVector: - if dirEntry.unionData == 0 { - Log.Debugf("\toffset is not valid") - break - } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) { - Log.Debugf("\toffset out of range") - break + if err := c.updateEntryData(dirEntry, &entry.Data); err != nil { + return fmt.Errorf("updating stat data for entry %s failed: %v", name, err) } - combinedCounter := unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]) // offset - vecLen := vectorLen(combinedCounter) - offsetVector := add(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector)) - - data := make([][]adapter.CombinedCounter, vecLen) - for i := uint64(0); i < vecLen; i++ { - cb := *(*uint64)(add(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0)))) - counterVec := unsafe.Pointer(&c.sharedHeader[uintptr(cb)]) - vecLen2 := vectorLen(counterVec) - for j := uint64(0); j < vecLen2; j++ { - offset := uintptr(j) * unsafe.Sizeof(adapter.CombinedCounter{}) - val := *(*adapter.CombinedCounter)(add(counterVec, offset)) - data[i] = append(data[i], val) - } + } + + if !c.accessEnd(&sa) { + return adapter.ErrStatsDataBusy + } + + return nil +} + +// listIndexes lists indexes for all stat entries that match any of the regex patterns. +func (c *StatsClient) listIndexes(patterns ...string) (indexes []uint32, err error) { + if len(patterns) == 0 { + return c.listIndexesFunc(nil) + } + var regexes = make([]*regexp.Regexp, len(patterns)) + for i, pattern := range patterns { + r, err := regexp.Compile(pattern) + if err != nil { + return nil, fmt.Errorf("compiling regexp failed: %v", err) } - statEntry.Data = adapter.CombinedCounterStat(data) - - case adapter.NameVector: - if dirEntry.unionData == 0 { - Log.Debugf("\toffset is not valid") - break - } else if dirEntry.unionData >= uint64(len(c.sharedHeader)) { - Log.Debugf("\toffset out of range") - break + regexes[i] = r + } + nameMatches := func(name []byte) bool { + for _, r := range regexes { + if r.Match(name) { + return true + } } + return false + } + return c.listIndexesFunc(nameMatches) +} - nameVector := unsafe.Pointer(&c.sharedHeader[dirEntry.unionData]) // offset - vecLen := vectorLen(nameVector) - offsetVector := add(unsafe.Pointer(&c.sharedHeader[0]), uintptr(dirEntry.offsetVector)) - fmt.Printf("vecLen: %v\n", vecLen) +func (c *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) + } - data := make([]adapter.Name, vecLen) - for i := uint64(0); i < vecLen; i++ { - cb := *(*uint64)(add(offsetVector, uintptr(i)*unsafe.Sizeof(uint64(0)))) - if cb == 0 { - Log.Debugf("\tname vector cb out of range") - continue - } - nameVec := unsafe.Pointer(&c.sharedHeader[cb]) - fmt.Printf("offsetVector: %v, cb: %v\n", offsetVector, cb) - vecLen2 := vectorLen(nameVec) - - var nameStr []byte - for j := uint64(0); j < vecLen2; j++ { - offset := uintptr(j) * unsafe.Sizeof(byte(0)) - val := *(*byte)(add(nameVec, offset)) - if val > 0 { - nameStr = append(nameStr, val) + dirVector := c.getStatDirVector() + vecLen := uint32(vectorLen(dirVector)) + + for i := uint32(0); i < vecLen; i++ { + dirEntry := c.getStatDirIndex(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 } } - data[i] = adapter.Name(nameStr) + if len(name) == 0 || !f(name) { + continue + } } - statEntry.Data = adapter.NameStat(data) - - default: - Log.Warnf("Unknown type %d for stat entry: %s", statEntry.Type, statEntry.Name) + indexes = append(indexes, i) } - Log.Debugf("\tentry data: %#v", statEntry.Data) - - return statEntry + return indexes, nil } -func nameMatches(name string, patterns []string) bool { - if len(patterns) == 0 { - return true - } - for _, pattern := range patterns { - matched, err := regexp.MatchString(pattern, name) - if err == nil && matched { - return true +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 false + + return entries, nil }