blob: 7b0f1ec193f17936cb183ef3b8c0123694e24d52 [file] [log] [blame]
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -08001/*
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:
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
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.
14 */
15
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.
19 */
20package main
21
22import (
23 "context"
24 "fmt"
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -070025 "path"
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -080026 "path/filepath"
27 "strings"
28 "syscall"
29 "time"
30
31 "github.com/hanwen/go-fuse/v2/fs"
32 "github.com/hanwen/go-fuse/v2/fuse"
33
34 "git.fd.io/govpp.git/adapter"
35 "git.fd.io/govpp.git/adapter/statsclient"
36)
37
38func updateDir(ctx context.Context, n *fs.Inode, cl *statsclient.StatsClient, dirPath string) syscall.Errno {
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -070039 stats, err := cl.PrepareDir(dirPath)
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -080040 if err != nil {
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -070041 LogMsg(fmt.Sprintf("Listing stats index failed: %v\n", err))
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -080042 return syscall.EAGAIN
43 }
44
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -070045 n.Operations().(*dirNode).epoch = stats.Epoch
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -080046
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -070047 n.RmAllChildren()
48
49 for _, entry := range stats.Entries {
50 localPath := strings.TrimPrefix(string(entry.Name), dirPath)
51 dirPath, base := filepath.Split(localPath)
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -080052
53 parent := n
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -070054 for _, component := range strings.Split(dirPath, "/") {
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -080055 if len(component) == 0 {
56 continue
57 }
58 child := parent.GetChild(component)
59 if child == nil {
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -070060 child = parent.NewInode(ctx, &dirNode{client: cl, epoch: stats.Epoch},
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -080061 fs.StableAttr{Mode: fuse.S_IFDIR})
62 parent.AddChild(component, child, true)
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -070063 } else {
64 child.Operations().(*dirNode).epoch = stats.Epoch
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -080065 }
66
67 parent = child
68 }
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -070069
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -080070 filename := strings.Replace(base, " ", "_", -1)
71 child := parent.GetChild(filename)
72 if child == nil {
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -070073 child := parent.NewPersistentInode(ctx, &statNode{client: cl, index: entry.Index}, fs.StableAttr{})
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -080074 parent.AddChild(filename, child, true)
75 }
76 }
77 return 0
78}
79
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -070080func getCounterContent(index uint32, client *statsclient.StatsClient) (content string, status syscall.Errno) {
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -080081 content = ""
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -070082 statsDir, err := client.PrepareDirOnIndex(index)
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -080083 if err != nil {
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -070084 LogMsg(fmt.Sprintf("Dumping stats on index failed: %v\n", err))
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -080085 return content, syscall.EAGAIN
86 }
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -070087 if len(statsDir.Entries) != 1 {
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -080088 return content, syscall.ENOENT
89 }
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -070090 result := statsDir.Entries[0]
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -080091 if result.Data == nil {
92 return content, 0
93 }
94
95 switch result.Type {
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)
104 }
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)
111 }
112 }
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])
119 }
120 }
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))
126 }
127 default:
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
131 }
132 return content, fs.OK
133}
134
Adrian Villin56387402024-06-11 10:32:08 +0200135// The dirNode structure represents directories
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -0800136type dirNode struct {
137 fs.Inode
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -0700138 client *statsclient.StatsClient
139 epoch int64
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -0800140}
141
142var _ = (fs.NodeOpendirer)((*dirNode)(nil))
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -0700143var _ = (fs.NodeGetattrer)((*dirNode)(nil))
144var _ = (fs.NodeOnAdder)((*dirNode)(nil))
145
146func (dn *dirNode) OnAdd(ctx context.Context) {
147 if dn.Inode.IsRoot() {
148 updateDir(ctx, &dn.Inode, dn.client, "/")
149 }
150}
151
152func (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
156 return 0
157}
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -0800158
159func (dn *dirNode) Opendir(ctx context.Context) syscall.Errno {
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -0700160 var status syscall.Errno = syscall.F_OK
161 var sleepTime time.Duration = 10 * time.Millisecond
162 newEpoch, inProgress := dn.client.GetEpoch()
163 for inProgress {
164 newEpoch, inProgress = dn.client.GetEpoch()
165 time.Sleep(sleepTime)
166 sleepTime = sleepTime * 2
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -0800167 }
168
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -0700169 //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)
174 }
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -0800175 return status
176}
177
Adrian Villin56387402024-06-11 10:32:08 +0200178// The statNode structure represents counters
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -0800179type statNode struct {
180 fs.Inode
181 client *statsclient.StatsClient
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -0700182 index uint32
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -0800183}
184
185var _ = (fs.NodeOpener)((*statNode)(nil))
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -0700186var _ = (fs.NodeGetattrer)((*statNode)(nil))
187
188func (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
192 return 0
193}
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -0800194
Adrian Villin56387402024-06-11 10:32:08 +0200195// When a file is opened, the correpsonding counter value is dumped and a file handle is created
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -0800196func (sn *statNode) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) {
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -0700197 content, status := getCounterContent(sn.index, sn.client)
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -0800198 if status == syscall.ENOENT {
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -0700199 _, parent := sn.Inode.Parent()
200 parent.RmChild(sn.Inode.Path(parent))
201
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -0800202 }
203 return &statFH{data: []byte(content)}, fuse.FOPEN_DIRECT_IO, status
204}
205
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.
208 */
209type statFH struct {
210 data []byte
211}
212
213var _ = (fs.FileReader)((*statFH)(nil))
214
215func (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) {
218 end = len(fh.data)
219 }
220 return fuse.ReadResultData(fh.data[off:end]), fs.OK
221}
222
Adrian Villin56387402024-06-11 10:32:08 +0200223// NewStatsFileSystem creates the fs for the stat segment.
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -0800224func NewStatsFileSystem(sc *statsclient.StatsClient) (root fs.InodeEmbedder, err error) {
Arthur de Kerhor9cfbd3b2021-03-24 07:32:07 -0700225 return &dirNode{client: sc}, nil
Arthur de Kerhor1f13f8f2021-03-03 08:49:15 -0800226}