/
hammer.go
302 lines (257 loc) · 7.58 KB
/
hammer.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
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
package main
import (
"flag"
"fmt"
"simpleauth"
"io/ioutil"
"log"
"math/rand"
"net/http"
"net/url"
"oauth"
"runtime"
"scenario"
"strings"
"sync/atomic"
"time"
)
// to reduce size of thread, speed up
const SizePerThread = 10000000
// for proxy, debug use only
//var DefaultTransport RoundTripper = &Transport{Proxy: ProxyFromEnvironment}
// Counter will be an atomic, to count the number of request handled
// which will be used to print PPS, etc.
type Counter struct {
totalReq int64 // total # of request
totalResTime int64 // total response time
totalErr int64 // how many error
totalResSlow int64 // how many slow response
totalSend int64
lastSend int64
lastReq int64
client *http.Client
monitor *time.Ticker
// ideally error should be organized by type TODO
throttle <-chan time.Time
}
// init
func (c *Counter) Init() {
// set up HTTP proxy
if proxy != "none" {
proxyUrl, err := url.Parse(proxy)
if err != nil {
log.Fatal(err)
}
c.client = &http.Client{
Transport: &http.Transport{
DisableKeepAlives: false,
MaxIdleConnsPerHost: 200000,
Proxy: http.ProxyURL(proxyUrl),
},
}
} else {
c.client = &http.Client{
Transport: &http.Transport{
DisableKeepAlives: false,
MaxIdleConnsPerHost: 200000,
},
}
}
}
// increase the count and record response time.
func (c *Counter) recordRes(_time int64, method string) {
atomic.AddInt64(&c.totalReq, 1)
atomic.AddInt64(&c.totalResTime, _time)
// if longer that 200ms, it is a slow response
if _time > slowThreshold*1000000 {
atomic.AddInt64(&c.totalResSlow, 1)
log.Println("slow response -> ", float64(_time)/1.0e9, method)
}
}
func (c *Counter) recordError() {
atomic.AddInt64(&c.totalErr, 1)
}
func (c *Counter) recordSend() {
atomic.AddInt64(&c.totalSend, 1)
}
// main goroutine to drive traffic
func (c *Counter) hammer(rg *rand.Rand) {
// before send out, update send count
c.recordSend()
call, err := profile.NextCall(rg)
if err != nil {
log.Println("next call error: ", err)
return
}
req, err := http.NewRequest(call.Method, call.URL, strings.NewReader(call.Body))
// log.Println(call, req, err)
switch auth_method {
case "oauth":
_signature := oauth_client.AuthorizationHeaderWithBodyHash(nil, call.Method, call.URL, url.Values{}, call.Body)
req.Header.Add("Authorization", _signature)
case "simples2s":
// simple authen here
// SignS2SRequest(method string, url string, body string) (string, string, error)
_signature, _timestamp, _ := simple_client.SignS2SRequest(call.Method, call.URL, call.Body)
req.Header.Add("Authorization", "S2S"+" realm=\"modern-war\""+
", signature=\""+_signature+"\", timestamp=\""+_timestamp+"\"")
case "intrenalc2s":
// simple authen here
// SignS2SRequest(method string, url string, body string) (string, string, error)
_signature, _timestamp, _ := simple_client.SignC2SRequest(call.Method, call.URL, call.Body)
req.Header.Add("Authorization", "C2S"+" realm=\"jackpot-slots\""+
", signature=\""+_signature+"\", timestamp=\""+_timestamp+"\"")
}
// Add special haeader for PATCH, PUT and POST
switch call.Method {
case "PATCH", "PUT", "POST":
switch call.Type {
case "REST":
req.Header.Set("Content-Type", "application/json; charset=utf-8")
break
case "WWW":
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
break
}
}
t1 := time.Now().UnixNano()
res, err := c.client.Do(req)
response_time := time.Now().UnixNano() - t1
/*
###
disable reading res.body, no need for our purpose for now,
by doing this, hope we can save more file descriptor.
##
*/
defer req.Body.Close()
switch {
case err != nil:
log.Println("Response Time: ", float64(response_time)/1.0e9, " Erorr: when", call.Method, call.URL, "with error ", err)
c.recordError()
case res.StatusCode >= 400 && res.StatusCode != 409:
log.Println("Got error code --> ", res.Status, "for call ", call.Method, " ", call.URL)
c.recordError()
default:
// only do successful response here
defer res.Body.Close()
c.recordRes(response_time, call.URL)
data, _ := ioutil.ReadAll(res.Body)
if call.CallBack == nil && !debug {
} else {
if res.StatusCode == 409 {
log.Println("Http 409 Res Body : ", string(data))
}
if debug {
log.Println("Req : ", call.Method, call.URL)
if auth_method != "none" {
log.Println("Authorization: ", string(req.Header.Get("Authorization")))
}
log.Println("Req Body : ", call.Body)
log.Println("Response: ", res.Status)
log.Println("Res Body : ", string(data))
}
if call.CallBack != nil {
call.CallBack(call.SePoint, scenario.NEXT, data)
}
}
}
}
func (c *Counter) monitorHammer() {
sps := c.totalSend - c.lastSend
pps := c.totalReq - c.lastReq
backlog := c.totalSend - c.totalReq - c.totalErr
atomic.StoreInt64(&c.lastReq, c.totalReq)
atomic.StoreInt64(&c.lastSend, c.totalSend)
avgT := float64(c.totalResTime) / (float64(c.totalReq) * 1.0e9)
log.Println(
" total: ", fmt.Sprintf("%4d", c.totalSend),
" req/s: ", fmt.Sprintf("%4d", sps),
" res/s: ", fmt.Sprintf("%4d", pps),
" avg: ", fmt.Sprintf("%2.4f", avgT),
" pending: ", backlog,
" err:", c.totalErr,
"|", fmt.Sprintf("%2.2f%s", (float64(c.totalErr)*100.0/float64(c.totalErr+c.totalReq)), "%"),
" slow: ", fmt.Sprintf("%2.2f%s", (float64(c.totalResSlow)*100.0/float64(c.totalReq)), "%"),
profile.CustomizedReport())
}
func (c *Counter) launch(rps int64) {
_p := time.Duration(rps)
_interval := 1000000000.0 / _p
c.throttle = time.Tick(_interval * time.Nanosecond)
go func() {
i := 0
for {
if i == len(rands) {
i = 0
}
<-c.throttle
go c.hammer(rands[i])
i++
}
}()
c.monitor = time.NewTicker(time.Second)
go func() {
for {
<-c.monitor.C // rate limit for monitor routine
go c.monitorHammer()
}
}()
}
// init the program from command line
var (
rps int64
profileFile string
profileType string
slowThreshold int64
debug bool
auth_method string
sessionAmount int
proxy string
// profile
profile scenario.Profile
// rands
rands []*rand.Rand
simple_client = new(simpleauth.Client)
oauth_client = new(oauth.Client)
)
func init() {
flag.Int64Var(&rps, "rps", 500, "Set Request Per Second")
flag.StringVar(&profileFile, "profile", "", "The path to the traffic profile")
flag.Int64Var(&slowThreshold, "threshold", 200, "Set slowness standard (in millisecond)")
flag.StringVar(&profileType, "type", "default", "Profile type (default|session|your session type)")
flag.BoolVar(&debug, "debug", false, "debug flag (true|false)")
flag.StringVar(&auth_method, "auth", "none", "Set authorization flag (oauth|simple(c|s)2s|none)")
flag.IntVar(&sessionAmount, "size", 100, "session amount")
flag.StringVar(&proxy, "proxy", "none", "Set HTTP proxy (need to specify scheme. e.g. http://127.0.0.1:8888)")
simple_client.C2S_Secret = "----"
simple_client.S2S_Secret = "----"
}
// main func
func main() {
flag.Parse()
NCPU := runtime.NumCPU()
runtime.GOMAXPROCS(1)
// to speed up
rands = make([]*rand.Rand, NCPU)
for i, _ := range rands {
s := rand.NewSource(time.Now().UnixNano())
rands[i] = rand.New(s)
}
log.Println("cpu number -> ", NCPU)
log.Println("rps -> ", rps)
log.Println("slow threshold -> ", slowThreshold, "ms")
log.Println("profile type -> ", profileType)
log.Println("Proxy -> ", proxy)
profile, _ = scenario.New(profileType, sessionAmount)
if profileFile != "" {
profile.InitFromFile(profileFile)
} else {
profile.InitFromCode()
}
rand.Seed(time.Now().UnixNano())
counter := new(Counter)
counter.Init()
go counter.launch(rps)
var input string
fmt.Scanln(&input)
}