github.com/swiftstack/ProxyFS@v0.0.0-20210203235616-4017c267d62f/fsworkout/main.go (about)

     1  // Copyright (c) 2015-2021, NVIDIA CORPORATION.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"runtime"
    10  	"strconv"
    11  	"time"
    12  
    13  	"github.com/swiftstack/ProxyFS/conf"
    14  	"github.com/swiftstack/ProxyFS/fs"
    15  	"github.com/swiftstack/ProxyFS/inode"
    16  	"github.com/swiftstack/ProxyFS/trackedlock"
    17  	"github.com/swiftstack/ProxyFS/transitions"
    18  )
    19  
    20  const (
    21  	dirInodeNamePrefix  = "__fsworkout_dir_"
    22  	fileInodeNamePrefix = "__fsworkout_file_"
    23  )
    24  
    25  var (
    26  	doNextStepChan  chan bool
    27  	inodesPerThread uint64
    28  	measureCreate   bool
    29  	measureDestroy  bool
    30  	measureStat     bool
    31  	perThreadDir    bool
    32  	rootDirMutex    trackedlock.Mutex
    33  	stepErrChan     chan error
    34  	threads         uint64
    35  	volumeHandle    fs.VolumeHandle
    36  	volumeName      string
    37  )
    38  
    39  func usage(file *os.File) {
    40  	fmt.Fprintf(file, "Usage:\n")
    41  	fmt.Fprintf(file, "    %v [cCsSdD] threads inodes-per-thread conf-file [section.option=value]*\n", os.Args[0])
    42  	fmt.Fprintf(file, "  where:\n")
    43  	fmt.Fprintf(file, "    c                       run create  test in common root dir\n")
    44  	fmt.Fprintf(file, "    C                       run create  test in per thread  dir\n")
    45  	fmt.Fprintf(file, "    s                       run stat    test in common root dir\n")
    46  	fmt.Fprintf(file, "    S                       run stat    test in per thread  dir\n")
    47  	fmt.Fprintf(file, "    d                       run destroy test in common root dir\n")
    48  	fmt.Fprintf(file, "    D                       run destroy test in per thread  dir\n")
    49  	fmt.Fprintf(file, "    threads                 number of threads\n")
    50  	fmt.Fprintf(file, "    inodes-per-thread       number of inodes each thread will reference\n")
    51  	fmt.Fprintf(file, "    conf-file               input to conf.MakeConfMapFromFile()\n")
    52  	fmt.Fprintf(file, "    [section.option=value]* optional input to conf.UpdateFromStrings()\n")
    53  	fmt.Fprintf(file, "\n")
    54  	fmt.Fprintf(file, "Note: Precisely one test selector must be specified\n")
    55  	fmt.Fprintf(file, "      It is expected that c, s, then d are run in sequence\n")
    56  	fmt.Fprintf(file, "                  or that C, S, then D are run in sequence\n")
    57  	fmt.Fprintf(file, "      It is expected that cleanproxyfs is run before & after the sequence\n")
    58  }
    59  
    60  func main() {
    61  	var (
    62  		confMap                      conf.ConfMap
    63  		durationOfMeasuredOperations time.Duration
    64  		err                          error
    65  		latencyPerOpInMilliSeconds   float64
    66  		opsPerSecond                 float64
    67  		primaryPeer                  string
    68  		threadIndex                  uint64
    69  		timeAfterMeasuredOperations  time.Time
    70  		timeBeforeMeasuredOperations time.Time
    71  		volumeGroupToCheck           string
    72  		volumeGroupToUse             string
    73  		volumeGroupList              []string
    74  		volumeList                   []string
    75  		whoAmI                       string
    76  	)
    77  
    78  	// Parse arguments
    79  
    80  	if 5 > len(os.Args) {
    81  		usage(os.Stderr)
    82  		os.Exit(1)
    83  	}
    84  
    85  	switch os.Args[1] {
    86  	case "c":
    87  		measureCreate = true
    88  	case "C":
    89  		measureCreate = true
    90  		perThreadDir = true
    91  	case "s":
    92  		measureStat = true
    93  	case "S":
    94  		measureStat = true
    95  		perThreadDir = true
    96  	case "d":
    97  		measureDestroy = true
    98  	case "D":
    99  		measureDestroy = true
   100  		perThreadDir = true
   101  	default:
   102  		fmt.Fprintf(os.Stderr, "os.Args[1] ('%v') must be one of 'c', 'C', 'r', 'R', 'd', or 'D'\n", os.Args[1])
   103  		os.Exit(1)
   104  	}
   105  
   106  	threads, err = strconv.ParseUint(os.Args[2], 10, 64)
   107  	if nil != err {
   108  		fmt.Fprintf(os.Stderr, "strconv.ParseUint(\"%v\", 10, 64) of threads failed: %v\n", os.Args[2], err)
   109  		os.Exit(1)
   110  	}
   111  	if 0 == threads {
   112  		fmt.Fprintf(os.Stderr, "threads must be a positive number\n")
   113  		os.Exit(1)
   114  	}
   115  
   116  	inodesPerThread, err = strconv.ParseUint(os.Args[3], 10, 64)
   117  	if nil != err {
   118  		fmt.Fprintf(os.Stderr, "strconv.ParseUint(\"%v\", 10, 64) of inodes-per-thread failed: %v\n", os.Args[3], err)
   119  		os.Exit(1)
   120  	}
   121  	if 0 == inodesPerThread {
   122  		fmt.Fprintf(os.Stderr, "inodes-per-thread must be a positive number\n")
   123  		os.Exit(1)
   124  	}
   125  
   126  	confMap, err = conf.MakeConfMapFromFile(os.Args[4])
   127  	if nil != err {
   128  		fmt.Fprintf(os.Stderr, "conf.MakeConfMapFromFile(\"%v\") failed: %v\n", os.Args[4], err)
   129  		os.Exit(1)
   130  	}
   131  
   132  	if 5 < len(os.Args) {
   133  		err = confMap.UpdateFromStrings(os.Args[5:])
   134  		if nil != err {
   135  			fmt.Fprintf(os.Stderr, "confMap.UpdateFromStrings(%#v) failed: %v\n", os.Args[5:], err)
   136  			os.Exit(1)
   137  		}
   138  	}
   139  
   140  	// Upgrade confMap if necessary
   141  	err = transitions.UpgradeConfMapIfNeeded(confMap)
   142  	if nil != err {
   143  		fmt.Fprintf(os.Stderr, "Failed to upgrade config: %v", err)
   144  		os.Exit(1)
   145  	}
   146  
   147  	// Start up needed ProxyFS components
   148  
   149  	err = transitions.Up(confMap)
   150  	if nil != err {
   151  		fmt.Fprintf(os.Stderr, "transitions.Up() failed: %v\n", err)
   152  		os.Exit(1)
   153  	}
   154  
   155  	// Select first Volume of the first "active" VolumeGroup in [FSGlobals]VolumeGroupList
   156  	whoAmI, err = confMap.FetchOptionValueString("Cluster", "WhoAmI")
   157  	if nil != err {
   158  		fmt.Fprintf(os.Stderr, "confMap.FetchOptionValueString(\"Cluster\", \"WhoAmI\") failed: %v\n", err)
   159  		os.Exit(1)
   160  	}
   161  
   162  	volumeGroupList, err = confMap.FetchOptionValueStringSlice("FSGlobals", "VolumeGroupList")
   163  	if nil != err {
   164  		fmt.Fprintf(os.Stderr, "confMap.FetchOptionValueStringSlice(\"FSGlobals\", \"VolumeGroupList\") failed: %v\n", err)
   165  		os.Exit(1)
   166  	}
   167  
   168  	volumeGroupToUse = ""
   169  
   170  	for _, volumeGroupToCheck = range volumeGroupList {
   171  		primaryPeer, err = confMap.FetchOptionValueString("VolumeGroup:"+volumeGroupToCheck, "PrimaryPeer")
   172  		if nil != err {
   173  			fmt.Fprintf(os.Stderr, "confMap.FetchOptionValueString(\"VolumeGroup:%s\", \"PrimaryPeer\") failed: %v\n", volumeGroupToCheck, err)
   174  			os.Exit(1)
   175  		}
   176  		if whoAmI == primaryPeer {
   177  			volumeGroupToUse = volumeGroupToCheck
   178  			break
   179  		}
   180  	}
   181  
   182  	if "" == volumeGroupToUse {
   183  		fmt.Fprintf(os.Stderr, "confMap didn't contain an \"active\" VolumeGroup")
   184  		os.Exit(1)
   185  	}
   186  
   187  	volumeList, err = confMap.FetchOptionValueStringSlice("VolumeGroup:"+volumeGroupToUse, "VolumeList")
   188  	if nil != err {
   189  		fmt.Fprintf(os.Stderr, "confMap.FetchOptionValueStringSlice(\"VolumeGroup:%s\", \"PrimaryPeer\") failed: %v\n", volumeGroupToUse, err)
   190  		os.Exit(1)
   191  	}
   192  	if 1 > len(volumeList) {
   193  		fmt.Fprintf(os.Stderr, "confMap.FetchOptionValueStringSlice(\"VolumeGroup:%s\", \"VolumeList\") returned empty volumeList", volumeGroupToUse)
   194  		os.Exit(1)
   195  	}
   196  
   197  	volumeName = volumeList[0]
   198  
   199  	volumeHandle, err = fs.FetchVolumeHandleByVolumeName(volumeName)
   200  	if nil != err {
   201  		fmt.Fprintf(os.Stderr, "fs.FetchVolumeHandleByVolumeName(\"%value\",) failed: %v\n", volumeName, err)
   202  		os.Exit(1)
   203  	}
   204  
   205  	// Perform tests
   206  
   207  	stepErrChan = make(chan error, 0)   //threads)
   208  	doNextStepChan = make(chan bool, 0) //threads)
   209  
   210  	// Do initialization step
   211  	for threadIndex = uint64(0); threadIndex < threads; threadIndex++ {
   212  		go fsWorkout(threadIndex)
   213  	}
   214  	for threadIndex = uint64(0); threadIndex < threads; threadIndex++ {
   215  		err = <-stepErrChan
   216  		if nil != err {
   217  			fmt.Fprintf(os.Stderr, "fsWorkout() initialization step returned: %v\n", err)
   218  			os.Exit(1)
   219  		}
   220  	}
   221  
   222  	// Do measured operations step
   223  	timeBeforeMeasuredOperations = time.Now()
   224  	for threadIndex = uint64(0); threadIndex < threads; threadIndex++ {
   225  		doNextStepChan <- true
   226  	}
   227  	for threadIndex = uint64(0); threadIndex < threads; threadIndex++ {
   228  		err = <-stepErrChan
   229  		if nil != err {
   230  			fmt.Fprintf(os.Stderr, "fsWorkout() measured operations step returned: %v\n", err)
   231  			os.Exit(1)
   232  		}
   233  	}
   234  	timeAfterMeasuredOperations = time.Now()
   235  
   236  	// Do shutdown step
   237  	for threadIndex = uint64(0); threadIndex < threads; threadIndex++ {
   238  		doNextStepChan <- true
   239  	}
   240  	for threadIndex = uint64(0); threadIndex < threads; threadIndex++ {
   241  		err = <-stepErrChan
   242  		if nil != err {
   243  			fmt.Fprintf(os.Stderr, "fsWorkout() shutdown step returned: %v\n", err)
   244  			os.Exit(1)
   245  		}
   246  	}
   247  
   248  	// Stop ProxyFS components launched above
   249  
   250  	err = transitions.Down(confMap)
   251  	if nil != err {
   252  		fmt.Fprintf(os.Stderr, "transitions.Down() failed: %v\n", err)
   253  		os.Exit(1)
   254  	}
   255  
   256  	// Report results
   257  
   258  	durationOfMeasuredOperations = timeAfterMeasuredOperations.Sub(timeBeforeMeasuredOperations)
   259  
   260  	opsPerSecond = float64(threads*inodesPerThread*1000*1000*1000) / float64(durationOfMeasuredOperations.Nanoseconds())
   261  	latencyPerOpInMilliSeconds = float64(durationOfMeasuredOperations.Nanoseconds()) / float64(inodesPerThread*1000*1000)
   262  
   263  	fmt.Printf("opsPerSecond = %10.2f\n", opsPerSecond)
   264  	fmt.Printf("latencyPerOp = %10.2f ms\n", latencyPerOpInMilliSeconds)
   265  }
   266  
   267  func fsWorkout(threadIndex uint64) {
   268  	var (
   269  		dirInodeName    string
   270  		dirInodeNumber  inode.InodeNumber
   271  		err             error
   272  		fileInodeName   []string
   273  		fileInodeNumber inode.InodeNumber
   274  		i               uint64
   275  	)
   276  
   277  	// Do initialization step
   278  	if perThreadDir {
   279  		dirInodeName = fmt.Sprintf("%s%016X", dirInodeNamePrefix, threadIndex)
   280  		if measureCreate {
   281  			dirInodeNumber, err = volumeHandle.Mkdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil, inode.RootDirInodeNumber, dirInodeName, inode.PosixModePerm)
   282  			if nil != err {
   283  				stepErrChan <- err
   284  				runtime.Goexit()
   285  			}
   286  		} else { // measureStat || measureDestroy
   287  			dirInodeNumber, err = volumeHandle.Lookup(inode.InodeRootUserID, inode.InodeGroupID(0), nil, inode.RootDirInodeNumber, dirInodeName)
   288  			if nil != err {
   289  				stepErrChan <- err
   290  				runtime.Goexit()
   291  			}
   292  		}
   293  	} else { // !perThreadDir
   294  		dirInodeNumber = inode.RootDirInodeNumber
   295  	}
   296  	fileInodeName = make([]string, inodesPerThread)
   297  	for i = 0; i < inodesPerThread; i++ {
   298  		fileInodeName[i] = fmt.Sprintf("%s%016X_%016X", fileInodeNamePrefix, threadIndex, i)
   299  	}
   300  
   301  	// Indicate initialization step is done
   302  	stepErrChan <- nil
   303  
   304  	// Await signal to proceed with measured operations step
   305  	_ = <-doNextStepChan
   306  
   307  	// Do measured operations
   308  	for i = 0; i < inodesPerThread; i++ {
   309  		if measureCreate {
   310  			fileInodeNumber, err = volumeHandle.Create(inode.InodeRootUserID, inode.InodeGroupID(0), nil, dirInodeNumber, fileInodeName[i], inode.PosixModePerm)
   311  			if nil != err {
   312  				stepErrChan <- err
   313  				runtime.Goexit()
   314  			}
   315  		} else if measureStat {
   316  			fileInodeNumber, err = volumeHandle.Lookup(inode.InodeRootUserID, inode.InodeGroupID(0), nil, dirInodeNumber, fileInodeName[i])
   317  			if nil != err {
   318  				stepErrChan <- err
   319  				runtime.Goexit()
   320  			}
   321  			_, err = volumeHandle.Getstat(inode.InodeRootUserID, inode.InodeGroupID(0), nil, fileInodeNumber)
   322  			if nil != err {
   323  				stepErrChan <- err
   324  				runtime.Goexit()
   325  			}
   326  		} else { // measureDestroy
   327  			err = volumeHandle.Unlink(inode.InodeRootUserID, inode.InodeGroupID(0), nil, dirInodeNumber, fileInodeName[i])
   328  			if nil != err {
   329  				stepErrChan <- err
   330  				runtime.Goexit()
   331  			}
   332  		}
   333  	}
   334  
   335  	// Indicate measured operations step is done
   336  	stepErrChan <- nil
   337  
   338  	// Await signal to proceed with shutdown step
   339  	_ = <-doNextStepChan
   340  
   341  	// Do shutdown step
   342  	if perThreadDir && measureDestroy {
   343  		err = volumeHandle.Rmdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil, inode.RootDirInodeNumber, dirInodeName)
   344  		if nil != err {
   345  			stepErrChan <- err
   346  			runtime.Goexit()
   347  		}
   348  	}
   349  
   350  	// Indicate shutdown step is done
   351  	stepErrChan <- nil
   352  }