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 list, err := cl.ListStats(dirPath)
41 log.Println("list stats failed:", err)
50 for _, path := range list {
51 localPath := strings.TrimPrefix(path, dirPath)
52 dir, base := filepath.Split(localPath)
55 for _, component := range strings.Split(dir, "/") {
56 if len(component) == 0 {
59 child := parent.GetChild(component)
61 child = parent.NewPersistentInode(ctx, &dirNode{client: cl, lastUpdate: time.Now()},
62 fs.StableAttr{Mode: fuse.S_IFDIR})
63 parent.AddChild(component, child, true)
68 filename := strings.Replace(base, " ", "_", -1)
69 child := parent.GetChild(filename)
71 child := parent.NewPersistentInode(ctx, &statNode{client: cl, path: path}, fs.StableAttr{})
72 parent.AddChild(filename, child, true)
78 func getCounterContent(path string, client *statsclient.StatsClient) (content string, status syscall.Errno) {
80 //We add '$' because we deal with regexp here
81 res, err := client.DumpStats(path + "$")
83 return content, syscall.EAGAIN
86 return content, syscall.ENOENT
90 if result.Data == nil {
95 case adapter.ScalarIndex:
96 stats := result.Data.(adapter.ScalarStat)
97 content = fmt.Sprintf("%.2f\n", stats)
98 case adapter.ErrorIndex:
99 stats := result.Data.(adapter.ErrorStat)
100 content = fmt.Sprintf("%-16s%s\n", "Index", "Count")
101 for i, value := range stats {
102 content += fmt.Sprintf("%-16d%d\n", i, value)
104 case adapter.SimpleCounterVector:
105 stats := result.Data.(adapter.SimpleCounterStat)
106 content = fmt.Sprintf("%-16s%-16s%s\n", "Thread", "Index", "Packets")
107 for i, vector := range stats {
108 for j, value := range vector {
109 content += fmt.Sprintf("%-16d%-16d%d\n", i, j, value)
112 case adapter.CombinedCounterVector:
113 stats := result.Data.(adapter.CombinedCounterStat)
114 content = fmt.Sprintf("%-16s%-16s%-16s%s\n", "Thread", "Index", "Packets", "Bytes")
115 for i, vector := range stats {
116 for j, value := range vector {
117 content += fmt.Sprintf("%-16d%-16d%-16d%d\n", i, j, value[0], value[1])
120 case adapter.NameVector:
121 stats := result.Data.(adapter.NameStat)
122 content = fmt.Sprintf("%-16s%s\n", "Index", "Name")
123 for i, value := range stats {
124 content += fmt.Sprintf("%-16d%s\n", i, string(value))
127 content = fmt.Sprintf("Unknown stat type: %d\n", result.Type)
128 //For now, the empty type (file deleted) is not implemented in GoVPP
129 return content, syscall.ENOENT
131 return content, fs.OK
134 type rootNode struct {
136 client *statsclient.StatsClient
140 var _ = (fs.NodeOnAdder)((*rootNode)(nil))
142 func (root *rootNode) OnAdd(ctx context.Context) {
143 updateDir(ctx, &root.Inode, root.client, "/")
144 root.lastUpdate = time.Now()
147 //The dirNode structure represents directories
148 type dirNode struct {
150 client *statsclient.StatsClient
154 var _ = (fs.NodeOpendirer)((*dirNode)(nil))
156 func (dn *dirNode) Opendir(ctx context.Context) syscall.Errno {
157 //We do not update a directory more than once a second, as counters are rarely added/deleted.
158 if time.Now().Sub(dn.lastUpdate) < time.Second {
162 //directoryPath is the path to the current directory from root
163 directoryPath := "/" + dn.Inode.Path(nil) + "/"
164 status := updateDir(ctx, &dn.Inode, dn.client, directoryPath)
165 dn.lastUpdate = time.Now()
169 //The statNode structure represents counters
170 type statNode struct {
172 client *statsclient.StatsClient
176 var _ = (fs.NodeOpener)((*statNode)(nil))
178 //When a file is opened, the correpsonding counter value is dumped and a file handle is created
179 func (sn *statNode) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) {
180 content, status := getCounterContent(sn.path, sn.client)
181 if status == syscall.ENOENT {
182 sn.Inode.ForgetPersistent()
184 return &statFH{data: []byte(content)}, fuse.FOPEN_DIRECT_IO, status
187 /* The statFH structure aims at dislaying the counters dynamically.
188 * It allows the Kernel to read data as I/O without having to specify files sizes, as they may evolve dynamically.
194 var _ = (fs.FileReader)((*statFH)(nil))
196 func (fh *statFH) Read(ctx context.Context, data []byte, off int64) (fuse.ReadResult, syscall.Errno) {
197 end := int(off) + len(data)
198 if end > len(fh.data) {
201 return fuse.ReadResultData(fh.data[off:end]), fs.OK
204 //NewStatsFileSystem creates the fs for the stat segment.
205 func NewStatsFileSystem(sc *statsclient.StatsClient) (root fs.InodeEmbedder, err error) {
206 return &rootNode{client: sc}, nil