github.com/swiftstack/proxyfs@v0.0.0-20201223034610-5434d919416e/inodeworkout/main.go (about)

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