github.com/codingeasygo/util@v0.0.0-20231206062002-1ce2f004b7d9/xtest/perf.go (about)

     1  package xtest
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"sync"
     7  	"sync/atomic"
     8  	"time"
     9  
    10  	"github.com/codingeasygo/util/xtime"
    11  )
    12  
    13  // var FullError = fmt.Errorf("runner is almost full")
    14  
    15  type FullError struct {
    16  	Inner error
    17  }
    18  
    19  func NewFullError(e error) (err *FullError) {
    20  	err = &FullError{Inner: e}
    21  	return
    22  }
    23  
    24  func (f *FullError) Error() string {
    25  	return fmt.Sprintf("FullError(%v)", f.Inner)
    26  }
    27  
    28  func IsFullError(e error) (ok bool) {
    29  	_, ok = e.(*FullError)
    30  	return
    31  }
    32  
    33  func DoPerf(tc int, logf string, call func(int)) (int64, error) {
    34  	return DoPerfV(tc, tc, logf, call)
    35  }
    36  func DoPerfV(total, tc int, logf string, call func(int)) (int64, error) {
    37  	return DoPerfV_(total, tc, logf,
    38  		func(i int) error {
    39  			call(i)
    40  			return nil
    41  		})
    42  }
    43  
    44  func DoAutoPerfV(total, tc, peradd int, logf string, pretimeout int64, call func(int) error) (used int64, max, avg int, err error) {
    45  	perf := NewPerf()
    46  	return perf.AutoExec(total, tc, peradd, logf, pretimeout, call)
    47  }
    48  
    49  func DoPerfV_(total, tc int, logf string, call func(int) error) (int64, error) {
    50  	return DoAutoPerfV_(total, tc, logf,
    51  		func(idx int, state Perf, callErr error) (int, error) {
    52  			return 1, nil
    53  		}, call)
    54  }
    55  
    56  func DoAutoPerfV_(total, tc int, logf string, increase func(idx int, state Perf, callErr error) (int, error), call func(int) error) (int64, error) {
    57  	perf := NewPerf()
    58  	return perf.Exec(total, tc, logf, increase, call)
    59  }
    60  
    61  type Perf struct {
    62  	Running        int32
    63  	Max            int32
    64  	Avg            int32
    65  	PerUsedMax     int64
    66  	PerUsedMin     int64
    67  	PerUsedAvg     int64
    68  	PerUsedAll     int64
    69  	Done           int64
    70  	Used           int64
    71  	ErrCount       int64
    72  	lck            *sync.RWMutex
    73  	mrunning       bool
    74  	mwait          *sync.WaitGroup
    75  	stdout, stderr *os.File
    76  	ShowState      bool `json:"-"`
    77  }
    78  
    79  func NewPerf() *Perf {
    80  	return &Perf{
    81  		lck:   &sync.RWMutex{},
    82  		mwait: &sync.WaitGroup{},
    83  	}
    84  }
    85  
    86  func (p *Perf) String() string {
    87  	return fmt.Sprintf("Used:%v,Done:%v,Error:%v,Running:%v,Max:%v,Avg:%v,PerMax:%v,PerMin:%v,PerAvg:%v",
    88  		p.Used, p.Done, p.ErrCount, p.Running, p.Max, p.Avg, p.PerUsedMax, p.PerUsedMin, p.PerUsedAvg)
    89  }
    90  
    91  func (p *Perf) AutoExec(total, tc, peradd int, logf string, pretimeout int64, call func(int) error) (used int64, max, avg int, err error) {
    92  	used, err = p.Exec(total, tc, logf,
    93  		func(idx int, state Perf, callErr error) (int, error) {
    94  			beg := xtime.Now()
    95  			if callErr == nil {
    96  				if xtime.Now()-beg < pretimeout {
    97  					return peradd, nil
    98  				}
    99  				if int(state.Running) < tc {
   100  					return 1, nil
   101  				}
   102  				return 0, nil
   103  			} else if IsFullError(callErr) {
   104  				atomic.AddInt64(&p.ErrCount, 1)
   105  				if int(state.Running) < tc {
   106  					return 1, nil
   107  				}
   108  				return 0, nil
   109  			} else {
   110  				return 0, callErr
   111  			}
   112  		}, call)
   113  	max, avg = int(p.Max), int(p.Avg)
   114  	return
   115  }
   116  
   117  func (p *Perf) perdone(perused int64) {
   118  	p.lck.Lock()
   119  	defer p.lck.Unlock()
   120  	p.Done++
   121  	p.PerUsedAll += perused
   122  	p.PerUsedAvg = p.PerUsedAll / p.Done
   123  	if p.PerUsedMax < perused {
   124  		p.PerUsedMax = perused
   125  	}
   126  	if p.PerUsedMin == 0 || p.PerUsedMin > perused {
   127  		p.PerUsedMin = perused
   128  	}
   129  }
   130  func (p *Perf) monitor() {
   131  	var allrunning, allc int32
   132  	p.mrunning = true
   133  	p.mwait.Add(1)
   134  	beg := xtime.Now()
   135  	for p.mrunning {
   136  		running := p.Running
   137  		allrunning += running
   138  		allc++
   139  		p.Avg = allrunning / allc
   140  		if p.Max < running {
   141  			p.Max = running
   142  		}
   143  		p.Used = xtime.Now() - beg
   144  		if p.ShowState {
   145  			fmt.Fprintf(p.stdout, "State:%v\n", p)
   146  		}
   147  		time.Sleep(time.Second)
   148  	}
   149  	p.mwait.Done()
   150  }
   151  
   152  func (p *Perf) Exec(total, tc int, logf string, increase func(idx int, state Perf, err error) (int, error), call func(int) error) (int64, error) {
   153  	p.stdout, p.stderr = os.Stdout, os.Stderr
   154  	if len(logf) > 0 {
   155  		f, err := os.OpenFile(logf, os.O_APPEND|os.O_CREATE|os.O_WRONLY, os.ModePerm)
   156  		if err != nil {
   157  			return 0, err
   158  		}
   159  		os.Stdout = f
   160  		os.Stderr = f
   161  		// log.SetWriter(f)
   162  	}
   163  	if tc > total {
   164  		tc = total
   165  	}
   166  	go p.monitor()
   167  	ws := sync.WaitGroup{}
   168  	// ws.Add(total)
   169  	beg := xtime.Now()
   170  	var tidx_ int32 = 0
   171  	var run_call func(int)
   172  	var run_next func(int, error)
   173  	var err error = nil
   174  	run_call = func(v int) {
   175  		perbeg := xtime.Now()
   176  		terr := call(v)
   177  		atomic.AddInt32(&p.Running, -1)
   178  		perused := xtime.Now() - perbeg
   179  		if terr == nil {
   180  			p.perdone(perused)
   181  		}
   182  		run_next(v, terr)
   183  		ws.Done()
   184  	}
   185  	var increaselck = sync.RWMutex{}
   186  	run_next = func(v int, callErr error) {
   187  		increaselck.Lock()
   188  		defer increaselck.Unlock()
   189  		nc, terr := increase(v, *p, callErr)
   190  		if terr != nil {
   191  			err = terr
   192  			return
   193  		}
   194  		for i := 0; i < nc; i++ {
   195  			ridx := int(atomic.AddInt32(&tidx_, 1))
   196  			if ridx >= total {
   197  				break
   198  			}
   199  			ws.Add(1)
   200  			atomic.AddInt32(&p.Running, 1)
   201  			go run_call(ridx)
   202  		}
   203  	}
   204  	atomic.AddInt32(&tidx_, int32(tc-1))
   205  	for i := 0; i < tc; i++ {
   206  		ws.Add(1)
   207  		atomic.AddInt32(&p.Running, 1)
   208  		go run_call(i)
   209  	}
   210  	ws.Wait()
   211  	end := xtime.Now()
   212  	if len(logf) > 0 {
   213  		os.Stdout.Close()
   214  		os.Stdout = p.stdout
   215  		os.Stderr = p.stderr
   216  		// log.SetWriter(os.Stdout)
   217  	}
   218  	p.mrunning = false
   219  	p.mwait.Wait()
   220  	return end - beg, err
   221  }