github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/bench/tools/aisloader/work.go (about) 1 // Package aisloader 2 /* 3 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 4 */ 5 6 package aisloader 7 8 import ( 9 "errors" 10 "fmt" 11 "net/http" 12 "os" 13 "path" 14 "strconv" 15 "sync" 16 "time" 17 18 "github.com/NVIDIA/aistore/bench/tools/aisloader/stats" 19 "github.com/NVIDIA/aistore/cmn" 20 "github.com/NVIDIA/aistore/cmn/atomic" 21 "github.com/NVIDIA/aistore/cmn/cos" 22 "github.com/NVIDIA/aistore/cmn/debug" 23 "github.com/NVIDIA/aistore/cmn/mono" 24 "github.com/NVIDIA/aistore/memsys" 25 "github.com/NVIDIA/aistore/tools/readers" 26 ) 27 28 const ( 29 opPut = iota 30 opGet 31 opConfig 32 ) 33 34 type ( 35 workOrder struct { 36 op int 37 proxyURL string 38 bck cmn.Bck 39 objName string // In the format of 'virtual dir' + "/" + objName 40 size int64 41 err error 42 start time.Time 43 end time.Time 44 latencies httpLatencies 45 cksumType string 46 sgl *memsys.SGL 47 } 48 ) 49 50 func postNewWorkOrder() (err error) { 51 var wo *workOrder 52 switch { 53 case runParams.getConfig: 54 wo = newGetConfigWorkOrder() 55 case runParams.putPct == 100: 56 wo, err = newPutWorkOrder() 57 case runParams.putPct == 0: 58 wo, err = newGetWorkOrder() 59 default: 60 var put bool 61 if runParams.putPct == 50 { 62 put = mono.NanoTime()&1 == 1 63 } else { 64 put = runParams.putPct > rnd.Intn(99) 65 } 66 if put { 67 wo, err = newPutWorkOrder() 68 } else { 69 wo, err = newGetWorkOrder() 70 } 71 } 72 if err == nil { 73 workCh <- wo 74 } 75 return 76 } 77 78 func validateWorkOrder(wo *workOrder, delta time.Duration) error { 79 if wo.op == opGet || wo.op == opPut { 80 if delta == 0 { 81 return fmt.Errorf("%s has the same start time as end time", wo) 82 } 83 } 84 return nil 85 } 86 87 func completeWorkOrder(wo *workOrder, terminating bool) { 88 delta := timeDelta(wo.end, wo.start) 89 90 if wo.err == nil && traceHTTPSig.Load() { 91 var lat *stats.MetricLatsAgg 92 switch wo.op { 93 case opGet: 94 lat = &intervalStats.statsd.GetLat 95 case opPut: 96 lat = &intervalStats.statsd.PutLat 97 } 98 if lat != nil { 99 lat.Add("latency.proxyconn", wo.latencies.ProxyConn) 100 lat.Add("latency.proxy", wo.latencies.Proxy) 101 lat.Add("latency.targetconn", wo.latencies.TargetConn) 102 lat.Add("latency.target", wo.latencies.Target) 103 lat.Add("latency.posthttp", wo.latencies.PostHTTP) 104 lat.Add("latency.proxyheader", wo.latencies.ProxyWroteHeader) 105 lat.Add("latency.proxyrequest", wo.latencies.ProxyWroteRequest) 106 lat.Add("latency.targetheader", wo.latencies.TargetWroteHeader) 107 lat.Add("latency.proxyresponse", wo.latencies.ProxyFirstResponse) 108 lat.Add("latency.targetrequest", wo.latencies.TargetWroteRequest) 109 lat.Add("latency.targetresponse", wo.latencies.TargetFirstResponse) 110 } 111 } 112 113 if err := validateWorkOrder(wo, delta); err != nil { 114 _, _ = fmt.Fprintf(os.Stderr, "[ERROR] %s", err.Error()) 115 return 116 } 117 118 switch wo.op { 119 case opGet: 120 getPending-- 121 intervalStats.statsd.Get.AddPending(getPending) 122 if wo.err == nil { 123 intervalStats.get.Add(wo.size, delta) 124 intervalStats.statsd.Get.Add(wo.size, delta) 125 } else { 126 fmt.Println("GET failed: ", wo.err) 127 intervalStats.statsd.Get.AddErr() 128 intervalStats.get.AddErr() 129 } 130 case opPut: 131 putPending-- 132 intervalStats.statsd.Put.AddPending(putPending) 133 if wo.err == nil { 134 bucketObjsNames.AddObjName(wo.objName) 135 intervalStats.put.Add(wo.size, delta) 136 intervalStats.statsd.Put.Add(wo.size, delta) 137 } else { 138 fmt.Println("PUT failed: ", wo.err) 139 intervalStats.put.AddErr() 140 intervalStats.statsd.Put.AddErr() 141 } 142 if wo.sgl == nil || terminating { 143 return 144 } 145 146 now, l := time.Now(), len(wo2Free) 147 debug.Assert(!wo.end.IsZero()) 148 // free previously executed PUT SGLs 149 for i := 0; i < l; i++ { 150 if terminating { 151 return 152 } 153 w := wo2Free[i] 154 // delaying freeing sgl for `wo2FreeDelay` 155 // (background at https://github.com/golang/go/issues/30597) 156 if now.Sub(w.end) < wo2FreeDelay { 157 break 158 } 159 if w.sgl != nil && !w.sgl.IsNil() { 160 w.sgl.Free() 161 copy(wo2Free[i:], wo2Free[i+1:]) 162 i-- 163 l-- 164 wo2Free = wo2Free[:l] 165 } 166 } 167 // append to free later 168 wo2Free = append(wo2Free, wo) 169 case opConfig: 170 if wo.err == nil { 171 intervalStats.getConfig.Add(1, delta) 172 intervalStats.statsd.Config.Add(delta, wo.latencies.Proxy, wo.latencies.ProxyConn) 173 } else { 174 fmt.Println("GET config failed: ", wo.err) 175 intervalStats.getConfig.AddErr() 176 } 177 default: 178 debug.Assert(false) // Should never be here 179 } 180 } 181 182 func doPut(wo *workOrder) { 183 var ( 184 sgl *memsys.SGL 185 url = wo.proxyURL 186 ) 187 if runParams.readerType == readers.TypeSG { 188 sgl = gmm.NewSGL(wo.size) 189 wo.sgl = sgl 190 } 191 r, err := readers.New(readers.Params{ 192 Type: runParams.readerType, 193 SGL: sgl, 194 Path: runParams.tmpDir, 195 Name: wo.objName, 196 Size: wo.size, 197 }, wo.cksumType) 198 199 if err != nil { 200 wo.err = err 201 return 202 } 203 if runParams.randomProxy { 204 debug.Assert(!isDirectS3()) 205 psi, err := runParams.smap.GetRandProxy(false /*excl. primary*/) 206 if err != nil { 207 fmt.Printf("PUT(wo): %v\n", err) 208 os.Exit(1) 209 } 210 url = psi.URL(cmn.NetPublic) 211 } 212 if !traceHTTPSig.Load() { 213 if isDirectS3() { 214 wo.err = s3put(wo.bck, wo.objName, r) 215 } else { 216 wo.err = put(url, wo.bck, wo.objName, r.Cksum(), r) 217 } 218 } else { 219 debug.Assert(!isDirectS3()) 220 wo.err = putWithTrace(url, wo.bck, wo.objName, &wo.latencies, r.Cksum(), r) 221 } 222 if runParams.readerType == readers.TypeFile { 223 r.Close() 224 os.Remove(path.Join(runParams.tmpDir, wo.objName)) 225 } 226 } 227 228 func doGet(wo *workOrder) { 229 var ( 230 url = wo.proxyURL 231 ) 232 if runParams.randomProxy { 233 debug.Assert(!isDirectS3()) 234 psi, err := runParams.smap.GetRandProxy(false /*excl. primary*/) 235 if err != nil { 236 fmt.Printf("GET(wo): %v\n", err) 237 os.Exit(1) 238 } 239 url = psi.URL(cmn.NetPublic) 240 } 241 if !traceHTTPSig.Load() { 242 if isDirectS3() { 243 wo.size, wo.err = s3getDiscard(wo.bck, wo.objName) 244 } else { 245 wo.size, wo.err = getDiscard(url, wo.bck, 246 wo.objName, runParams.readOff, runParams.readLen, runParams.verifyHash, runParams.latest) 247 } 248 } else { 249 debug.Assert(!isDirectS3()) 250 wo.size, wo.err = getTraceDiscard(url, wo.bck, 251 wo.objName, &wo.latencies, runParams.readOff, runParams.readLen, runParams.verifyHash, runParams.latest) 252 } 253 } 254 255 func doGetConfig(wo *workOrder) { 256 wo.latencies, wo.err = getConfig(wo.proxyURL) 257 } 258 259 func worker(wos <-chan *workOrder, results chan<- *workOrder, wg *sync.WaitGroup, numGets *atomic.Int64) { 260 defer wg.Done() 261 262 for { 263 wo, more := <-wos 264 if !more { 265 return 266 } 267 268 wo.start = time.Now() 269 270 switch wo.op { 271 case opPut: 272 doPut(wo) 273 case opGet: 274 doGet(wo) 275 numGets.Inc() 276 case opConfig: 277 doGetConfig(wo) 278 default: 279 // Should not come here 280 } 281 282 wo.end = time.Now() 283 results <- wo 284 } 285 } 286 287 /////////////// 288 // workOrder // 289 /////////////// 290 291 func newPutWorkOrder() (*workOrder, error) { 292 objName, err := _genObjName() 293 if err != nil { 294 return nil, err 295 } 296 size := runParams.minSize 297 if runParams.maxSize != runParams.minSize { 298 size = rnd.Int63n(runParams.maxSize+1-runParams.minSize) + runParams.minSize 299 } 300 putPending++ 301 return &workOrder{ 302 proxyURL: runParams.proxyURL, 303 bck: runParams.bck, 304 op: opPut, 305 objName: objName, 306 size: size, 307 cksumType: runParams.cksumType, 308 }, nil 309 } 310 311 func _genObjName() (string, error) { 312 cnt := objNameCnt.Inc() 313 if runParams.maxputs != 0 && cnt-1 == runParams.maxputs { 314 return "", fmt.Errorf("number of PUT objects reached maxputs limit (%d)", runParams.maxputs) 315 } 316 317 var ( 318 comps [3]string 319 idx = 0 320 ) 321 322 if runParams.subDir != "" { 323 comps[idx] = runParams.subDir 324 idx++ 325 } 326 327 if runParams.putShards != 0 { 328 comps[idx] = fmt.Sprintf("%05x", cnt%runParams.putShards) 329 idx++ 330 } 331 332 if useRandomObjName { 333 comps[idx] = cos.RandStringWithSrc(rnd, randomObjNameLen) 334 idx++ 335 } else { 336 objectNumber := (cnt - 1) << suffixIDMaskLen 337 objectNumber |= suffixID 338 comps[idx] = strconv.FormatUint(objectNumber, 16) 339 idx++ 340 } 341 342 return path.Join(comps[0:idx]...), nil 343 } 344 345 func newGetWorkOrder() (*workOrder, error) { 346 if bucketObjsNames.Len() == 0 { 347 return nil, errors.New("no objects in bucket") 348 } 349 350 getPending++ 351 return &workOrder{ 352 proxyURL: runParams.proxyURL, 353 bck: runParams.bck, 354 op: opGet, 355 objName: bucketObjsNames.ObjName(), 356 }, nil 357 } 358 359 func newGetConfigWorkOrder() *workOrder { 360 return &workOrder{ 361 proxyURL: runParams.proxyURL, 362 op: opConfig, 363 } 364 } 365 366 func (wo *workOrder) String() string { 367 var errstr, opName string 368 switch wo.op { 369 case opGet: 370 opName = http.MethodGet 371 case opPut: 372 opName = http.MethodPut 373 case opConfig: 374 opName = "CONFIG" 375 } 376 377 if wo.err != nil { 378 errstr = ", error: " + wo.err.Error() 379 } 380 381 return fmt.Sprintf("WO: %s/%s, start:%s end:%s, size: %d, type: %s%s", 382 wo.bck, wo.objName, wo.start.Format(time.StampMilli), wo.end.Format(time.StampMilli), wo.size, opName, errstr) 383 }