/
go-loadtest.go
248 lines (218 loc) · 6.74 KB
/
go-loadtest.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
package main
// @author Robin Verlangen
// Web application loadtester written in Go
import (
"flag"
"log"
"strings"
"strconv"
"time"
"runtime"
"net"
"net/http"
"math/rand"
"sync"
)
// Variables
var urlsIn string
var concurrency int
var requests int
var urls map[string]int
var urlQueue chan string
var finished chan bool
var timeoutTime int
var waitToFinish bool
var reportInterval int
var cpuCount int = runtime.NumCPU()
var startTime time.Time
var endTime time.Time
var elapsedTime time.Duration
var totalTimeSpend int64
var totalTimeSpendMux sync.Mutex
var minTimeSpend int64 = int64(^uint64(0) >> 1) // Max int
var minTimeSpendMux sync.Mutex
var maxTimeSpend int64 = -(minTimeSpend - 1) // Min int
var maxTimeSpendMux sync.Mutex
// Counters
var startCounter int = int(0)
var doneCounter int = int(0)
var doneCounterMux sync.Mutex
var errorCounter int = int(0)
var errorCounterMux sync.Mutex
// Init settings
func init() {
flag.StringVar(&urlsIn, "urls", "", "Urls to benchmark")
flag.IntVar(&concurrency, "c", 8*cpuCount, "Concurrency")
flag.IntVar(&requests, "n", 10000, "Amount of requests")
flag.IntVar(&timeoutTime, "t", 10, "Timeout in seconds")
flag.Parse()
}
// Requester
func requester() {
go func() {
// Client
transport := &http.Transport{
Dial: func(netw, addr string) (net.Conn, error) {
deadline := time.Now().Add(time.Duration(timeoutTime) * time.Second)
c, err := net.DialTimeout(netw, addr, time.Second)
if err != nil {
return nil, err
}
c.SetDeadline(deadline)
return c, nil
}}
httpclient := &http.Client{Transport: transport}
for {
var url string
url = <- urlQueue
// Prepare url
url = parseUrl(url)
// Start timer
var timeA time.Time = time.Now()
// Request
resp, err := httpclient.Get(url)
// Time spend
var dt int64 = time.Since(timeA).Nanoseconds()
totalTimeSpendMux.Lock()
totalTimeSpend += dt
totalTimeSpendMux.Unlock()
// Min, max
if dt < minTimeSpend {
minTimeSpendMux.Lock()
minTimeSpend = dt
minTimeSpendMux.Unlock()
}
if dt > maxTimeSpend {
maxTimeSpendMux.Lock()
maxTimeSpend = dt
maxTimeSpendMux.Unlock()
}
// Validate error
if err != nil {
// Count error
errorCounterMux.Lock()
errorCounter++
errorCounterMux.Unlock()
} else {
defer resp.Body.Close()
}
// Count finished
doneCounterMux.Lock()
doneCounter++
if doneCounter % reportInterval == 0 {
printProgress()
}
// Done?
if waitToFinish && len(urlQueue) == 0 && doneCounter >= startCounter {
elapsedTime = time.Since(startTime)
endTime = time.Now()
doneCounterMux.Unlock()
finished <- true
} else {
doneCounterMux.Unlock()
}
}
}()
}
// Progress
func printProgress() {
var perc float32 = float32(doneCounter) / float32(requests) * float32(100)
log.Printf("Finished %d requests (%3.1f%%)", doneCounter, perc)
}
// Summary
func printSummary() {
//var dt float64 = float64(endTime.Nanosecond() - startTime.Nanosecond())
log.Printf("----- SUMMARY -----")
log.Printf("Total %d requests", doneCounter)
log.Printf("Successful %d requests", doneCounter - errorCounter)
log.Printf("Failed %d requests", errorCounter)
log.Printf("Took %s in total", elapsedTime)
log.Printf("Time per request %f ms (mean)", float64(totalTimeSpend) / float64(requests) / float64(1000000))
log.Printf("Time per request %f ms (mean, concurrent)", float64(elapsedTime.Nanoseconds()) / float64(requests) / float64(1000000))
log.Printf("Time longest request %f ms", float64(maxTimeSpend) / float64(1000000))
log.Printf("Time shortest request %f ms", float64(minTimeSpend) / float64(1000000))
log.Printf("-------------------")
}
// Parse url
func parseUrl(url string) string {
url = strings.Replace(url, "{RAND_INT}", strconv.Itoa(rand.Int()), -1)
return url
}
// Url producer
func urlProducer() {
go func() {
// Calculate total
var totalWeight = 0
for _,weight := range urls {
totalWeight += weight
}
log.Printf("%d total weight\n", totalWeight)
// Start populating
for {
// Spawn urls with regard to weighting
for url,weight := range urls {
var chance = totalWeight / weight
for i := 0; i < chance; i++ {
urlQueue <- url
startCounter++
// Stop if we have enough
if startCounter >= requests {
break
}
}
}
// Stop when we have enough populated
if startCounter >= requests {
waitToFinish = true
//log.Printf("%d request are enqueued\n", requests)
break
}
}
}()
}
// Info
func printInfo() {
log.Println("Starting go-loadtest")
log.Println("Documentation: https://github.com/RobinUS2/go-loadtest")
log.Println("-------------------")
}
// Main
func main() {
// Info
printInfo()
// Queue
urlQueue = make(chan string, requests)
finished = make(chan bool, 1)
waitToFinish = false
reportInterval = requests / 10
// Use all cores
runtime.GOMAXPROCS(cpuCount)
// Parse conf
urls = make(map[string]int)
splits := strings.Split(urlsIn, ";")
for _,element := range splits {
elmSplit := strings.SplitN(element, ":", 2)
weight, _ := strconv.Atoi(elmSplit[0])
urls[elmSplit[1]] = weight
}
// Settings
log.Printf("Located %d urls\n", len(urls))
log.Printf("Concurrency is %d client(s)\n", concurrency)
log.Printf("Amount of requests %d\n", requests)
log.Printf("Maximum response time %d second(s)\n", timeoutTime)
// Start
log.Println("Starting loadtest")
log.Println("-------------------")
// Filling queue
urlProducer()
// Start requesters
startTime = time.Now()
for i := 0; i < concurrency; i++ {
requester()
}
// Wait
<-finished
printProgress()
printSummary()
log.Println("Done go-loadtest")
}