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  }