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 }