2 * Copyright (c) 2021 Cisco Systems and/or its affiliates.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at:
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
16 /*Go-FUSE allows us to define the behaviour of our filesystem by recoding any primitive function we need.
17 *The structure of the filesystem is constructed as a tree.
18 *Each type of nodes (root, directory, file) follows its own prmitives.
31 "github.com/hanwen/go-fuse/v2/fs"
32 "github.com/hanwen/go-fuse/v2/fuse"
34 "git.fd.io/govpp.git/adapter"
35 "git.fd.io/govpp.git/adapter/statsclient"
38 func updateDir(ctx context.Context, n *fs.Inode, cl *statsclient.StatsClient, dirPath string) syscall.Errno {
39 stats, err := cl.PrepareDir(dirPath)
41 LogMsg(fmt.Sprintf("Listing stats index failed: %v\n", err))
45 n.Operations().(*dirNode).epoch = stats.Epoch
49 for _, entry := range stats.Entries {
50 localPath := strings.TrimPrefix(string(entry.Name), dirPath)
51 dirPath, base := filepath.Split(localPath)
54 for _, component := range strings.Split(dirPath, "/") {
55 if len(component) == 0 {
58 child := parent.GetChild(component)
60 child = parent.NewInode(ctx, &dirNode{client: cl, epoch: stats.Epoch},
61 fs.StableAttr{Mode: fuse.S_IFDIR})
62 parent.AddChild(component, child, true)
64 child.Operations().(*dirNode).epoch = stats.Epoch
70 filename := strings.Replace(base, " ", "_", -1)
71 child := parent.GetChild(filename)
73 child := parent.NewPersistentInode(ctx, &statNode{client: cl, index: entry.Index}, fs.StableAttr{})
74 parent.AddChild(filename, child, true)
80 func getCounterContent(index uint32, client *statsclient.StatsClient) (content string, status syscall.Errno) {
82 statsDir, err := client.PrepareDirOnIndex(index)
84 LogMsg(fmt.Sprintf("Dumping stats on index failed: %v\n", err))
85 return content, syscall.EAGAIN
87 if len(statsDir.Entries) != 1 {
88 return content, syscall.ENOENT
90 result := statsDir.Entries[0]
91 if result.Data == nil {
96 case adapter.ScalarIndex:
97 stats := result.Data.(adapter.ScalarStat)
98 content = fmt.Sprintf("%.2f\n", stats)
99 case adapter.ErrorIndex:
100 stats := result.Data.(adapter.ErrorStat)
101 content = fmt.Sprintf("%-16s%s\n", "Index", "Count")
102 for i, value := range stats {
103 content += fmt.Sprintf("%-16d%d\n", i, value)
105 case adapter.SimpleCounterVector:
106 stats := result.Data.(adapter.SimpleCounterStat)
107 content = fmt.Sprintf("%-16s%-16s%s\n", "Thread", "Index", "Packets")
108 for i, vector := range stats {
109 for j, value := range vector {
110 content += fmt.Sprintf("%-16d%-16d%d\n", i, j, value)
113 case adapter.CombinedCounterVector:
114 stats := result.Data.(adapter.CombinedCounterStat)
115 content = fmt.Sprintf("%-16s%-16s%-16s%s\n", "Thread", "Index", "Packets", "Bytes")
116 for i, vector := range stats {
117 for j, value := range vector {
118 content += fmt.Sprintf("%-16d%-16d%-16d%d\n", i, j, value[0], value[1])
121 case adapter.NameVector:
122 stats := result.Data.(adapter.NameStat)
123 content = fmt.Sprintf("%-16s%s\n", "Index", "Name")
124 for i, value := range stats {
125 content += fmt.Sprintf("%-16d%s\n", i, string(value))
128 content = fmt.Sprintf("Unknown stat type: %d\n", result.Type)
129 //For now, the empty type (file deleted) is not implemented in GoVPP
130 return content, syscall.ENOENT
132 return content, fs.OK
135 //The dirNode structure represents directories
136 type dirNode struct {
138 client *statsclient.StatsClient
142 var _ = (fs.NodeOpendirer)((*dirNode)(nil))
143 var _ = (fs.NodeGetattrer)((*dirNode)(nil))
144 var _ = (fs.NodeOnAdder)((*dirNode)(nil))
146 func (dn *dirNode) OnAdd(ctx context.Context) {
147 if dn.Inode.IsRoot() {
148 updateDir(ctx, &dn.Inode, dn.client, "/")
152 func (dn *dirNode) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
153 out.Mtime = uint64(time.Now().Unix())
154 out.Atime = out.Mtime
155 out.Ctime = out.Mtime
159 func (dn *dirNode) Opendir(ctx context.Context) syscall.Errno {
160 var status syscall.Errno = syscall.F_OK
161 var sleepTime time.Duration = 10 * time.Millisecond
162 newEpoch, inProgress := dn.client.GetEpoch()
164 newEpoch, inProgress = dn.client.GetEpoch()
165 time.Sleep(sleepTime)
166 sleepTime = sleepTime * 2
169 //We check that the directory epoch is up to date
170 if dn.epoch != newEpoch {
171 //directoryPath is the path to the current directory from root
172 directoryPath := path.Clean("/" + dn.Inode.Path(nil) + "/")
173 status = updateDir(ctx, &dn.Inode, dn.client, directoryPath)
178 //The statNode structure represents counters
179 type statNode struct {
181 client *statsclient.StatsClient
185 var _ = (fs.NodeOpener)((*statNode)(nil))
186 var _ = (fs.NodeGetattrer)((*statNode)(nil))
188 func (fh *statNode) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
189 out.Mtime = uint64(time.Now().Unix())
190 out.Atime = out.Mtime
191 out.Ctime = out.Mtime
195 //When a file is opened, the correpsonding counter value is dumped and a file handle is created
196 func (sn *statNode) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) {
197 content, status := getCounterContent(sn.index, sn.client)
198 if status == syscall.ENOENT {
199 _, parent := sn.Inode.Parent()
200 parent.RmChild(sn.Inode.Path(parent))
203 return &statFH{data: []byte(content)}, fuse.FOPEN_DIRECT_IO, status
206 /* The statFH structure aims at dislaying the counters dynamically.
207 * It allows the Kernel to read data as I/O without having to specify files sizes, as they may evolve dynamically.
213 var _ = (fs.FileReader)((*statFH)(nil))
215 func (fh *statFH) Read(ctx context.Context, data []byte, off int64) (fuse.ReadResult, syscall.Errno) {
216 end := int(off) + len(data)
217 if end > len(fh.data) {
220 return fuse.ReadResultData(fh.data[off:end]), fs.OK
223 //NewStatsFileSystem creates the fs for the stat segment.
224 func NewStatsFileSystem(sc *statsclient.StatsClient) (root fs.InodeEmbedder, err error) {
225 return &dirNode{client: sc}, nil