/* * 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" "path" "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 { stats, err := cl.PrepareDir(dirPath) if err != nil { LogMsg(fmt.Sprintf("Listing stats index failed: %v\n", err)) return syscall.EAGAIN } n.Operations().(*dirNode).epoch = stats.Epoch n.RmAllChildren() for _, entry := range stats.Entries { localPath := strings.TrimPrefix(string(entry.Name), dirPath) dirPath, base := filepath.Split(localPath) parent := n for _, component := range strings.Split(dirPath, "/") { if len(component) == 0 { continue } child := parent.GetChild(component) if child == nil { child = parent.NewInode(ctx, &dirNode{client: cl, epoch: stats.Epoch}, fs.StableAttr{Mode: fuse.S_IFDIR}) parent.AddChild(component, child, true) } else { child.Operations().(*dirNode).epoch = stats.Epoch } parent = child } filename := strings.Replace(base, " ", "_", -1) child := parent.GetChild(filename) if child == nil { child := parent.NewPersistentInode(ctx, &statNode{client: cl, index: entry.Index}, fs.StableAttr{}) parent.AddChild(filename, child, true) } } return 0 } func getCounterContent(index uint32, client *statsclient.StatsClient) (content string, status syscall.Errno) { content = "" statsDir, err := client.PrepareDirOnIndex(index) if err != nil { LogMsg(fmt.Sprintf("Dumping stats on index failed: %v\n", err)) return content, syscall.EAGAIN } if len(statsDir.Entries) != 1 { return content, syscall.ENOENT } result := statsDir.Entries[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 } //The dirNode structure represents directories type dirNode struct { fs.Inode client *statsclient.StatsClient epoch int64 } var _ = (fs.NodeOpendirer)((*dirNode)(nil)) var _ = (fs.NodeGetattrer)((*dirNode)(nil)) var _ = (fs.NodeOnAdder)((*dirNode)(nil)) func (dn *dirNode) OnAdd(ctx context.Context) { if dn.Inode.IsRoot() { updateDir(ctx, &dn.Inode, dn.client, "/") } } func (dn *dirNode) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno { out.Mtime = uint64(time.Now().Unix()) out.Atime = out.Mtime out.Ctime = out.Mtime return 0 } func (dn *dirNode) Opendir(ctx context.Context) syscall.Errno { var status syscall.Errno = syscall.F_OK var sleepTime time.Duration = 10 * time.Millisecond newEpoch, inProgress := dn.client.GetEpoch() for inProgress { newEpoch, inProgress = dn.client.GetEpoch() time.Sleep(sleepTime) sleepTime = sleepTime * 2 } //We check that the directory epoch is up to date if dn.epoch != newEpoch { //directoryPath is the path to the current directory from root directoryPath := path.Clean("/" + dn.Inode.Path(nil) + "/") status = updateDir(ctx, &dn.Inode, dn.client, directoryPath) } return status } //The statNode structure represents counters type statNode struct { fs.Inode client *statsclient.StatsClient index uint32 } var _ = (fs.NodeOpener)((*statNode)(nil)) var _ = (fs.NodeGetattrer)((*statNode)(nil)) func (fh *statNode) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno { out.Mtime = uint64(time.Now().Unix()) out.Atime = out.Mtime out.Ctime = out.Mtime return 0 } //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.index, sn.client) if status == syscall.ENOENT { _, parent := sn.Inode.Parent() parent.RmChild(sn.Inode.Path(parent)) } 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 &dirNode{client: sc}, nil }