-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathmain.go
284 lines (232 loc) · 7.04 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
package main
import (
"flag"
"fmt"
"log"
"sort"
"sync"
"sync/atomic"
"time"
"github.com/pkg/errors"
"github.com/sergi/go-diff/diffmatchpatch"
"github.com/valyala/fasthttp"
)
var (
ErrWrongPhase = errors.New(`Wrong phase`)
ErrWrongAmmoFile = errors.New(`Cannot parse ammo file`)
ErrResponseDiff = errors.New(`The server response is different than expected`)
)
var (
bytesNull = []byte(`null`)
bytesEmpty = []byte(`<EMPTY>`)
)
type (
Header struct {
Key, Value []byte
}
Request struct {
Skip bool
LineNo int
IsGet bool
URI []byte
Headers []Header
Body []byte
}
Response struct {
Status int
Body []byte
}
Bullet struct {
Request Request
Response Response
}
BenchResult struct {
bulletIdx int
status int
body []byte
dur time.Duration
}
BenchTop struct {
req []byte
dur time.Duration
}
)
var (
argv struct {
hlcupdocsPath string
serverAddr string
filterReq string
filterURI string
phase uint
benchTime time.Duration
concurrent uint
testRun bool
hideFailed bool
allowNulls bool
utf8 bool
bodyDiff bool
tankRps uint
}
maxReqNo int
muMaxReqNo sync.Mutex
bullets []*Bullet
emptyPOSTResponseBody = []byte(`{}`)
)
func init() {
flag.StringVar(&argv.hlcupdocsPath, `hlcupdocs`, `./hlcupdocs/`, `path to hlcupdocs/`)
flag.StringVar(&argv.serverAddr, `addr`, `http://127.0.0.1:80`, `test server address`)
flag.StringVar(&argv.filterReq, `filter`, ``, `regexp for filter requests, i.e. "^458 " or "/accounts/filter/"`)
flag.StringVar(&argv.filterURI, `uri`, ``, `substring for filter requests URI`)
flag.UintVar(&argv.phase, `phase`, 1, `phase number (1, 2, 3)`)
flag.DurationVar(&argv.benchTime, `time`, 10*time.Second, `benchmark duration`)
flag.BoolVar(&argv.testRun, `test`, false, `test run (send every query only once. ignore -time and -concurrent)`)
flag.BoolVar(&argv.hideFailed, `hide-failed`, false, `do not print info about every failed request`)
flag.BoolVar(&argv.allowNulls, `allow-nulls`, false, `allow null in response data`)
flag.BoolVar(&argv.utf8, `utf8`, false, `show request & response bodies in UTF-8 human-readable format`)
flag.BoolVar(&argv.bodyDiff, `diff`, false, `show colorful body diffs instead both variants (red - wrong, green - need)`)
flag.UintVar(&argv.concurrent, `concurrent`, 1, `concurrent users`)
flag.UintVar(&argv.tankRps, `tank`, 0, `run as tank: 0 -> this (rps) for benchmark duration. ignore -concurrent`)
flag.Parse()
}
func main() {
if err := loadData(); err != nil {
log.Fatalln(errors.Wrap(err, `Cannot load data from `+argv.hlcupdocsPath))
}
fmt.Println(`bullets count:`, len(bullets))
benchServer()
}
func benchServer() {
var queries int64
client := &fasthttp.Client{}
mt := time.Now().UnixNano()
var enough, currBullet int64
concurrent := int(argv.concurrent)
if argv.testRun {
fmt.Println(`Start test run`)
concurrent = 1
}
var benchResultsAll benchResult
wg := &sync.WaitGroup{}
if argv.tankRps == 0 {
fmt.Printf("Start %s benchmark in %d concurrent users\n", argv.benchTime, concurrent)
benchResultsAll = make(benchResult, concurrent)
wg.Add(concurrent)
for i := 0; i < concurrent; i++ {
go pifpaf(i, &benchResultsAll, client, &enough, &queries, wg)
}
} else {
currBullet = -1
maxReqNo = 0
fmt.Printf("Start %s benchmark in tank mode 0->%d\n", argv.benchTime, argv.tankRps)
delta := float64(argv.tankRps*uint(time.Second)) / float64(argv.benchTime)
benchResultsAll = make(benchResult, 0)
offset := 0
pause := time.Duration(0)
for wrk := float64(0); wrk < float64(argv.tankRps); wrk += delta {
wg.Add(int(wrk))
for i := 0; i < int(wrk); i++ {
benchResultsAll = append(benchResultsAll, nil)
go func(ii int, slp time.Duration) {
time.Sleep(slp)
pifpafTank(ii, &benchResultsAll, client, &queries, &currBullet, wg)
}(offset, pause)
offset++
}
pause += time.Second
}
}
if !argv.testRun {
time.Sleep(argv.benchTime)
atomic.StoreInt64(&enough, 1)
}
wg.Wait()
mt = (time.Now().UnixNano() - mt) / int64(time.Millisecond)
rps := float64(queries) / (float64(mt) / 1000)
fmt.Println(`Done. Check the answers...`)
var benchtop [10]*BenchTop
chtop := make(chan *BenchTop, 100)
var errorsAll int64
wg.Add(len(benchResultsAll))
for i := 0; i < len(benchResultsAll); i++ {
go func(i int) {
defer wg.Done()
var myErrors int64
hideFailed := argv.hideFailed
for _, benchResult := range benchResultsAll[i] {
bullet := bullets[benchResult.bulletIdx]
if benchResult.status != bullet.Response.Status {
if !hideFailed {
bodyReq, bodyRespGot, bodyRespExpect := getReqRespBodies(bullet, &benchResult)
fmt.Printf("REQUEST URI: %s\nREQUEST BODY: %s\n", bullet.Request.URI, bodyReq)
fmt.Printf("STATUS GOT: %d \nSTATUS EXP: %d\n", benchResult.status, bullet.Response.Status)
if argv.bodyDiff {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(string(bodyRespGot), string(bodyRespExpect), false)
fmt.Printf("BODIES DIFF: %s\n\n", dmp.DiffPrettyText(diffs))
} else {
fmt.Printf("BODY GOT: %s\nBODY EXP: %s\n\n", bodyRespGot, bodyRespExpect)
}
}
myErrors++
} else if (bullet.Response.Status == 200) && !equalResponseBodies(benchResult.body, bullet.Response.Body) {
if !hideFailed {
bodyReq, bodyRespGot, bodyRespExpect := getReqRespBodies(bullet, &benchResult)
fmt.Printf("REQUEST URI: %s\nREQUEST BODY: %s\n", bullet.Request.URI, bodyReq)
if argv.bodyDiff {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(string(bodyRespGot), string(bodyRespExpect), false)
fmt.Printf("BODIES DIFF: %s\n\n", dmp.DiffPrettyText(diffs))
} else {
fmt.Printf("BODY GOT: %s\nBODY EXP: %s\n\n", bodyRespGot, bodyRespExpect)
}
}
myErrors++
}
chtop <- &BenchTop{
req: bullet.Request.URI,
dur: benchResult.dur,
}
}
if myErrors > 0 {
atomic.AddInt64(&errorsAll, myErrors)
}
}(i)
}
go func() {
for bt := range chtop {
ln := len(benchtop)
idx := sort.Search(ln, func(i int) bool {
return benchtop[i] == nil || (benchtop[i].dur <= bt.dur)
})
if idx < ln {
copy(benchtop[idx+1:], benchtop[idx:])
benchtop[idx] = bt
}
}
}()
wg.Wait()
close(chtop)
if errorsAll == 0 {
fmt.Println(`All answers is OK`)
} else {
fmt.Printf("%d requests (%.2f%%) failed\n", errorsAll, 100*float64(errorsAll)/float64(queries))
}
fmt.Printf("%d queries in %d ms => %.0f rps\n", queries, mt, rps)
fmt.Println("Top slowest queries:")
for _, top := range benchtop {
if top != nil {
fmt.Printf("%s:%s\n", top.dur, top.req)
}
}
}
func getReqRespBodies(bullet *Bullet, benchResult *BenchResult) (bodyReq, bodyRespGot, bodyRespExpect []byte) {
bodyReq = bullet.Request.Body
bodyRespGot = benchResult.body
bodyRespExpect = bullet.Response.Body
if argv.utf8 {
bodyReq = utf8MixedUnescaped(bodyReq)
bodyRespGot = utf8MixedUnescaped(bodyRespGot)
bodyRespExpect = utf8MixedUnescaped(bodyRespExpect)
}
return
}