56387407c3
- added checkstyle-go and fixstyle-go - comments in stats_fs.go were missing a space Type: make Change-Id: I520acab5ff61eaf9d0ccfd9425bdc41f74559198 Signed-off-by: Adrian Villin <avillin@cisco.com>
227 lines
6.7 KiB
Go
227 lines
6.7 KiB
Go
/*
|
|
* 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
|
|
}
|