github.com/hanwen/go-fuse@v1.0.0/benchmark/stat_test.go (about) 1 // Copyright 2016 the Go-FUSE Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package benchmark 6 7 import ( 8 "fmt" 9 "io/ioutil" 10 "log" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "runtime" 15 "sort" 16 "testing" 17 "time" 18 19 "github.com/hanwen/go-fuse/fuse/nodefs" 20 "github.com/hanwen/go-fuse/fuse/pathfs" 21 "github.com/hanwen/go-fuse/internal/testutil" 22 ) 23 24 func setupFs(fs pathfs.FileSystem, N int) (string, func()) { 25 opts := &nodefs.Options{ 26 EntryTimeout: 0.0, 27 AttrTimeout: 0.0, 28 NegativeTimeout: 0.0, 29 } 30 mountPoint := testutil.TempDir() 31 nfs := pathfs.NewPathNodeFs(fs, nil) 32 state, _, err := nodefs.MountRoot(mountPoint, nfs.Root(), opts) 33 if err != nil { 34 panic(fmt.Sprintf("cannot mount %v", err)) // ugh - benchmark has no error methods. 35 } 36 lmap := NewLatencyMap() 37 if testutil.VerboseTest() { 38 state.RecordLatencies(lmap) 39 } 40 go state.Serve() 41 42 return mountPoint, func() { 43 if testutil.VerboseTest() { 44 var total time.Duration 45 for _, n := range []string{"LOOKUP", "GETATTR", "OPENDIR", "READDIR", 46 "READDIRPLUS", "RELEASEDIR", "FLUSH", 47 } { 48 if count, dt := lmap.Get(n); count > 0 { 49 total += dt 50 log.Printf("%s %v/call n=%d", n, 51 dt/time.Duration(count), count) 52 } 53 } 54 55 log.Printf("total %v, %v/bench op", total, total/time.Duration(N)) 56 } 57 58 err := state.Unmount() 59 if err != nil { 60 log.Println("error during unmount", err) 61 } else { 62 os.RemoveAll(mountPoint) 63 } 64 } 65 } 66 67 func TestNewStatFs(t *testing.T) { 68 fs := NewStatFS() 69 for _, n := range []string{ 70 "file.txt", "sub/dir/foo.txt", 71 "sub/dir/bar.txt", "sub/marine.txt"} { 72 fs.AddFile(n) 73 } 74 75 wd, clean := setupFs(fs, 1) 76 defer clean() 77 78 names, err := ioutil.ReadDir(wd) 79 if err != nil { 80 t.Fatalf("failed: %v", err) 81 } 82 if len(names) != 2 { 83 t.Error("readdir /", names) 84 } 85 86 fi, err := os.Lstat(wd + "/sub") 87 if err != nil { 88 t.Fatalf("failed: %v", err) 89 } 90 if !fi.IsDir() { 91 t.Error("mode", fi) 92 } 93 names, err = ioutil.ReadDir(wd + "/sub") 94 if err != nil { 95 t.Fatalf("failed: %v", err) 96 } 97 if len(names) != 2 { 98 t.Error("readdir /sub", names) 99 } 100 names, err = ioutil.ReadDir(wd + "/sub/dir") 101 if err != nil { 102 t.Fatalf("failed: %v", err) 103 } 104 if len(names) != 2 { 105 t.Error("readdir /sub/dir", names) 106 } 107 108 fi, err = os.Lstat(wd + "/sub/marine.txt") 109 if err != nil { 110 t.Fatalf("failed: %v", err) 111 } 112 if fi.Mode()&os.ModeType != 0 { 113 t.Error("mode", fi) 114 } 115 } 116 117 func BenchmarkGoFuseStat(b *testing.B) { 118 b.StopTimer() 119 fs := NewStatFS() 120 121 wd, _ := os.Getwd() 122 fileList := wd + "/testpaths.txt" 123 files := ReadLines(fileList) 124 for _, fn := range files { 125 fs.AddFile(fn) 126 } 127 128 wd, clean := setupFs(fs, b.N) 129 defer clean() 130 131 for i, l := range files { 132 files[i] = filepath.Join(wd, l) 133 } 134 135 threads := runtime.GOMAXPROCS(0) 136 if err := TestingBOnePass(b, threads, fileList, wd); err != nil { 137 log.Fatalf("TestingBOnePass %v8", err) 138 } 139 } 140 141 func readdir(d string) error { 142 f, err := os.Open(d) 143 if err != nil { 144 return err 145 } 146 if _, err := f.Readdirnames(-1); err != nil { 147 return err 148 } 149 return f.Close() 150 } 151 152 func BenchmarkGoFuseReaddir(b *testing.B) { 153 b.StopTimer() 154 fs := NewStatFS() 155 156 wd, _ := os.Getwd() 157 dirSet := map[string]struct{}{} 158 159 for _, fn := range ReadLines(wd + "/testpaths.txt") { 160 fs.AddFile(fn) 161 dirSet[filepath.Dir(fn)] = struct{}{} 162 } 163 164 wd, clean := setupFs(fs, b.N) 165 defer clean() 166 167 var dirs []string 168 for dir := range dirSet { 169 dirs = append(dirs, filepath.Join(wd, dir)) 170 } 171 b.StartTimer() 172 todo := b.N 173 for todo > 0 { 174 if len(dirs) > todo { 175 dirs = dirs[:todo] 176 } 177 for _, d := range dirs { 178 if err := readdir(d); err != nil { 179 b.Fatal(err) 180 } 181 } 182 todo -= len(dirs) 183 } 184 b.StopTimer() 185 } 186 187 func TestingBOnePass(b *testing.B, threads int, filelist, mountPoint string) error { 188 runtime.GC() 189 var before, after runtime.MemStats 190 runtime.ReadMemStats(&before) 191 192 // We shell out to an external program, so the time spent by 193 // the part stat'ing doesn't interfere with the time spent by 194 // the FUSE server. 195 cmd := exec.Command("./bulkstat.bin", 196 fmt.Sprintf("-cpu=%d", threads), 197 fmt.Sprintf("-prefix=%s", mountPoint), 198 fmt.Sprintf("-N=%d", b.N), 199 fmt.Sprintf("-quiet=%v", !testutil.VerboseTest()), 200 filelist) 201 202 cmd.Stdout = os.Stdout 203 cmd.Stderr = os.Stderr 204 205 b.StartTimer() 206 err := cmd.Run() 207 b.StopTimer() 208 runtime.ReadMemStats(&after) 209 if err != nil { 210 return err 211 } 212 213 if testutil.VerboseTest() { 214 fmt.Printf("GC count %d, total GC time: %d ns/file\n", 215 after.NumGC-before.NumGC, (after.PauseTotalNs-before.PauseTotalNs)/uint64(b.N)) 216 } 217 return nil 218 } 219 220 // Add this so we can estimate impact on latency numbers. 221 func BenchmarkTimeNow(b *testing.B) { 222 for i := 0; i < b.N; i++ { 223 time.Now() 224 } 225 } 226 227 func BenchmarkCFuseThreadedStat(b *testing.B) { 228 b.StopTimer() 229 230 wd, _ := os.Getwd() 231 fileList := wd + "/testpaths.txt" 232 lines := ReadLines(fileList) 233 unique := map[string]int{} 234 for _, l := range lines { 235 unique[l] = 1 236 dir, _ := filepath.Split(l) 237 for dir != "/" && dir != "" { 238 unique[dir] = 1 239 dir = filepath.Clean(dir) 240 dir, _ = filepath.Split(dir) 241 } 242 } 243 244 out := []string{} 245 for k := range unique { 246 out = append(out, k) 247 } 248 249 f, err := ioutil.TempFile("", "") 250 if err != nil { 251 b.Fatalf("failed: %v", err) 252 } 253 sort.Strings(out) 254 for _, k := range out { 255 f.Write([]byte(fmt.Sprintf("/%s\n", k))) 256 } 257 f.Close() 258 259 mountPoint := testutil.TempDir() 260 cmd := exec.Command(wd+"/cstatfs", 261 "-o", 262 "entry_timeout=0.0,attr_timeout=0.0,ac_attr_timeout=0.0,negative_timeout=0.0", 263 mountPoint) 264 cmd.Env = append(os.Environ(), 265 fmt.Sprintf("STATFS_INPUT=%s", f.Name())) 266 cmd.Start() 267 268 bin, err := exec.LookPath("fusermount") 269 if err != nil { 270 b.Fatalf("failed: %v", err) 271 } 272 stop := exec.Command(bin, "-u", mountPoint) 273 if err != nil { 274 b.Fatalf("failed: %v", err) 275 } 276 defer stop.Run() 277 278 time.Sleep(100 * time.Millisecond) 279 os.Lstat(mountPoint) 280 threads := runtime.GOMAXPROCS(0) 281 if err := TestingBOnePass(b, threads, fileList, mountPoint); err != nil { 282 log.Fatalf("TestingBOnePass %v", err) 283 } 284 }