benchmarks/stat_files/main.go (118 lines of code) (raw):
// Copyright 2015 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Create and open a bunch of files, then measure the performance of repeatedly
// stating them.
package main
import (
"errors"
"flag"
"fmt"
"log"
"os"
"sync"
"sync/atomic"
"time"
"golang.org/x/net/context"
"golang.org/x/sync/errgroup"
"github.com/googlecloudplatform/gcsfuse/v2/benchmarks/internal/format"
"github.com/jacobsa/fuse/fsutil"
)
var fDir = flag.String("dir", "", "Directory within which to create the files.")
var fNumFiles = flag.Int("num_files", 4, "Number of files to create.")
var fDuration = flag.Duration("duration", 10*time.Second, "How long to run.")
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
func closeAll(files []*os.File) {
for _, f := range files {
f.Close()
}
}
func createFiles(
dir string,
numFiles int) (files []*os.File, err error) {
group, ctx := errgroup.WithContext(context.Background())
// Create files in parallel, and write them to a channel.
const parallelism = 128
var counter uint64
fileChan := make(chan *os.File)
var wg sync.WaitGroup
for i := 0; i < parallelism; i++ {
wg.Add(1)
group.Go(func() (err error) {
defer wg.Done()
for {
// Should we create another?
count := atomic.AddUint64(&counter, 1)
if count > uint64(numFiles) {
return
}
// Create it.
var f *os.File
f, err = fsutil.AnonymousFile(dir)
if err != nil {
err = fmt.Errorf("AnonymousFile: %w", err)
return
}
// Write it to the channel.
select {
case fileChan <- f:
case <-ctx.Done():
err = ctx.Err()
return
}
}
})
}
go func() {
wg.Wait()
close(fileChan)
}()
// Accumulate into the slice.
group.Go(func() (err error) {
for f := range fileChan {
files = append(files, f)
}
return
})
err = group.Wait()
if err != nil {
closeAll(files)
files = nil
}
return
}
////////////////////////////////////////////////////////////////////////
// main logic
////////////////////////////////////////////////////////////////////////
func run() (err error) {
if *fDir == "" {
err = errors.New("you must set --dir")
return
}
if *fNumFiles <= 0 {
err = fmt.Errorf("invalid setting for --num_files: %d", *fNumFiles)
return
}
// Create the temporary files.
log.Printf("Creating %d temporary files...", *fNumFiles)
files, err := createFiles(*fDir, *fNumFiles)
if err != nil {
err = fmt.Errorf("ListBackups: %w", err)
return
}
defer closeAll(files)
// Repeatedly stat the files.
log.Println("Measuring...")
var statCount int64
start := time.Now()
for ; time.Since(start) < *fDuration; statCount++ {
_, err = files[statCount%int64(len(files))].Stat()
if err != nil {
err = fmt.Errorf("stat: %w", err)
return
}
}
d := time.Since(start)
// Report.
seconds := float64(d) / float64(time.Second)
statsPerSec := float64(statCount) / seconds
fmt.Printf(
"Statted %d times in %v (%s)\n",
statCount,
d,
format.Hertz(statsPerSec))
fmt.Println()
return
}
func main() {
log.SetFlags(log.Lmicroseconds | log.Lshortfile)
flag.Parse()
err := run()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}