github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/bench/microbenchmarks/nstlvl/nstlvl_test.go (about)

     1  // Package nstlvl is intended to measure impact (or lack of thereof) of POSIX directory nesting on random read performance.
     2  /*
     3   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package nstlvl_test
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"io"
    11  	"math/rand"
    12  	"os"
    13  	"os/exec"
    14  	"runtime"
    15  	"strconv"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/NVIDIA/aistore/bench/microbenchmarks/nstlvl"
    20  	"github.com/NVIDIA/aistore/cmn/cos"
    21  	"github.com/NVIDIA/aistore/tools/trand"
    22  )
    23  
    24  // run with all defaults:
    25  // $ go test -v -bench=.
    26  //
    27  // generate and random-read 300K 4K-size files:
    28  // $ go test -v -bench=. -size=4096 -num=300000
    29  //
    30  // generate 8K files under `/ais/mpath` and run for at least 1m (to increase the number of iterations)
    31  // $ go test -v -bench=. -size=8192 -dir=/ais/mpath -benchtime=1m
    32  //
    33  // print usage and exit:
    34  // $ go test -bench=. -usage
    35  
    36  const (
    37  	fileNameLen = 15
    38  	dirs        = "/aaaaaa/bbbbbb/cccccc/dddddd/eeeeee/ffffff/gggggg/hhhhhh/iiiiii/jjjjjj"
    39  	dirNameLen  = 6 // NOTE: must reflect the length of dirs' dirs
    40  	skipModulo  = 967
    41  )
    42  
    43  type benchContext struct {
    44  	// internal
    45  	rnd       *rand.Rand
    46  	fileNames []string
    47  
    48  	// command line
    49  	level     int
    50  	fileSize  int64
    51  	fileCount int
    52  	skipMod   int
    53  	dir       string
    54  	help      bool
    55  }
    56  
    57  var benchCtx benchContext
    58  
    59  func init() {
    60  	flag.IntVar(&benchCtx.level, "level", 2, "initial (mountpath) nesting level")
    61  	flag.IntVar(&benchCtx.fileCount, "num", 1000, "number of files to generate (and then randomly read)")
    62  	flag.Int64Var(&benchCtx.fileSize, "size", cos.KiB, "file/object size")
    63  	flag.StringVar(&benchCtx.dir, "dir", "/tmp", "top directory for generated files")
    64  	flag.BoolVar(&benchCtx.help, "usage", false, "show command-line options")
    65  }
    66  
    67  func (bctx *benchContext) init() {
    68  	flag.Parse()
    69  	if bctx.help {
    70  		flag.Usage()
    71  		os.Exit(0)
    72  	}
    73  	if bctx.fileCount < 10 {
    74  		fmt.Printf("Error: number of files (%d) must be greater than 10 (see README for usage)\n", bctx.fileCount)
    75  		os.Exit(2)
    76  	}
    77  	fmt.Printf("files: %d size: %d\n", bctx.fileCount, bctx.fileSize)
    78  	bctx.fileNames = make([]string, 0, bctx.fileCount)
    79  	bctx.rnd = cos.NowRand()
    80  	bctx.skipMod = skipModulo
    81  	if bctx.fileCount < skipModulo {
    82  		bctx.skipMod = bctx.fileCount/2 - 1
    83  	}
    84  }
    85  
    86  func BenchmarkNestedLevel(b *testing.B) {
    87  	benchCtx.init()
    88  	for _, extraDepth := range []int{0, 2, 4, 6} {
    89  		nestedLvl := benchCtx.level + extraDepth
    90  
    91  		benchCtx.createFiles(nestedLvl)
    92  		b.Run(strconv.Itoa(nestedLvl), benchNestedLevel)
    93  		benchCtx.removeFiles()
    94  	}
    95  }
    96  
    97  func benchNestedLevel(b *testing.B) {
    98  	for i, j, k := 0, 0, 0; i < b.N; i, j = i+1, j+benchCtx.skipMod {
    99  		if j >= benchCtx.fileCount {
   100  			k++
   101  			if k >= benchCtx.skipMod {
   102  				k = 0
   103  			}
   104  			j = k
   105  		}
   106  		fqn := benchCtx.fileNames[j]
   107  		file, err := os.Open(fqn)
   108  		cos.AssertNoErr(err)
   109  		cos.DrainReader(file)
   110  		cos.Close(file)
   111  	}
   112  }
   113  
   114  func (bctx *benchContext) createFiles(lvl int) {
   115  	var (
   116  		reader = &io.LimitedReader{R: bctx.rnd, N: bctx.fileSize}
   117  		buf    = make([]byte, 32*cos.KiB)
   118  	)
   119  	for range bctx.fileCount {
   120  		fileName := bctx.dir + dirs[:lvl*(dirNameLen+1)+1] + bctx.randNestName()
   121  		file, err := cos.CreateFile(fileName)
   122  		cos.AssertNoErr(err)
   123  		_, err = io.CopyBuffer(file, reader, buf)
   124  		cos.AssertNoErr(err)
   125  		err = file.Close()
   126  		cos.AssertNoErr(err)
   127  
   128  		reader.N = bctx.fileSize
   129  		bctx.fileNames = append(bctx.fileNames, fileName)
   130  	}
   131  
   132  	cmd := exec.Command("sync")
   133  	_, err := cmd.Output()
   134  	cos.AssertNoErr(err)
   135  	time.Sleep(time.Second)
   136  
   137  	nstlvl.DropCaches()
   138  	runtime.GC()
   139  	time.Sleep(time.Second)
   140  }
   141  
   142  func (bctx *benchContext) removeFiles() {
   143  	err := os.RemoveAll(bctx.dir + dirs[:dirNameLen+1])
   144  	cos.AssertNoErr(err)
   145  	bctx.fileNames = bctx.fileNames[:0]
   146  }
   147  
   148  func (*benchContext) randNestName() string {
   149  	return trand.String(fileNameLen)
   150  }