summaryrefslogtreecommitdiffstats
path: root/extras/vpp_stats_fs/stats_fs.go
diff options
context:
space:
mode:
authorArthur de Kerhor <arthurdekerhor@gmail.com>2021-03-03 08:49:15 -0800
committerDave Barach <openvpp@barachs.net>2021-03-24 12:16:43 +0000
commit1f13f8fd8a5f5f68e6e03825115c64383f17a1b5 (patch)
tree966eb8209ed495c3a544aeaa32f223f5e67214ae /extras/vpp_stats_fs/stats_fs.go
parent4d2726ece83f391a7cadb1314415ef23104f9ffe (diff)
misc: fuse fs for the stats segment
This extra allows to mount a FUSE filesystem reflecting the state of the stats segment. Type: feature Signed-off-by: Arthur de Kerhor <arthurdekerhor@gmail.com> Change-Id: I692f9ca5a65c1123b3cf28c761455eec36049791
Diffstat (limited to 'extras/vpp_stats_fs/stats_fs.go')
-rw-r--r--extras/vpp_stats_fs/stats_fs.go207
1 files changed, 207 insertions, 0 deletions
diff --git a/extras/vpp_stats_fs/stats_fs.go b/extras/vpp_stats_fs/stats_fs.go
new file mode 100644
index 00000000000..a9b8ae77633
--- /dev/null
+++ b/extras/vpp_stats_fs/stats_fs.go
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2021 Cisco Systems 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.
+ */
+
+/*Go-FUSE allows us to define the behaviour of our filesystem by recoding any primitive function we need.
+ *The structure of the filesystem is constructed as a tree.
+ *Each type of nodes (root, directory, file) follows its own prmitives.
+ */
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "path/filepath"
+ "strings"
+ "syscall"
+ "time"
+
+ "github.com/hanwen/go-fuse/v2/fs"
+ "github.com/hanwen/go-fuse/v2/fuse"
+
+ "git.fd.io/govpp.git/adapter"
+ "git.fd.io/govpp.git/adapter/statsclient"
+)
+
+func updateDir(ctx context.Context, n *fs.Inode, cl *statsclient.StatsClient, dirPath string) syscall.Errno {
+ list, err := cl.ListStats(dirPath)
+ if err != nil {
+ log.Println("list stats failed:", err)
+ return syscall.EAGAIN
+ }
+
+ if list == nil {
+ n.ForgetPersistent()
+ return syscall.ENOENT
+ }
+
+ for _, path := range list {
+ localPath := strings.TrimPrefix(path, dirPath)
+ dir, base := filepath.Split(localPath)
+
+ parent := n
+ for _, component := range strings.Split(dir, "/") {
+ if len(component) == 0 {
+ continue
+ }
+ child := parent.GetChild(component)
+ if child == nil {
+ child = parent.NewPersistentInode(ctx, &dirNode{client: cl, lastUpdate: time.Now()},
+ fs.StableAttr{Mode: fuse.S_IFDIR})
+ parent.AddChild(component, child, true)
+ }
+
+ parent = child
+ }
+ filename := strings.Replace(base, " ", "_", -1)
+ child := parent.GetChild(filename)
+ if child == nil {
+ child := parent.NewPersistentInode(ctx, &statNode{client: cl, path: path}, fs.StableAttr{})
+ parent.AddChild(filename, child, true)
+ }
+ }
+ return 0
+}
+
+func getCounterContent(path string, client *statsclient.StatsClient) (content string, status syscall.Errno) {
+ content = ""
+ //We add '$' because we deal with regexp here
+ res, err := client.DumpStats(path + "$")
+ if err != nil {
+ return content, syscall.EAGAIN
+ }
+ if res == nil {
+ return content, syscall.ENOENT
+ }
+
+ result := res[0]
+ if result.Data == nil {
+ return content, 0
+ }
+
+ switch result.Type {
+ case adapter.ScalarIndex:
+ stats := result.Data.(adapter.ScalarStat)
+ content = fmt.Sprintf("%.2f\n", stats)
+ case adapter.ErrorIndex:
+ stats := result.Data.(adapter.ErrorStat)
+ content = fmt.Sprintf("%-16s%s\n", "Index", "Count")
+ for i, value := range stats {
+ content += fmt.Sprintf("%-16d%d\n", i, value)
+ }
+ case adapter.SimpleCounterVector:
+ stats := result.Data.(adapter.SimpleCounterStat)
+ content = fmt.Sprintf("%-16s%-16s%s\n", "Thread", "Index", "Packets")
+ for i, vector := range stats {
+ for j, value := range vector {
+ content += fmt.Sprintf("%-16d%-16d%d\n", i, j, value)
+ }
+ }
+ case adapter.CombinedCounterVector:
+ stats := result.Data.(adapter.CombinedCounterStat)
+ content = fmt.Sprintf("%-16s%-16s%-16s%s\n", "Thread", "Index", "Packets", "Bytes")
+ for i, vector := range stats {
+ for j, value := range vector {
+ content += fmt.Sprintf("%-16d%-16d%-16d%d\n", i, j, value[0], value[1])
+ }
+ }
+ case adapter.NameVector:
+ stats := result.Data.(adapter.NameStat)
+ content = fmt.Sprintf("%-16s%s\n", "Index", "Name")
+ for i, value := range stats {
+ content += fmt.Sprintf("%-16d%s\n", i, string(value))
+ }
+ default:
+ content = fmt.Sprintf("Unknown stat type: %d\n", result.Type)
+ //For now, the empty type (file deleted) is not implemented in GoVPP
+ return content, syscall.ENOENT
+ }
+ return content, fs.OK
+}
+
+type rootNode struct {
+ fs.Inode
+ client *statsclient.StatsClient
+ lastUpdate time.Time
+}
+
+var _ = (fs.NodeOnAdder)((*rootNode)(nil))
+
+func (root *rootNode) OnAdd(ctx context.Context) {
+ updateDir(ctx, &root.Inode, root.client, "/")
+ root.lastUpdate = time.Now()
+}
+
+//The dirNode structure represents directories
+type dirNode struct {
+ fs.Inode
+ client *statsclient.StatsClient
+ lastUpdate time.Time
+}
+
+var _ = (fs.NodeOpendirer)((*dirNode)(nil))
+
+func (dn *dirNode) Opendir(ctx context.Context) syscall.Errno {
+ //We do not update a directory more than once a second, as counters are rarely added/deleted.
+ if time.Now().Sub(dn.lastUpdate) < time.Second {
+ return 0
+ }
+
+ //directoryPath is the path to the current directory from root
+ directoryPath := "/" + dn.Inode.Path(nil) + "/"
+ status := updateDir(ctx, &dn.Inode, dn.client, directoryPath)
+ dn.lastUpdate = time.Now()
+ return status
+}
+
+//The statNode structure represents counters
+type statNode struct {
+ fs.Inode
+ client *statsclient.StatsClient
+ path string
+}
+
+var _ = (fs.NodeOpener)((*statNode)(nil))
+
+//When a file is opened, the correpsonding counter value is dumped and a file handle is created
+func (sn *statNode) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) {
+ content, status := getCounterContent(sn.path, sn.client)
+ if status == syscall.ENOENT {
+ sn.Inode.ForgetPersistent()
+ }
+ return &statFH{data: []byte(content)}, fuse.FOPEN_DIRECT_IO, status
+}
+
+/* The statFH structure aims at dislaying the counters dynamically.
+ * It allows the Kernel to read data as I/O without having to specify files sizes, as they may evolve dynamically.
+ */
+type statFH struct {
+ data []byte
+}
+
+var _ = (fs.FileReader)((*statFH)(nil))
+
+func (fh *statFH) Read(ctx context.Context, data []byte, off int64) (fuse.ReadResult, syscall.Errno) {
+ end := int(off) + len(data)
+ if end > len(fh.data) {
+ end = len(fh.data)
+ }
+ return fuse.ReadResultData(fh.data[off:end]), fs.OK
+}
+
+//NewStatsFileSystem creates the fs for the stat segment.
+func NewStatsFileSystem(sc *statsclient.StatsClient) (root fs.InodeEmbedder, err error) {
+ return &rootNode{client: sc}, nil
+}