forked from valyala/ybc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
197 lines (175 loc) · 5.66 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
// Benchmark tool for go-cdn-booster or any other http/1.1 server
//
// This tool is similar to the well known 'ab' tool. The main difference
// is that this tool issues HTTP/1.1 'Connection: Keep-Alive' requests
// over the limited number of open connections. The number of such connections
// is equivalent to workersCount.
//
// Known limitations:
// * It cannot test HTTP servers without HTTP/1.1 keep-alive connections
// support.
// * Currently it shows only the following stats:
// * time taken for the test
// * Kbytes read - the total size of responses' body
// * qps - average queries per second
// * Kbps - average Kbytes per second received from the server.
package main
import (
"bufio"
"crypto/tls"
"flag"
"fmt"
"io"
"log"
"math/rand"
"net"
"net/url"
"runtime"
"strings"
"sync"
"time"
"github.com/valyala/fasthttp"
"github.com/vharitonsky/iniflags"
)
var (
numCpu = runtime.NumCPU()
filesCount = flag.Int("filesCount", 500, "The number of distinct files to cache. Random query parameter is added to testUrl for generating distinct file urls")
goMaxProcs = flag.Int("goMaxProcs", numCpu, "The number of go procs")
maxPendingRequestsPerConnection = flag.Int("maxPendingRequestsPerConnection", 100, "The maximum number of pending requests per connection to the testUrl")
requestsCount = flag.Int("requestsCount", 100000, "The number of requests to perform")
requestsPerConnectionCount = flag.Int("requestsPerConnectionCount", 100, "The maximum number of requests per connection to the testUrl. This value shouldn't exceed max keepalive requests count set on the server")
testUrl = flag.String("testUrl", "http://localhost:8098/", "Url to test")
workersCount = flag.Int("workersCount", 4*numCpu, "The number of workers")
)
var (
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
}
)
func main() {
iniflags.Parse()
flag.VisitAll(func(f *flag.Flag) {
fmt.Printf("%s=%v\n", f.Name, f.Value)
})
runtime.GOMAXPROCS(*goMaxProcs)
testUri, err := url.Parse(*testUrl)
if err != nil {
log.Fatalf("Error=[%s] when parsing testUrl=[%s]\n", err, *testUrl)
}
ch := make(chan int, 100000)
bytesRead := make([]int64, *workersCount)
wg := &sync.WaitGroup{}
for i := 0; i < *workersCount; i++ {
wg.Add(1)
go worker(ch, wg, testUri, &bytesRead[i])
}
log.Printf("Test started\n")
startTime := time.Now()
for i := 0; i < *requestsCount; i++ {
ch <- 1
}
close(ch)
wg.Wait()
duration := time.Since(startTime)
seconds := float64(duration) / float64(time.Second)
var totalBytesRead int64
for i := 0; i < *workersCount; i++ {
totalBytesRead += bytesRead[i]
}
kbytesRead := float64(totalBytesRead) / float64(1000)
qps := float64(*requestsCount) / seconds
kbps := kbytesRead / seconds
log.Printf("Done\n")
log.Printf("%d requests from %d workers in %s\n", *requestsCount, *workersCount, duration)
log.Printf("%.0f Kbytes read, %.0f qps, %.0f Kbps\n", kbytesRead, qps, kbps)
}
func worker(ch <-chan int, wg *sync.WaitGroup, testUri *url.URL, bytesRead *int64) {
defer wg.Done()
hostPort := testUri.Host
if !strings.Contains(hostPort, ":") {
port := "80"
if testUri.Scheme == "https" {
port = "443"
}
hostPort = net.JoinHostPort(hostPort, port)
}
for {
n := issueRequestsPerConnection(ch, hostPort, testUri)
if n == 0 {
break
}
*bytesRead += n
}
}
func issueRequestsPerConnection(ch <-chan int, hostPort string, testUri *url.URL) int64 {
conn, err := net.Dial("tcp", hostPort)
if err != nil {
log.Fatalf("Error=[%s] when connecting to [%s]\n", err, hostPort)
}
defer conn.Close()
if testUri.Scheme == "https" {
conn = tls.Client(conn, tlsConfig)
if err = conn.(*tls.Conn).Handshake(); err != nil {
log.Fatalf("Error during tls handshake: [%s]\n", err)
}
}
statsChan := make(chan int64)
requestsChan := make(chan int, *maxPendingRequestsPerConnection)
go readResponses(conn, statsChan, requestsChan)
writeRequests(conn, ch, requestsChan, testUri)
close(requestsChan)
return <-statsChan
}
func writeRequests(conn net.Conn, ch <-chan int, requestsChan chan<- int, testUri *url.URL) {
var requestsWritten int
w := bufio.NewWriter(conn)
requestUri := testUri.RequestURI()
delimiter := "?"
if strings.Contains(requestUri, "?") {
delimiter = "&"
}
for _ = range ch {
requestStr := []byte(fmt.Sprintf("GET %s%s%d HTTP/1.1\r\nHost: %s\r\nUser-Agent: go-cdn-booster-bench\r\n\r\n",
requestUri, delimiter, rand.Intn(*filesCount), testUri.Host))
if _, err := w.Write(requestStr); err != nil {
log.Fatalf("Error=[%s] when writing HTTP request [%d] to connection\n", err, requestsWritten)
}
requestsWritten += 1
select {
case requestsChan <- requestsWritten:
default:
if err := w.Flush(); err != nil {
log.Fatalf("Error when flushing requests' buffer: [%s]\n", err)
}
requestsChan <- requestsWritten
}
if requestsWritten == *requestsPerConnectionCount {
break
}
}
if err := w.Flush(); err != nil {
log.Fatalf("Error when flushing requests' buffer: [%s]\n", err)
}
}
var responsePool sync.Pool
func readResponses(r io.Reader, statsChan chan<- int64, requestsChan <-chan int) {
v := responsePool.Get()
if v == nil {
v = &fasthttp.Response{}
}
resp := v.(*fasthttp.Response)
var bytesRead int64
rb := bufio.NewReader(r)
for n := range requestsChan {
err := resp.Read(rb)
if err != nil {
log.Fatalf("Error when reading response %d: [%s]\n", n, err)
}
if resp.StatusCode() != 200 {
log.Fatalf("Unexpected status code for the response %d: [%d]\n", n, resp.StatusCode())
}
bytesRead += int64(len(resp.Body()))
}
statsChan <- bytesRead
responsePool.Put(v)
}