2021-06-15 12:21:02 +00:00
package main
import (
"fmt"
"io/ioutil"
2021-06-26 13:40:28 +00:00
"net"
2021-06-15 12:21:02 +00:00
"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 ( )
2021-07-03 08:07:54 +00:00
body = kingpin . Flag ( "body" , "HTTP request body, if start the body with @, the rest should be a filename to read" ) . Short ( 'b' ) . String ( )
2021-06-15 12:21:02 +00:00
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-27 02:41:41 +00:00
cert = kingpin . Flag ( "cert" , "Path to the client's TLS Certificate" ) . ExistingFile ( )
key = kingpin . Flag ( "key" , "Path to the client's TLS Certificate Private Key" ) . ExistingFile ( )
insecure = kingpin . Flag ( "insecure" , "Controls whether a client verifies the server's certificate chain and host name" ) . Short ( 'k' ) . Bool ( )
2021-06-15 12:21:02 +00:00
2021-06-26 13:40:28 +00:00
chartsListenAddr = kingpin . Flag ( "listen" , "Listen addr to serve Web UI" ) . Default ( ":18888" ) . String ( )
2021-06-15 12:21:02 +00:00
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 ( )
2021-06-27 02:41:41 +00:00
autoOpenBrowser = kingpin . Flag ( "auto-open-browser" , "Specify whether auto open browser to show Web charts" ) . Bool ( )
2021-11-23 02:13:07 +00:00
clean = kingpin . Flag ( "clean" , "Clean the histogram bar once its finished. Default is true" ) . Default ( "true" ) . NegatableBool ( )
2021-06-27 02:41:41 +00:00
2021-06-15 12:21:02 +00:00
url = kingpin . Arg ( "url" , "request url" ) . Required ( ) . String ( )
)
func errAndExit ( msg string ) {
2021-06-27 02:41:41 +00:00
fmt . Fprintln ( os . Stderr , "plow: " + msg )
2021-06-15 12:21:02 +00:00
os . Exit ( 1 )
}
2021-06-27 02:41:41 +00:00
var CompactUsageTemplate = ` { { define "FormatCommand" - } }
{ { if . FlagSummary } } { { . FlagSummary } } { { end - } }
{ { range . Args } } { { if not . Required } } [ { { end } } < { { . Name } } > { { if . Value | IsCumulative } } ... { { end } } { { if not . Required } } ] { { end } } { { end - } }
{ { end - } }
{ { define "FormatCommandList" - } }
{ { range . - } }
{ { if not . Hidden - } }
{ { . Depth | Indent } } { { . Name } } { { if . Default } } * { { end } } { { template "FormatCommand" . } }
{ { end - } }
{ { template "FormatCommandList" . Commands - } }
{ { end - } }
{ { end - } }
{ { define "FormatUsage" - } }
{ { template "FormatCommand" . } } { { if . Commands } } < command > [ < args > ... ] { { end } }
{ { if . Help } }
{ { . Help | Wrap 0 - } }
{ { end - } }
{ { end - } }
{ { if . Context . SelectedCommand - } }
{ { T "usage:" } } { { . App . Name } } { { template "FormatUsage" . Context . SelectedCommand } }
{ { else - } }
{ { T "usage:" } } { { . App . Name } } { { template "FormatUsage" . App } }
{ { end - } }
Examples :
2021-06-15 12:21:02 +00:00
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
2021-06-27 02:41:41 +00:00
{ { if . Context . Flags - } }
{ { T "Flags:" } }
{ { . Context . Flags | FlagsToTwoColumns | FormatTwoColumns } }
Flags default values also read from env PLOW_SOME_FLAG , such as PLOW_TIMEOUT = 5 s equals to -- timeout = 5 s
{ { end - } }
{ { if . Context . Args - } }
{ { T "Args:" } }
{ { . Context . Args | ArgsToTwoColumns | FormatTwoColumns } }
{ { end - } }
{ { if . Context . SelectedCommand - } }
{ { if . Context . SelectedCommand . Commands - } }
{ { T "Commands:" } }
{ { . Context . SelectedCommand } }
{ { . Context . SelectedCommand . Commands | CommandsToTwoColumns | FormatTwoColumns } }
{ { end - } }
{ { else if . App . Commands - } }
{ { T "Commands:" } }
{ { . App . Commands | CommandsToTwoColumns | FormatTwoColumns } }
{ { end - } }
2021-06-15 12:21:02 +00:00
`
2021-06-27 02:41:41 +00:00
func main ( ) {
kingpin . UsageTemplate ( CompactUsageTemplate ) .
2021-06-30 11:02:19 +00:00
Version ( "1.1.0" ) .
2021-06-27 02:41:41 +00:00
Author ( "six-ddc@github" ) .
Resolver ( kingpin . PrefixedEnvarResolver ( "PLOW_" , ";" ) ) .
Help = ` A high-performance HTTP benchmarking tool with real-time web UI and terminal displaying `
2021-06-15 12:21:02 +00:00
kingpin . Parse ( )
2021-06-27 02:41:41 +00:00
2021-06-15 12:21:02 +00:00
if * requests >= 0 && * requests < int64 ( * concurrency ) {
errAndExit ( "requests must greater than or equal concurrency" )
return
}
2021-06-27 02:41:41 +00:00
if ( * cert != "" && * key == "" ) || ( * cert == "" && * key != "" ) {
errAndExit ( "must specify cert and key at the same time" )
return
}
2021-06-15 12:21:02 +00:00
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 {
2021-06-27 02:41:41 +00:00
url : * url ,
method : * method ,
headers : * headers ,
bodyBytes : bodyBytes ,
bodyFile : bodyFile ,
certPath : * cert ,
keyPath : * key ,
insecure : * insecure ,
2021-06-15 12:21:02 +00:00
maxConns : * concurrency ,
doTimeout : * timeout ,
readTimeout : * respReadTimeout ,
writeTimeout : * reqWriteTimeout ,
dialTimeout : * dialTimeout ,
socks5Proxy : * socks5 ,
contentType : * contentType ,
host : * host ,
}
2021-06-27 02:41:41 +00:00
requester , err := NewRequester ( * concurrency , * requests , * duration , & clientOpt )
if err != nil {
errAndExit ( err . Error ( ) )
return
}
// description
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
2021-06-27 02:41:41 +00:00
// charts listener
2021-06-26 13:40:28 +00:00
var ln net . Listener
2021-06-23 15:46:54 +00:00
if * chartsListenAddr != "" {
2021-06-26 13:40:28 +00:00
ln , err = net . Listen ( "tcp" , * chartsListenAddr )
if err != nil {
errAndExit ( err . Error ( ) )
return
}
fmt . Printf ( "@ Real-time charts is listening on http://%s\n" , ln . Addr ( ) . String ( ) )
2021-06-23 15:46:54 +00:00
}
fmt . Printf ( "\n" )
2021-06-27 02:41:41 +00:00
// do request
2021-06-15 12:21:02 +00:00
go requester . Run ( )
2021-06-27 02:41:41 +00:00
// metrics collection
2021-06-15 12:21:02 +00:00
report := NewStreamReport ( )
go report . Collect ( requester . RecordChan ( ) )
2021-06-26 13:40:28 +00:00
if ln != nil {
2021-06-27 02:41:41 +00:00
// serve charts data
2021-06-26 13:40:28 +00:00
charts , err := NewCharts ( ln , report . Charts , desc )
2021-06-15 12:21:02 +00:00
if err != nil {
errAndExit ( err . Error ( ) )
return
}
2021-06-27 02:41:41 +00:00
go charts . Serve ( * autoOpenBrowser )
2021-06-15 12:21:02 +00:00
}
2021-06-27 02:41:41 +00:00
// terminal printer
2021-11-23 02:13:07 +00:00
printer := NewPrinter ( * requests , * duration , ! * clean )
2021-06-15 12:21:02 +00:00
printer . PrintLoop ( report . Snapshot , * interval , * seconds , report . Done ( ) )
}