Add '--rate' to limit the frequency of requests
This commit is contained in:
parent
514bef9850
commit
e579a5d69e
|
@ -5,7 +5,7 @@
|
||||||
[![GitHub license](https://img.shields.io/github/license/six-ddc/plow.svg)](https://github.com/six-ddc/plow/blob/main/LICENSE)
|
[![GitHub license](https://img.shields.io/github/license/six-ddc/plow.svg)](https://github.com/six-ddc/plow/blob/main/LICENSE)
|
||||||
[![made-with-Go](https://img.shields.io/badge/Made%20with-Go-1f425f.svg)](http://golang.org)
|
[![made-with-Go](https://img.shields.io/badge/Made%20with-Go-1f425f.svg)](http://golang.org)
|
||||||
|
|
||||||
Plow is a HTTP(S) benchmarking tool, written in Golang. It uses
|
Plow is an HTTP(S) benchmarking tool, written in Golang. It uses
|
||||||
excellent [fasthttp](https://github.com/valyala/fasthttp#http-client-comparison-with-nethttp) instead of Go's default
|
excellent [fasthttp](https://github.com/valyala/fasthttp#http-client-comparison-with-nethttp) instead of Go's default
|
||||||
net/http due to its lightning fast performance.
|
net/http due to its lightning fast performance.
|
||||||
|
|
||||||
|
@ -104,6 +104,7 @@ Examples:
|
||||||
Flags:
|
Flags:
|
||||||
--help Show context-sensitive help.
|
--help Show context-sensitive help.
|
||||||
-c, --concurrency=1 Number of connections to run concurrently
|
-c, --concurrency=1 Number of connections to run concurrently
|
||||||
|
--rate=infinity Number of requests per time unit, examples: --rate 50 --rate 10/ms
|
||||||
-n, --requests=-1 Number of requests to run
|
-n, --requests=-1 Number of requests to run
|
||||||
-d, --duration=DURATION Duration of test, examples: -d 10s -d 3m
|
-d, --duration=DURATION Duration of test, examples: -d 10s -d 3m
|
||||||
-i, --interval=200ms Print snapshot result every interval, use 0 to print once at the end
|
-i, --interval=200ms Print snapshot result every interval, use 0 to print once at the end
|
||||||
|
@ -124,8 +125,8 @@ Flags:
|
||||||
--resp-timeout=DURATION Timeout for full response reading
|
--resp-timeout=DURATION Timeout for full response reading
|
||||||
--socks5=ip:port Socks5 proxy
|
--socks5=ip:port Socks5 proxy
|
||||||
--auto-open-browser Specify whether auto open browser to show Web charts
|
--auto-open-browser Specify whether auto open browser to show Web charts
|
||||||
--[no-]summary Only print the summary without realtime reports
|
|
||||||
--[no-]clean Clean the histogram bar once its finished. Default is true
|
--[no-]clean Clean the histogram bar once its finished. Default is true
|
||||||
|
--[no-]summary Only print the summary without realtime reports
|
||||||
--version Show application version.
|
--version Show application version.
|
||||||
|
|
||||||
Flags default values also read from env PLOW_SOME_FLAG, such as PLOW_TIMEOUT=5s equals to --timeout=5s
|
Flags default values also read from env PLOW_SOME_FLAG, such as PLOW_TIMEOUT=5s equals to --timeout=5s
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -13,6 +13,7 @@ require (
|
||||||
github.com/nicksnyder/go-i18n v1.10.1 // indirect
|
github.com/nicksnyder/go-i18n v1.10.1 // indirect
|
||||||
github.com/valyala/fasthttp v1.33.0
|
github.com/valyala/fasthttp v1.33.0
|
||||||
go.uber.org/automaxprocs v1.4.0
|
go.uber.org/automaxprocs v1.4.0
|
||||||
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306
|
||||||
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780
|
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -57,6 +57,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
|
||||||
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 h1:CEBpW6C191eozfEuWdUmIAHn7lwlLxJ7HVdr2e2Tsrw=
|
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 h1:CEBpW6C191eozfEuWdUmIAHn7lwlLxJ7HVdr2e2Tsrw=
|
||||||
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780/go.mod h1:3HH7i1SgMqlzxCcBmUHW657sD4Kvv9sC3HpL3YukzwA=
|
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780/go.mod h1:3HH7i1SgMqlzxCcBmUHW657sD4Kvv9sC3HpL3YukzwA=
|
||||||
|
|
70
main.go
70
main.go
|
@ -2,16 +2,20 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gopkg.in/alecthomas/kingpin.v3-unstable"
|
"gopkg.in/alecthomas/kingpin.v3-unstable"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
concurrency = kingpin.Flag("concurrency", "Number of connections to run concurrently").Short('c').Default("1").Int()
|
concurrency = kingpin.Flag("concurrency", "Number of connections to run concurrently").Short('c').Default("1").Int()
|
||||||
|
reqRate = rateFlag(kingpin.Flag("rate", "Number of requests per time unit, examples: --rate 50 --rate 10/ms").Default("infinity"))
|
||||||
requests = kingpin.Flag("requests", "Number of requests to run").Short('n').Default("-1").Int64()
|
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()
|
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()
|
interval = kingpin.Flag("interval", "Print snapshot result every interval, use 0 to print once at the end").Short('i').Default("200ms").Duration()
|
||||||
|
@ -99,9 +103,71 @@ Examples:
|
||||||
{{end -}}
|
{{end -}}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
type rateFlagValue struct {
|
||||||
|
infinity bool
|
||||||
|
limit rate.Limit
|
||||||
|
v string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *rateFlagValue) Set(v string) error {
|
||||||
|
if v == "infinity" {
|
||||||
|
f.infinity = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
retErr := fmt.Errorf("--rate format %q doesn't match the \"freq/duration\" (i.e. 50/1s)", v)
|
||||||
|
ps := strings.SplitN(v, "/", 2)
|
||||||
|
switch len(ps) {
|
||||||
|
case 1:
|
||||||
|
ps = append(ps, "1s")
|
||||||
|
case 0:
|
||||||
|
return retErr
|
||||||
|
}
|
||||||
|
|
||||||
|
freq, err := strconv.Atoi(ps[0])
|
||||||
|
if err != nil {
|
||||||
|
return retErr
|
||||||
|
}
|
||||||
|
if freq == 0 {
|
||||||
|
f.infinity = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ps[1] {
|
||||||
|
case "ns", "us", "µs", "ms", "s", "m", "h":
|
||||||
|
ps[1] = "1" + ps[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
per, err := time.ParseDuration(ps[1])
|
||||||
|
if err != nil {
|
||||||
|
return retErr
|
||||||
|
}
|
||||||
|
|
||||||
|
f.limit = rate.Limit(float64(freq) / per.Seconds())
|
||||||
|
f.v = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *rateFlagValue) Limit() *rate.Limit {
|
||||||
|
if f.infinity {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &f.limit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *rateFlagValue) String() string {
|
||||||
|
return f.v
|
||||||
|
}
|
||||||
|
|
||||||
|
func rateFlag(c *kingpin.Clause) (target *rateFlagValue) {
|
||||||
|
target = new(rateFlagValue)
|
||||||
|
c.SetValue(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
kingpin.UsageTemplate(CompactUsageTemplate).
|
kingpin.UsageTemplate(CompactUsageTemplate).
|
||||||
Version("1.1.0").
|
Version("1.2.0").
|
||||||
Author("six-ddc@github").
|
Author("six-ddc@github").
|
||||||
Resolver(kingpin.PrefixedEnvarResolver("PLOW_", ";")).
|
Resolver(kingpin.PrefixedEnvarResolver("PLOW_", ";")).
|
||||||
Help = `A high-performance HTTP benchmarking tool with real-time web UI and terminal displaying`
|
Help = `A high-performance HTTP benchmarking tool with real-time web UI and terminal displaying`
|
||||||
|
@ -160,7 +226,7 @@ func main() {
|
||||||
host: *host,
|
host: *host,
|
||||||
}
|
}
|
||||||
|
|
||||||
requester, err := NewRequester(*concurrency, *requests, *duration, &clientOpt)
|
requester, err := NewRequester(*concurrency, *requests, *duration, reqRate.Limit(), &clientOpt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errAndExit(err.Error())
|
errAndExit(err.Error())
|
||||||
return
|
return
|
||||||
|
|
17
requester.go
17
requester.go
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
"github.com/valyala/fasthttp/fasthttpproxy"
|
"github.com/valyala/fasthttp/fasthttpproxy"
|
||||||
"go.uber.org/automaxprocs/maxprocs"
|
"go.uber.org/automaxprocs/maxprocs"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
url2 "net/url"
|
url2 "net/url"
|
||||||
|
@ -90,6 +91,7 @@ func ThroughputInterceptorDial(dial fasthttp.DialFunc, r *int64, w *int64) fasth
|
||||||
|
|
||||||
type Requester struct {
|
type Requester struct {
|
||||||
concurrency int
|
concurrency int
|
||||||
|
reqRate *rate.Limit
|
||||||
requests int64
|
requests int64
|
||||||
duration time.Duration
|
duration time.Duration
|
||||||
clientOpt *ClientOpt
|
clientOpt *ClientOpt
|
||||||
|
@ -128,13 +130,14 @@ type ClientOpt struct {
|
||||||
host string
|
host string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRequester(concurrency int, requests int64, duration time.Duration, clientOpt *ClientOpt) (*Requester, error) {
|
func NewRequester(concurrency int, requests int64, duration time.Duration, reqRate *rate.Limit, clientOpt *ClientOpt) (*Requester, error) {
|
||||||
maxResult := concurrency * 100
|
maxResult := concurrency * 100
|
||||||
if maxResult > 8192 {
|
if maxResult > 8192 {
|
||||||
maxResult = 8192
|
maxResult = 8192
|
||||||
}
|
}
|
||||||
r := &Requester{
|
r := &Requester{
|
||||||
concurrency: concurrency,
|
concurrency: concurrency,
|
||||||
|
reqRate: reqRate,
|
||||||
requests: requests,
|
requests: requests,
|
||||||
duration: duration,
|
duration: duration,
|
||||||
clientOpt: clientOpt,
|
clientOpt: clientOpt,
|
||||||
|
@ -304,6 +307,11 @@ func (r *Requester) Run() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var limiter *rate.Limiter
|
||||||
|
if r.reqRate != nil {
|
||||||
|
limiter = rate.NewLimiter(*r.reqRate, 1)
|
||||||
|
}
|
||||||
|
|
||||||
semaphore := r.requests
|
semaphore := r.requests
|
||||||
for i := 0; i < r.concurrency; i++ {
|
for i := 0; i < r.concurrency; i++ {
|
||||||
r.wg.Add(1)
|
r.wg.Add(1)
|
||||||
|
@ -330,6 +338,13 @@ func (r *Requester) Run() {
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if limiter != nil {
|
||||||
|
err := limiter.Wait(ctx)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if r.requests > 0 && atomic.AddInt64(&semaphore, -1) < 0 {
|
if r.requests > 0 && atomic.AddInt64(&semaphore, -1) < 0 {
|
||||||
cancelFunc()
|
cancelFunc()
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in New Issue