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 }