2021-06-15 12:21:02 +00:00
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
2021-06-18 15:26:03 +00:00
"gopkg.in/alecthomas/kingpin.v3-unstable"
2021-06-15 12:21:02 +00:00
)
var (
concurrency = kingpin . Flag ( "concurrency" , "Number of connections to run concurrently" ) . Short ( 'c' ) . Default ( "1" ) . Int ( )
requests = kingpin . Flag ( "requests" , "Number of requests to run" ) . Short ( 'n' ) . Default ( "-1" ) . Int64 ( )
duration = kingpin . Flag ( "duration" , "Duration of test, examples: -d 10s -d 3m" ) . Short ( 'd' ) . PlaceHolder ( "DURATION" ) . Duration ( )
interval = kingpin . Flag ( "interval" , "Print snapshot result every interval, use 0 to print once at the end" ) . Short ( 'i' ) . Default ( "200ms" ) . Duration ( )
seconds = kingpin . Flag ( "seconds" , "Use seconds as time unit to print" ) . Bool ( )
body = kingpin . Flag ( "body" , "HTTP request body, if start the body with @, the rest should be a filename to read" ) . String ( )
stream = kingpin . Flag ( "stream" , "Specify whether to stream file specified by '--body @file' using chunked encoding or to read into memory" ) . Default ( "false" ) . Bool ( )
method = kingpin . Flag ( "method" , "HTTP method" ) . Default ( "GET" ) . Short ( 'm' ) . String ( )
headers = kingpin . Flag ( "header" , "Custom HTTP headers" ) . Short ( 'H' ) . PlaceHolder ( "K:V" ) . Strings ( )
host = kingpin . Flag ( "host" , "Host header" ) . String ( )
contentType = kingpin . Flag ( "content" , "Content-Type header" ) . Short ( 'T' ) . String ( )
2021-06-18 15:26:03 +00:00
chartsListenAddr = kingpin . Flag ( "listen" , "Listen addr to serve Web UI" ) . Default ( ":18888" ) . String ( )
2021-06-15 12:21:02 +00:00
chartsLinkAddr = kingpin . Flag ( "link" , "Link addr used for show Web html and request backend server" ) . Default ( "127.0.0.1:18888" ) . String ( )
timeout = kingpin . Flag ( "timeout" , "Timeout for each http request" ) . PlaceHolder ( "DURATION" ) . Duration ( )
dialTimeout = kingpin . Flag ( "dial-timeout" , "Timeout for dial addr" ) . PlaceHolder ( "DURATION" ) . Duration ( )
reqWriteTimeout = kingpin . Flag ( "req-timeout" , "Timeout for full request writing" ) . PlaceHolder ( "DURATION" ) . Duration ( )
respReadTimeout = kingpin . Flag ( "resp-timeout" , "Timeout for full response reading" ) . PlaceHolder ( "DURATION" ) . Duration ( )
socks5 = kingpin . Flag ( "socks5" , "Socks5 proxy" ) . PlaceHolder ( "ip:port" ) . String ( )
url = kingpin . Arg ( "url" , "request url" ) . Required ( ) . String ( )
)
func errAndExit ( msg string ) {
fmt . Fprintln ( os . Stderr , msg )
os . Exit ( 1 )
}
func main ( ) {
kingpin . UsageTemplate ( kingpin . CompactUsageTemplate ) . Version ( "1.0.0" ) . Author ( "six-ddc@github" )
kingpin . CommandLine . Help = ` A high - performance HTTP benchmarking tool with real - time web UI and terminal displaying
Example :
plow http : //127.0.0.1:8080/ -c 20 -n 100000
plow https : //httpbin.org/post -c 20 -d 5m --body @file.json -T 'application/json' -m POST
`
kingpin . Parse ( )
if * requests >= 0 && * requests < int64 ( * concurrency ) {
errAndExit ( "requests must greater than or equal concurrency" )
return
}
var err error
var bodyBytes [ ] byte
var bodyFile string
if strings . HasPrefix ( * body , "@" ) {
fileName := ( * body ) [ 1 : ]
if _ , err = os . Stat ( fileName ) ; err != nil {
errAndExit ( err . Error ( ) )
return
}
if * stream {
bodyFile = fileName
} else {
bodyBytes , err = ioutil . ReadFile ( fileName )
if err != nil {
errAndExit ( err . Error ( ) )
return
}
}
} else if * body != "" {
bodyBytes = [ ] byte ( * body )
}
clientOpt := ClientOpt {
url : * url ,
method : * method ,
headers : * headers ,
bodyBytes : bodyBytes ,
bodyFile : bodyFile ,
maxConns : * concurrency ,
doTimeout : * timeout ,
readTimeout : * respReadTimeout ,
writeTimeout : * reqWriteTimeout ,
dialTimeout : * dialTimeout ,
socks5Proxy : * socks5 ,
contentType : * contentType ,
host : * host ,
}
2021-06-18 15:26:03 +00:00
var desc string
desc = fmt . Sprintf ( "Benchmarking %s" , * url )
2021-06-15 12:21:02 +00:00
if * requests > 0 {
2021-06-18 15:26:03 +00:00
desc += fmt . Sprintf ( " with %d request(s)" , * requests )
2021-06-15 12:21:02 +00:00
}
if * duration > 0 {
2021-06-18 15:26:03 +00:00
desc += fmt . Sprintf ( " for %s" , duration . String ( ) )
2021-06-15 12:21:02 +00:00
}
2021-06-18 15:26:03 +00:00
desc += fmt . Sprintf ( " using %d connection(s)." , * concurrency )
fmt . Println ( desc )
2021-06-15 12:21:02 +00:00
if * chartsListenAddr != "" {
fmt . Printf ( "> Real-time charts is listening on http://%s/\n" , * chartsLinkAddr )
}
fmt . Printf ( "\n" )
requester , err := NewRequester ( * concurrency , * requests , * duration , & clientOpt )
if err != nil {
errAndExit ( err . Error ( ) )
return
}
go requester . Run ( )
report := NewStreamReport ( )
go report . Collect ( requester . RecordChan ( ) )
if * chartsListenAddr != "" {
2021-06-18 15:26:03 +00:00
charts , err := NewCharts ( * chartsListenAddr , * chartsLinkAddr , report . Charts , desc )
2021-06-15 12:21:02 +00:00
if err != nil {
errAndExit ( err . Error ( ) )
return
}
go charts . Serve ( )
}
printer := NewPrinter ( * requests , * duration )
printer . PrintLoop ( report . Snapshot , * interval , * seconds , report . Done ( ) )
}