diff --git a/README.md b/README.md index f7afbe0..9f72944 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ This tool supports wide variety of options to customize DNS benchmark and benchm * benchmark DNS servers with IPv4 and IPv6 addresses (for example GoogleDNS `8.8.8.8` and `2001:4860:4860::8888`) * benchmark DNS servers with all kinds of query types (A, AAAA, CNAME, HTTPS, ...) * benchmark DNS servers with a lot of parallel queries and connections (`--number`, `--concurrency` options) +* benchmark DNS servers for a specified duration (`--duration` option) * benchmark DNS servers using DNS queries over UDP or TCP * benchmark DNS servers with DoT * benchmark DNS servers using DoH @@ -74,7 +75,7 @@ Flags: -s, --server="127.0.0.1" DNS server IP:port to test. IPv6 is also supported, for example '[fddd:dddd::]:53'. Also DoH servers are supported such as `https://1.1.1.1/dns-query`, when such server is provided, the benchmark automatically switches to the use of DoH. Note that path on which DoH server handles requests (like `/dns-query`) has to be provided as well. -t, --type=A ... Query type. Repeatable flag. If multiple query types are specified then each query will be duplicated for each type. - -n, --number=1 How many times the provided queries are repeated. Note that the total number of queries issued = types*number*concurrency*len(queries). + -n, --number=NUMBER How many times the provided queries are repeated. Note that the total number of queries issued = types*number*concurrency*len(queries). -c, --concurrency=1 Number of concurrent queries to issue. -l, --rate-limit=0 Apply a global questions / second rate limit. --query-per-conn=0 Queries on a connection before creating a new one. 0: unlimited @@ -99,11 +100,13 @@ Flags: --plotf=png Format of graphs. Supported formats png, svg, pdf. --doh-method=post HTTP method to use for DoH requests --doh-protocol=1.1 HTTP protocol to use for DoH requests + -d, --duration=1m Specifies for how long the benchmark should be executing, the benchmark will run for the specified time while sending DNS requests in infinite loop based on data source. After running for specified duration, the benchmark + is cancelled. This option is exclusive with --number option. The duration is specified in GO duration format e.g. 10s, 15m, 1h. --version Show application version. Args: - Queries to issue. Can be local file referenced using @, for example @data/2-domains.Can also be resource accessible using HTTP, like https://raw.githubusercontent.com/Tantalor93/dnspyre/master/data/1000-domains, in that case the - file will be downloaded and saved inmemory. + Queries to issue. Can be local file referenced using @, for example @data/2-domains.Can also be resource accessible using HTTP, like https://raw.githubusercontent.com/Tantalor93/dnspyre/master/data/1000-domains, in that case the + file will be downloaded and saved inmemory. ``` ## Examples diff --git a/cmd/dnspyre/benchmark.go b/cmd/dnspyre/benchmark.go index d699ecf..7cdb0c1 100644 --- a/cmd/dnspyre/benchmark.go +++ b/cmd/dnspyre/benchmark.go @@ -98,6 +98,8 @@ type Benchmark struct { Queries []string + Duration time.Duration + // internal variable so we do not have to parse the address with each request. useDoH bool } @@ -108,6 +110,15 @@ func (b *Benchmark) normalize() { if !strings.Contains(b.Server, ":") && !b.useDoH { b.Server += ":53" } + + if b.Count == 0 && b.Duration == 0 { + b.Count = 1 + } + + if b.Duration > 0 && b.Count > 0 { + fmt.Fprintln(os.Stderr, "--number and --duration is specified at once, only one can be used") + os.Exit(1) + } } // Run executes benchmark. @@ -137,6 +148,12 @@ func (b *Benchmark) Run(ctx context.Context) []*ResultStats { } } + if b.Duration != 0 { + timeoutCtx, cancel := context.WithTimeout(ctx, b.Duration) + ctx = timeoutCtx + defer cancel() + } + if !b.Silent { fmt.Printf("Using %d hostnames\n", len(questions)) } @@ -222,7 +239,7 @@ func (b *Benchmark) Run(ctx context.Context) []*ResultStats { rando := rand.New(rand.NewSource(time.Now().Unix())) var i int64 - for i = 0; i < b.Count; i++ { + for i = 0; i < b.Count || b.Duration != 0; i++ { for _, qt := range qTypes { for _, q := range questions { if rando.Float64() > b.Probability { diff --git a/cmd/dnspyre/benchmark_test.go b/cmd/dnspyre/benchmark_test.go index 415febb..cc5021b 100644 --- a/cmd/dnspyre/benchmark_test.go +++ b/cmd/dnspyre/benchmark_test.go @@ -218,6 +218,35 @@ func Test_download_external_datasource_using_http(t *testing.T) { assertResult(t, rs) } +func Test_do_classic_dns_with_duration(t *testing.T) { + s := NewServer("udp", func(w dns.ResponseWriter, r *dns.Msg) { + ret := new(dns.Msg) + ret.SetReply(r) + ret.Answer = append(ret.Answer, A("example.org. IN A 127.0.0.1")) + w.WriteMsg(ret) + }) + defer s.Close() + + bench := Benchmark{ + Queries: []string{"example.org"}, + Types: []string{"A"}, + Server: s.Addr, + Concurrency: 1, + Duration: 2 * time.Second, + Probability: 1, + WriteTimeout: 5 * time.Second, + ReadTimeout: 5 * time.Second, + Rcodes: true, + Recurse: true, + } + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + rs := bench.Run(ctx) + + assert.GreaterOrEqual(t, rs[0].Counters.Total, int64(1), "there should be atleast one execution") +} + func assertResult(t *testing.T, rs []*ResultStats) { if assert.Len(t, rs, 2, "Run(ctx) rstats") { rs0 := rs[0] diff --git a/cmd/dnspyre/root.go b/cmd/dnspyre/root.go index 02f93ac..b54e6a3 100644 --- a/cmd/dnspyre/root.go +++ b/cmd/dnspyre/root.go @@ -29,7 +29,7 @@ var ( "Note that path on which DoH server handles requests (like `/dns-query`) has to be provided as well.").Short('s').Default("127.0.0.1").String() pTypes = pApp.Flag("type", "Query type. Repeatable flag. If multiple query types are specified then each query will be duplicated for each type.").Short('t').Default("A").Enums(getSupportedDNSTypes()...) - pCount = pApp.Flag("number", "How many times the provided queries are repeated. Note that the total number of queries issued = types*number*concurrency*len(queries).").Short('n').Default("1").Int64() + pCount = pApp.Flag("number", "How many times the provided queries are repeated. Note that the total number of queries issued = types*number*concurrency*len(queries).").Short('n').Int64() pConcurrency = pApp.Flag("concurrency", "Number of concurrent queries to issue.").Short('c').Default("1").Uint32() pRate = pApp.Flag("rate-limit", "Apply a global questions / second rate limit.").Short('l').Default("0").Int() @@ -66,6 +66,11 @@ var ( pDoHmethod = pApp.Flag("doh-method", "HTTP method to use for DoH requests").Default("post").Enum("get", "post") pDoHProtocol = pApp.Flag("doh-protocol", "HTTP protocol to use for DoH requests").Default("1.1").Enum("1.1", "2") + pDuration = pApp.Flag("duration", "Specifies for how long the benchmark should be executing, the benchmark will run for the specified time "+ + "while sending DNS requests in infinite loop based on data source. After running for specified duration, the benchmark is cancelled. "+ + "This option is exclusive with --number option. The duration is specified in GO duration format e.g. 10s, 15m, 1h."). + PlaceHolder("1m").Short('d').Duration() + pQueries = pApp.Arg("queries", "Queries to issue. Can be local file referenced using @, for example @data/2-domains."+ "Can also be resource accessible using HTTP, like https://raw.githubusercontent.com/Tantalor93/dnspyre/master/data/1000-domains, in that "+ "case the file will be downloaded and saved inmemory.").Required().Strings() @@ -107,6 +112,7 @@ func Execute() { PlotFormat: *pPlotFormat, DohMethod: *pDoHmethod, DohProtocol: *pDoHProtocol, + Duration: *pDuration, Queries: *pQueries, } diff --git a/docs/examples.md b/docs/examples.md index 6266c85..7f331ee 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -1,5 +1,6 @@ ## Examples + [parallel benchmark with repeating queries](#parallel-benchmark-with-repeating-queries) ++ [run benchmark over specified time](#run-benchmark-over-specified-time) + [sending AAAA DNS queries](#sending-AAAA-DNS-queries) + [hostnames provided directly](#multiple-hostnames-provided-directly) + [hostnames provided using file](#hostnames-provided-using-file) @@ -17,11 +18,18 @@ ### parallel benchmark with repeating queries this example will execute the benchmark in 10 parallel threads, where each thread will -send 2 `A example.com.` DNS queries serially +send 2 `A example.com.` DNS queries serially to the `8.8.8.8` server ``` dnspyre -n 2 -c 10 --server 8.8.8.8 --recurse example.com ``` +### run benchmark over specified time +this example will execute the benchmark in 10 parallel threads for a duration of 30 seconds while sending `A example.com` DNS queries +to the `8.8.8.8` server +``` +dnspyre --duration 30s -c 10 --server 8.8.8.8 google.com +``` + ### sending AAAA DNS queries ``` dnspyre -n 2 -c 10 --server 8.8.8.8 -t AAAA --recurse example.com @@ -72,6 +80,7 @@ coming from the local/experimental range with payload `fddddddd10000000000000000 ``` dnspyre -n 10 -c 10 --recurse idnes.cz --server 127.0.0.1 --ednsopt=65518:fddddddd100000000000000000000001 ``` + ### DoT benchmarking DoT server ```