github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ext/dsort/adjuster_internal_test.go (about)

     1  // Package dsort provides APIs for distributed archive file shuffling.
     2  /*
     3   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package dsort
     6  
     7  import (
     8  	"os"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/NVIDIA/aistore/cmn"
    13  	"github.com/NVIDIA/aistore/cmn/atomic"
    14  	"github.com/NVIDIA/aistore/cmn/cos"
    15  	"github.com/NVIDIA/aistore/core/mock"
    16  	"github.com/NVIDIA/aistore/fs"
    17  	. "github.com/onsi/ginkgo/v2"
    18  	. "github.com/onsi/gomega"
    19  )
    20  
    21  const (
    22  	testingConfigDir = "/tmp/ais_tests"
    23  )
    24  
    25  func calcSemaLimit(acquire, release func()) int {
    26  	var i atomic.Int32
    27  	wg := &sync.WaitGroup{}
    28  	ch := make(chan int32, 200)
    29  
    30  	for range 200 {
    31  		acquire()
    32  		wg.Add(1)
    33  		go func() {
    34  			ch <- i.Inc()
    35  			time.Sleep(50 * time.Microsecond)
    36  			i.Dec()
    37  			release()
    38  			wg.Done()
    39  		}()
    40  	}
    41  
    42  	wg.Wait()
    43  	close(ch)
    44  
    45  	res := int32(0)
    46  	for c := range ch {
    47  		res = max(res, c)
    48  	}
    49  
    50  	return int(res)
    51  }
    52  
    53  var _ = Describe("newConcAdjuster", func() {
    54  	mios := mock.NewIOS()
    55  
    56  	BeforeEach(func() {
    57  		err := cos.CreateDir(testingConfigDir)
    58  		Expect(err).ShouldNot(HaveOccurred())
    59  
    60  		fs.TestNew(mios)
    61  		_, _ = fs.Add(testingConfigDir, "daeID")
    62  
    63  		config := cmn.GCO.BeginUpdate()
    64  		config.Disk.IostatTimeShort = cos.Duration(10 * time.Millisecond)
    65  		config.Disk.DiskUtilLowWM = 70
    66  		config.Disk.DiskUtilHighWM = 80
    67  		config.Disk.DiskUtilMaxWM = 95
    68  		cmn.GCO.CommitUpdate(config)
    69  	})
    70  
    71  	AfterEach(func() {
    72  		err := os.RemoveAll(testingConfigDir)
    73  		Expect(err).ShouldNot(HaveOccurred())
    74  	})
    75  
    76  	It("should not have more goroutines than specified", func() {
    77  		var (
    78  			adjuster      = newConcAdjuster(0, 3)
    79  			expectedLimit = defaultConcFuncLimit * 3
    80  		)
    81  
    82  		limit := calcSemaLimit(adjuster.acquireGoroutineSema, adjuster.releaseGoroutineSema)
    83  		Expect(limit).To(Equal(expectedLimit))
    84  	})
    85  
    86  	It("should not have more goroutines than specified max limit", func() {
    87  		var (
    88  			adjuster      = newConcAdjuster(2, 3)
    89  			expectedLimit = 2 * 3
    90  		)
    91  
    92  		limit := calcSemaLimit(adjuster.acquireGoroutineSema, adjuster.releaseGoroutineSema)
    93  		Expect(limit).To(Equal(expectedLimit))
    94  	})
    95  
    96  	It("should converge to perfect limit", func() {
    97  		cfg := cmn.GCO.Get()
    98  
    99  		var (
   100  			perfectLimit = 13
   101  			perfectUtil  = int(cfg.Disk.DiskUtilMaxWM+cfg.Disk.DiskUtilHighWM) / 2
   102  		)
   103  		availablePaths := fs.GetAvail()
   104  		mi := availablePaths[testingConfigDir]
   105  
   106  		adjuster := newConcAdjuster(0, 1)
   107  
   108  		adjuster.start()
   109  		defer adjuster.stop()
   110  
   111  		for {
   112  			curLimit := calcSemaLimit(func() {
   113  				adjuster.acquireSema(mi)
   114  			}, func() {
   115  				adjuster.releaseSema(mi)
   116  			})
   117  
   118  			// If we get enough close we can just break
   119  			if cos.Abs(curLimit-perfectLimit) <= 1 {
   120  				break
   121  			}
   122  
   123  			curUtil := perfectUtil * curLimit / perfectLimit
   124  			mios.Utils.Set(testingConfigDir, int64(curUtil))
   125  
   126  			time.Sleep(time.Millisecond) // make sure that concurrency adjuster processed all information
   127  		}
   128  	})
   129  })