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  }