blob: 80c1509623462bca19c671be3fbd7e922ae7f0dc [file] [log] [blame]
/*
* 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
}