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

     1  package xtest
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"log"
     9  	"math"
    10  	"net/http"
    11  	"os"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"sync/atomic"
    17  	"time"
    18  
    19  	"github.com/codingeasygo/util/xtime"
    20  )
    21  
    22  func AutoExec2(logf string, precall func(state Perf2, ridx uint64, used int64, rerr error) (uint64, error), call func(uint64) error) (used int64, max, avg uint64, err error) {
    23  	var total, tc, peradd, runmax uint64 = 1000000, 100, 2, 100000
    24  	var pretimeout int64 = 1000
    25  	total, err = strconv.ParseUint(os.Getenv("PERF_TOTOAL"), 10, 64)
    26  	if err != nil {
    27  		total = 1000000
    28  	}
    29  	tc, err = strconv.ParseUint(os.Getenv("PERF_TC"), 10, 64)
    30  	if err != nil {
    31  		tc = 100
    32  	}
    33  	peradd, err = strconv.ParseUint(os.Getenv("PERF_PERADD"), 10, 64)
    34  	if err != nil {
    35  		peradd = 2
    36  	}
    37  	pretimeout, err = strconv.ParseInt(os.Getenv("PERF_TIMEOUT"), 10, 64)
    38  	if err != nil {
    39  		pretimeout = 1000
    40  	}
    41  	runmax, err = strconv.ParseUint(os.Getenv("PERF_MAX"), 10, 64)
    42  	if err != nil {
    43  		runmax = 100000
    44  	}
    45  	fmt.Printf("AutoExec:\n\ttotal:%v,tc:%v,peradd:%v,timeout:%v\n\n", total, tc, peradd, pretimeout)
    46  	perf := NewPerf2()
    47  	perf.ShowState = true
    48  	perf.RunninMax = runmax
    49  	perf.Timeout = pretimeout
    50  	// perf.ExternalStatus = func() string {
    51  	// 	mgo.Plck.Lock()
    52  	// 	defer mgo.Plck.Unlock()
    53  	// 	return fmt.Sprintf(",%v,%v", len(mgo.Pending), mgo.Donc)
    54  	// }
    55  	return perf.AutoExec(total, tc, peradd, logf, precall, call)
    56  }
    57  
    58  type Perf2 struct {
    59  	Running        int64
    60  	Max            uint64
    61  	Avg            uint64
    62  	PerUsed        map[float64]int64
    63  	PerUsedMax     int64
    64  	PerUsedMin     int64
    65  	PerUsedAvg     int64
    66  	PerUsedAll     int64
    67  	Done           int64
    68  	Errc           int64
    69  	Used           int64
    70  	lck            *sync.RWMutex
    71  	mrunning       bool
    72  	mwait          *sync.WaitGroup
    73  	stdout, stderr *os.File
    74  	ShowState      bool `json:"-"`
    75  	IncreaseDelay  int64
    76  	ExternalStatus func() string
    77  	RunninMax      uint64
    78  	timeouted      uint64
    79  	fulled         uint64
    80  	Timeout        int64
    81  }
    82  
    83  func NewPerf2() *Perf2 {
    84  	return &Perf2{
    85  		lck:            &sync.RWMutex{},
    86  		mwait:          &sync.WaitGroup{},
    87  		IncreaseDelay:  200,
    88  		ExternalStatus: func() string { return "" },
    89  		RunninMax:      8000,
    90  		PerUsed:        map[float64]int64{},
    91  	}
    92  }
    93  
    94  type DisVal []string
    95  
    96  func (d DisVal) Len() int {
    97  	return len(d)
    98  }
    99  
   100  func (d DisVal) Swap(i, j int) {
   101  	d[i], d[j] = d[j], d[i]
   102  }
   103  
   104  func (d DisVal) Less(i, j int) bool {
   105  	vali, _ := strconv.ParseFloat(strings.Split(d[i], ":")[0], 64)
   106  	valj, _ := strconv.ParseFloat(strings.Split(d[j], ":")[0], 64)
   107  	return vali < valj
   108  }
   109  
   110  func (p *Perf2) String() string {
   111  	p.lck.Lock()
   112  	dis := DisVal{}
   113  	for d, v := range p.PerUsed {
   114  		dis = append(dis, fmt.Sprintf("%v:%v%%", d, int64(100*float64(v)/float64(p.Done))))
   115  	}
   116  	p.lck.Unlock()
   117  	sort.Sort(dis)
   118  	alldis := strings.Join(dis, ", ")
   119  	external := p.ExternalStatus()
   120  	if len(external) > 0 {
   121  		alldis += "\n  " + external
   122  	}
   123  	// alldis += "\n  " + fmt.Sprintf("Max:%v", mgo.UserLockPending2.Max)
   124  	p.NotifyReal()
   125  	return fmt.Sprintf("Used:%v,Done:%v,Errc:%v,TPS:%v,Running:%v,Max:%v,Avg:%v,PerMax:%v,PerMin:%v,PerAvg:%v,Timeout:%v,Fullc:%v\n  %v",
   126  		p.Used, p.Done, p.Errc, p.Done*1000/p.Used, p.Running, p.Max, p.Avg, p.PerUsedMax, p.PerUsedMin, p.PerUsedAvg, p.timeouted, p.fulled, alldis)
   127  }
   128  
   129  func (p *Perf2) NotifyReal() {
   130  	realURI := os.Getenv("REAL_URI")
   131  	if len(realURI) > 0 {
   132  		host := os.Getenv("HOSTNAME")
   133  		host = strings.Split(host, ".")[0]
   134  		bys, _ := json.Marshal(map[string]map[string]interface{}{
   135  			host: {
   136  				"Used":       p.Used,
   137  				"Done":       p.Done,
   138  				"Errc":       p.Errc,
   139  				"TPS":        p.Done * 1000 / p.Used,
   140  				"Running":    p.Running,
   141  				"Max":        p.Max,
   142  				"Avg":        p.Avg,
   143  				"PerUsedMax": p.PerUsedMax,
   144  				"PerUsedAvg": p.PerUsedAvg,
   145  				"Timeout":    p.timeouted,
   146  				"Fullc":      p.fulled,
   147  			},
   148  		})
   149  		resp, err := http.Post(realURI, "application/json", bytes.NewBuffer(bys))
   150  		if err == nil {
   151  			ioutil.ReadAll(resp.Body)
   152  		}
   153  	}
   154  }
   155  
   156  func (p *Perf2) AutoExec(total, tc, peradd uint64, logf string, precall func(state Perf2, ridx uint64, used int64, rerr error) (uint64, error), call func(uint64) error) (used int64, max, avg uint64, err error) {
   157  	// percallc := 1
   158  	used, err = p.Exec(total, tc, logf,
   159  		func(state Perf2, ridx uint64, used int64, rerr error) (uint64, error) {
   160  			// time.Sleep(time.Second)
   161  			_, terr := precall(state, ridx, used, rerr)
   162  			if IsFullError(terr) {
   163  				atomic.AddUint64(&p.fulled, 1)
   164  				if uint64(p.Running) < tc {
   165  					return 1, nil
   166  				}
   167  				return 0, nil
   168  			}
   169  			if terr != nil {
   170  				return 0, terr
   171  			}
   172  			if p.Timeout > 0 && used >= p.Timeout {
   173  				atomic.AddUint64(&p.timeouted, 1)
   174  				if uint64(p.Running) < tc {
   175  					return 1, nil
   176  				}
   177  				return 0, nil
   178  			}
   179  			if state.Running > int64(p.RunninMax) {
   180  				return 0, nil
   181  			}
   182  			return peradd, nil
   183  			// beg := utils.Now()
   184  			// terr := precall(idx, state)
   185  			// if terr == nil {
   186  			// 	if state.Running >= p.RunninMax {
   187  			// 		return 0, nil
   188  			// 	}
   189  			// 	pretimeout := utils.Now()-beg < pretimeout
   190  			// 	if pretimeout {
   191  			// 		percallc++
   192  			// 		nadd := peradd*percallc - int(state.Running)
   193  			// 		return nadd, nil
   194  			// 	}
   195  			// 	p.pretimeouted = pretimeout
   196  			// 	if int(state.Running) < tc {
   197  			// 		return 1, nil
   198  			// 	}
   199  			// 	return 0, nil
   200  			// } else if terr == FullError {
   201  			// 	return 0, nil
   202  			// } else {
   203  			// 	return 0, terr
   204  			// }
   205  		}, call)
   206  
   207  	max, avg = p.Max, p.Avg
   208  	fmt.Printf(`
   209  		%v
   210  		------Done------
   211  		- total:%v
   212  		- max:%v
   213  		- avg:%v
   214  		- used:%v
   215  		- error:%v
   216  		----------------------
   217  		%v`, p.String(), total, max, avg, used, err, "\n")
   218  	return
   219  }
   220  
   221  func (p *Perf2) perdone(perused int64) {
   222  	p.lck.Lock()
   223  	defer p.lck.Unlock()
   224  	p.Done++
   225  	p.PerUsedAll += perused
   226  	p.PerUsedAvg = p.PerUsedAll / p.Done
   227  	if p.PerUsedMax < perused {
   228  		p.PerUsedMax = perused
   229  	}
   230  	if p.PerUsedMin == 0 || p.PerUsedMin > perused {
   231  		p.PerUsedMin = perused
   232  	}
   233  	if p.Timeout > 0 {
   234  		multiple := float64(perused) / float64(p.Timeout)
   235  		if multiple <= 0.25 {
   236  			p.PerUsed[0.25]++
   237  		} else if multiple <= 0.5 {
   238  			p.PerUsed[0.5]++
   239  		} else if multiple <= 0.75 {
   240  			p.PerUsed[0.75]++
   241  		} else if multiple <= 1 {
   242  			p.PerUsed[1]++
   243  		} else if multiple <= 1.5 {
   244  			p.PerUsed[1.5]++
   245  		} else if multiple <= 2 {
   246  			p.PerUsed[2]++
   247  		} else {
   248  			floatn := (math.Sqrt(8*multiple+1) - 1) / 2
   249  			n := float64(int64(floatn))
   250  			if n < floatn {
   251  				n++
   252  			}
   253  			p.PerUsed[(n-1)*(n+2)/2+1]++
   254  		}
   255  	}
   256  }
   257  func (p *Perf2) monitor() {
   258  	var allrunning, allc uint64
   259  	p.mrunning = true
   260  	p.mwait.Add(1)
   261  	defer p.mwait.Done()
   262  
   263  	beg := xtime.Now()
   264  	for p.mrunning {
   265  		running := uint64(p.Running)
   266  		allrunning += running
   267  		allc++
   268  		p.Avg = allrunning / allc
   269  		if p.Max < running {
   270  			p.Max = running
   271  		}
   272  		p.Used = xtime.Now() - beg
   273  		if p.ShowState {
   274  			fmt.Fprintf(p.stdout, "->%v\n", p)
   275  		}
   276  		time.Sleep(time.Second)
   277  	}
   278  }
   279  
   280  func (p *Perf2) Exec(total, tc uint64, logf string, increase func(state Perf2, ridx uint64, used int64, rerr error) (uint64, error), call func(uint64) error) (int64, error) {
   281  	p.stdout, p.stderr = os.Stdout, os.Stderr
   282  	if len(logf) > 0 {
   283  		f, err := os.OpenFile(logf, os.O_APPEND|os.O_CREATE|os.O_WRONLY, os.ModePerm)
   284  		if err != nil {
   285  			return 0, err
   286  		}
   287  		os.Stdout = f
   288  		os.Stderr = f
   289  		log.SetOutput(f)
   290  	}
   291  	if tc > total {
   292  		tc = total
   293  	}
   294  	go p.monitor()
   295  	ws := sync.WaitGroup{}
   296  	// ws.Add(total)
   297  	beg := xtime.Now()
   298  	var tidx_ uint64 = 0
   299  	var run_call func(ridx uint64)
   300  	var run_next func(ridx uint64, used int64, rerr error)
   301  	var err error = nil
   302  	run_call = func(ridx uint64) {
   303  		defer ws.Done()
   304  
   305  		perbeg := xtime.Now()
   306  		terr := call(ridx)
   307  		perused := xtime.Now() - perbeg
   308  		if terr == nil {
   309  			p.perdone(perused)
   310  		} else {
   311  			atomic.AddInt64(&p.Errc, 1)
   312  		}
   313  		run_next(ridx, perused, terr)
   314  		atomic.AddInt64(&p.Running, -1)
   315  
   316  	}
   317  	// increaseLck := sync.RWMutex{}
   318  	run_next = func(ridx uint64, used int64, rerr error) {
   319  		// increaseLck.Lock()
   320  		nc, terr := increase(*p, ridx, used, rerr)
   321  		// increaseLck.Unlock()
   322  		if terr != nil {
   323  			err = terr
   324  			return
   325  		}
   326  		for i := uint64(0); i < nc; i++ {
   327  			ridx := atomic.AddUint64(&tidx_, 1)
   328  			if ridx >= total {
   329  				break
   330  			}
   331  			ws.Add(1)
   332  			atomic.AddInt64(&p.Running, 1)
   333  			go run_call(ridx)
   334  		}
   335  	}
   336  	atomic.AddUint64(&tidx_, tc-1)
   337  	for i := uint64(0); i < tc; i++ {
   338  		ws.Add(1)
   339  		atomic.AddInt64(&p.Running, 1)
   340  		go run_call(i)
   341  	}
   342  	// for err == nil && tidx_ < int32(total) {
   343  	// 	time.Sleep(time.Duration(p.IncreaseDelay) * time.Millisecond)
   344  	// 	run_next(0)
   345  	// }
   346  	ws.Wait()
   347  	end := xtime.Now()
   348  	if len(logf) > 0 {
   349  		os.Stdout.Close()
   350  		os.Stdout = p.stdout
   351  		os.Stderr = p.stderr
   352  		log.SetOutput(os.Stdout)
   353  	}
   354  	p.mrunning = false
   355  	p.mwait.Wait()
   356  	return end - beg, err
   357  }
   358  
   359  func TimeoutSec(state Perf2, ridx uint64, used int64, rerr error) (uint64, error) {
   360  	if rerr != nil {
   361  		return 0, rerr
   362  	}
   363  	if used > 1000 {
   364  		return 0, NewFullError(fmt.Errorf("time out"))
   365  	}
   366  	return 1, nil
   367  }