Skip to content

Commit

Permalink
refactor printing logic (#316)
Browse files Browse the repository at this point in the history
* refactor printing logic

* adjust codecov action
  • Loading branch information
Tantalor93 authored Nov 26, 2024
1 parent 727f54c commit f14a6d3
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 96 deletions.
7 changes: 2 additions & 5 deletions .github/workflows/go.coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,8 @@ jobs:
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Build
run: go build -v ./...
- name: Test With Coverage
run: |
go test -coverprofile=cover.out -covermode=atomic -race ./...; [ -f cover.out ] && cat cover.out >> ../coverage.txt
- name: Run coverage
run: go test -race -coverprofile=coverage.out -covermode=atomic
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
env:
Expand Down
4 changes: 2 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,13 +205,13 @@ func Execute() {
end := time.Now()

if err != nil {
printutils.ErrPrint(os.Stderr, "There was an error while starting benchmark: %s\n", err.Error())
printutils.ErrFprintf(os.Stderr, "There was an error while starting benchmark: %s\n", err.Error())
close(sigsInt)
os.Exit(1)
}

if err := reporter.PrintReport(&benchmark, res, start, end.Sub(start)); err != nil {
printutils.ErrPrint(os.Stderr, "There was an error while printing report: %s\n", err.Error())
printutils.ErrFprintf(os.Stderr, "There was an error while printing report: %s\n", err.Error())
close(sigsInt)
os.Exit(1)
}
Expand Down
12 changes: 7 additions & 5 deletions pkg/dnsbench/benchmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ func (b *Benchmark) Run(ctx context.Context) ([]*ResultStats, error) {
}

if !b.Silent && !b.JSON {
fmt.Fprintf(b.Writer, "Using %s hostnames\n", printutils.HighlightStr(len(questions)))
printutils.NeutralFprintf(b.Writer, "Using %s hostnames\n", printutils.HighlightSprint(len(questions)))
}

var qTypes []uint16
Expand All @@ -334,18 +334,20 @@ func (b *Benchmark) Run(ctx context.Context) ([]*ResultStats, error) {
if b.Rate > 0 {
limit = ratelimit.New(b.Rate)
if b.RateLimitWorker == 0 {
limits = fmt.Sprintf("(limited to %s QPS overall)", printutils.HighlightStr(b.Rate))
limits = fmt.Sprintf("(limited to %s QPS overall)", printutils.HighlightSprint(b.Rate))
} else {
limits = fmt.Sprintf("(limited to %s QPS overall and %s QPS per concurrent worker)", printutils.HighlightStr(b.Rate), printutils.HighlightStr(b.RateLimitWorker))
limits = fmt.Sprintf("(limited to %s QPS overall and %s QPS per concurrent worker)",
printutils.HighlightSprint(b.Rate), printutils.HighlightSprint(b.RateLimitWorker))
}
}
if b.Rate == 0 && b.RateLimitWorker > 0 {
limits = fmt.Sprintf("(limited to %s QPS per concurrent worker)", printutils.HighlightStr(b.RateLimitWorker))
limits = fmt.Sprintf("(limited to %s QPS per concurrent worker)", printutils.HighlightSprint(b.RateLimitWorker))
}

if !b.Silent && !b.JSON {
network := b.network()
fmt.Fprintf(b.Writer, "Benchmarking %s via %s with %s concurrent requests %s\n", printutils.HighlightStr(b.Server), printutils.HighlightStr(network), printutils.HighlightStr(b.Concurrency), limits)
printutils.NeutralFprintf(b.Writer, "Benchmarking %s via %s with %s concurrent requests %s\n",
printutils.HighlightSprint(b.Server), printutils.HighlightSprint(network), printutils.HighlightSprint(b.Concurrency), limits)
}

var bar *progressbar.ProgressBar
Expand Down
62 changes: 62 additions & 0 deletions pkg/dnsbench/benchmark_plaindns_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ func (suite *PlainDNSTestSuite) TestBenchmark_Run_global_ratelimit() {
})
defer s.Close()

buf := bytes.Buffer{}
bench := dnsbench.Benchmark{
Queries: []string{"example.org"},
Types: []string{"A"},
Expand All @@ -502,6 +503,7 @@ func (suite *PlainDNSTestSuite) TestBenchmark_Run_global_ratelimit() {
RequestTimeout: 5 * time.Second,
Rcodes: true,
Recurse: true,
Writer: &buf,
}

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
Expand All @@ -513,6 +515,10 @@ func (suite *PlainDNSTestSuite) TestBenchmark_Run_global_ratelimit() {
// assert that total queries is 5 with +-1 precision, because benchmark cancellation based on duration is not that precise
// and one worker can start the resolution before cancelling
suite.InDelta(int64(5), rs[0].Counters.Total+rs[1].Counters.Total, 1.0)
suite.Equal(
fmt.Sprintf("Using 1 hostnames\nBenchmarking %s via udp with 2 concurrent requests (limited to 1 QPS overall)\n",
s.Addr), buf.String(),
)
}

func (suite *PlainDNSTestSuite) TestBenchmark_Run_worker_ratelimit() {
Expand All @@ -528,6 +534,7 @@ func (suite *PlainDNSTestSuite) TestBenchmark_Run_worker_ratelimit() {
})
defer s.Close()

buf := bytes.Buffer{}
bench := dnsbench.Benchmark{
Queries: []string{"example.org"},
Types: []string{"A"},
Expand All @@ -543,6 +550,7 @@ func (suite *PlainDNSTestSuite) TestBenchmark_Run_worker_ratelimit() {
RequestTimeout: 5 * time.Second,
Rcodes: true,
Recurse: true,
Writer: &buf,
}

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
Expand All @@ -556,6 +564,60 @@ func (suite *PlainDNSTestSuite) TestBenchmark_Run_worker_ratelimit() {
// because benchmark cancellation based on duration is not that precise
// and each worker can start the resolution before cancelling
suite.InDelta(int64(10), rs[0].Counters.Total+rs[1].Counters.Total, 2.0)
suite.Equal(
fmt.Sprintf("Using 1 hostnames\nBenchmarking %s via udp with 2 concurrent requests (limited to 1 QPS per concurrent worker)\n",
s.Addr), buf.String(),
)
}

func (suite *PlainDNSTestSuite) TestBenchmark_Run_global_ratelimit_precendence() {
s := NewServer(dnsbench.UDPTransport, nil, 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"))

// wait some time to actually have some observable duration
time.Sleep(time.Millisecond * 100)

w.WriteMsg(ret)
})
defer s.Close()

buf := bytes.Buffer{}
bench := dnsbench.Benchmark{
Queries: []string{"example.org"},
Types: []string{"A"},
Server: s.Addr,
TCP: false,
Concurrency: 2,
Duration: 5 * time.Second,
RateLimitWorker: 2,
Rate: 1,
Probability: 1,
WriteTimeout: 1 * time.Second,
ReadTimeout: 3 * time.Second,
ConnectTimeout: 1 * time.Second,
RequestTimeout: 5 * time.Second,
Rcodes: true,
Recurse: true,
Writer: &buf,
}

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
rs, err := bench.Run(ctx)

suite.Require().NoError(err, "expected no error from benchmark run")
suite.Require().Len(rs, 2, "expected results from two workers")

// assert that total queries is 10 with +-2 precision,
// because benchmark cancellation based on duration is not that precise
// and each worker can start the resolution before cancelling
suite.InDelta(int64(5), rs[0].Counters.Total+rs[1].Counters.Total, 1.0)
suite.Equal(
fmt.Sprintf("Using 1 hostnames\nBenchmarking %s via udp with 2 concurrent requests (limited to 1 QPS overall and 2 QPS per concurrent worker)\n",
s.Addr), buf.String(),
)
}

func (suite *PlainDNSTestSuite) TestBenchmark_Run_error() {
Expand Down
22 changes: 15 additions & 7 deletions pkg/printutils/printutils.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package printutils

import "github.com/fatih/color"
import (
"github.com/fatih/color"
)

var (
// ErrPrint is a wrapper for printing colored errors.
ErrPrint = color.New(color.FgRed).FprintfFunc()
// SuccessPrint is a wrapper for printing colored successes.
SuccessPrint = color.New(color.FgGreen).FprintfFunc()
// HighlightStr is a wrapper for highlighting strings with color.
HighlightStr = color.New(color.FgYellow).SprintFunc()
// ErrFprintf is a wrapper for printing colored errors.
ErrFprintf = color.New(color.FgRed).FprintfFunc()
// SuccessFprintf is a wrapper for printing colored successes.
SuccessFprintf = color.New(color.FgGreen).FprintfFunc()
// NeutralFprintf is a wrapper for printing neutral information.
NeutralFprintf = color.New().FprintfFunc()

highlightColor = color.New(color.FgYellow)
// HighlightSprintf is a wrapper for highlighting formatted strings with color.
HighlightSprintf = highlightColor.SprintfFunc()
// HighlightSprint is a wrapper for highlighting strings with color.
HighlightSprint = highlightColor.SprintFunc()
)
82 changes: 40 additions & 42 deletions pkg/reporter/stdreporter.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package reporter

import (
"fmt"
"io"
"sort"
"strconv"
Expand All @@ -21,12 +20,14 @@ func (s *standardReporter) print(params reportParameters) error {
printProgress(params.outputWriter, params.totalCounters)

if len(params.codeTotals) > 0 {
fmt.Fprintln(params.outputWriter)
fmt.Fprintln(params.outputWriter, "DNS response codes:")
printutils.NeutralFprintf(params.outputWriter, "\nDNS response codes:\n")
for i := dns.RcodeSuccess; i <= dns.RcodeBadCookie; i++ {
printFn := printutils.ErrPrint
printFn := printutils.ErrFprintf
if i == dns.RcodeSuccess {
printFn = printutils.SuccessPrint
printFn = printutils.SuccessFprintf
}
if i == dns.RcodeNameError {
printFn = printutils.NeutralFprintf
}
if c, ok := params.codeTotals[i]; ok {
printFn(params.outputWriter, "\t%s:\t%d\n", dns.RcodeToString[i], c)
Expand All @@ -41,35 +42,33 @@ func (s *standardReporter) print(params reportParameters) error {
sort.Ints(dohResponseStatuses)

if len(params.dohResponseStatusesTotals) > 0 {
fmt.Fprintln(params.outputWriter)
fmt.Fprintln(params.outputWriter, "DoH HTTP response status codes:")
printutils.NeutralFprintf(params.outputWriter, "\nDoH HTTP response status codes:\n")
for _, st := range dohResponseStatuses {
if st == 200 {
printutils.SuccessPrint(params.outputWriter, "\t%d:\t%d\n", st, params.dohResponseStatusesTotals[st])
printutils.SuccessFprintf(params.outputWriter, "\t%d:\t%d\n", st, params.dohResponseStatusesTotals[st])
} else {
printutils.ErrPrint(params.outputWriter, "\t%d:\t%d\n", st, params.dohResponseStatusesTotals[st])
printutils.ErrFprintf(params.outputWriter, "\t%d:\t%d\n", st, params.dohResponseStatusesTotals[st])
}
}
}

if len(params.qtypeTotals) > 0 {
fmt.Fprintln(params.outputWriter)
fmt.Fprintln(params.outputWriter, "DNS question types:")
printutils.NeutralFprintf(params.outputWriter, "\nDNS question types:\n")
for k, v := range params.qtypeTotals {
printutils.SuccessPrint(params.outputWriter, "\t%s:\t%d\n", k, v)
printutils.SuccessFprintf(params.outputWriter, "\t%s:\t%d\n", k, v)
}
}

if params.benchmark.DNSSEC {
fmt.Fprintln(params.outputWriter)
fmt.Fprintln(params.outputWriter, "Number of domains secured using DNSSEC:", printutils.HighlightStr(len(params.authenticatedDomains)))
printutils.NeutralFprintf(params.outputWriter,
"\nNumber of domains secured using DNSSEC: %s\n", printutils.HighlightSprint(len(params.authenticatedDomains)))
}

fmt.Fprintln(params.outputWriter)
printutils.NeutralFprintf(params.outputWriter, "\nTime taken for tests:\t%s\n",
printutils.HighlightSprint(roundDuration(params.benchmarkDuration)))
printutils.NeutralFprintf(params.outputWriter, "Questions per second:\t%s\n",
printutils.HighlightSprintf("%0.1f", float64(params.totalCounters.Total)/params.benchmarkDuration.Seconds()))

fmt.Fprintln(params.outputWriter, "Time taken for tests:\t", printutils.HighlightStr(roundDuration(params.benchmarkDuration).String()))
fmt.Fprintf(params.outputWriter, "Questions per second:\t %s", printutils.HighlightStr(fmt.Sprintf("%0.1f", float64(params.totalCounters.Total)/params.benchmarkDuration.Seconds())))
fmt.Fprintln(params.outputWriter)
min := time.Duration(params.hist.Min())
mean := time.Duration(params.hist.Mean())
sd := time.Duration(params.hist.StdDev())
Expand All @@ -81,22 +80,20 @@ func (s *standardReporter) print(params reportParameters) error {
p50 := time.Duration(params.hist.ValueAtQuantile(50))

if tc := params.hist.TotalCount(); tc > 0 {
fmt.Fprintln(params.outputWriter, "DNS timings,", printutils.HighlightStr(tc), "datapoints")
fmt.Fprintln(params.outputWriter, "\t min:\t\t", printutils.HighlightStr(roundDuration(min)))
fmt.Fprintln(params.outputWriter, "\t mean:\t\t", printutils.HighlightStr(roundDuration(mean)))
fmt.Fprintln(params.outputWriter, "\t [+/-sd]:\t", printutils.HighlightStr(roundDuration(sd)))
fmt.Fprintln(params.outputWriter, "\t max:\t\t", printutils.HighlightStr(roundDuration(max)))
fmt.Fprintln(params.outputWriter, "\t p99:\t\t", printutils.HighlightStr(roundDuration(p99)))
fmt.Fprintln(params.outputWriter, "\t p95:\t\t", printutils.HighlightStr(roundDuration(p95)))
fmt.Fprintln(params.outputWriter, "\t p90:\t\t", printutils.HighlightStr(roundDuration(p90)))
fmt.Fprintln(params.outputWriter, "\t p75:\t\t", printutils.HighlightStr(roundDuration(p75)))
fmt.Fprintln(params.outputWriter, "\t p50:\t\t", printutils.HighlightStr(roundDuration(p50)))
printutils.NeutralFprintf(params.outputWriter, "DNS timings, %s datapoints\n", printutils.HighlightSprint(tc))
printutils.NeutralFprintf(params.outputWriter, "\t min:\t\t%s\n", printutils.HighlightSprint(roundDuration(min)))
printutils.NeutralFprintf(params.outputWriter, "\t mean:\t\t%s\n", printutils.HighlightSprint(roundDuration(mean)))
printutils.NeutralFprintf(params.outputWriter, "\t [+/-sd]:\t%s\n", printutils.HighlightSprint(roundDuration(sd)))
printutils.NeutralFprintf(params.outputWriter, "\t max:\t\t%s\n", printutils.HighlightSprint(roundDuration(max)))
printutils.NeutralFprintf(params.outputWriter, "\t p99:\t\t%s\n", printutils.HighlightSprint(roundDuration(p99)))
printutils.NeutralFprintf(params.outputWriter, "\t p95:\t\t%s\n", printutils.HighlightSprint(roundDuration(p95)))
printutils.NeutralFprintf(params.outputWriter, "\t p90:\t\t%s\n", printutils.HighlightSprint(roundDuration(p90)))
printutils.NeutralFprintf(params.outputWriter, "\t p75:\t\t%s\n", printutils.HighlightSprint(roundDuration(p75)))
printutils.NeutralFprintf(params.outputWriter, "\t p50:\t\t%s\n", printutils.HighlightSprint(roundDuration(p50)))

dist := params.hist.Distribution()
if params.benchmark.HistDisplay && tc > 1 {
fmt.Fprintln(params.outputWriter)
fmt.Fprintln(params.outputWriter, "DNS distribution,", printutils.HighlightStr(tc), "datapoints")

printutils.NeutralFprintf(params.outputWriter, "\nDNS distribution, %s datapoints\n", printutils.HighlightSprint(tc))
printBars(params.outputWriter, dist)
}
}
Expand All @@ -107,39 +104,40 @@ func (s *standardReporter) print(params reportParameters) error {
}

if len(params.topErrs.m) > 0 {
printutils.ErrPrint(params.outputWriter, "\nTotal Errors: %d\n", sumerrs)
printutils.ErrPrint(params.outputWriter, "Top errors:\n")
printutils.ErrFprintf(params.outputWriter, "\nTotal Errors: %d\n", sumerrs)
printutils.ErrFprintf(params.outputWriter, "Top errors:\n")
for _, err := range params.topErrs.order {
printutils.ErrPrint(params.outputWriter, "%s\t%d (%.2f)%%\n", err, params.topErrs.m[err], (float64(params.topErrs.m[err])/float64(sumerrs))*100)
printutils.ErrFprintf(params.outputWriter, "%s\t%d (%.2f)%%\n", err, params.topErrs.m[err],
(float64(params.topErrs.m[err])/float64(sumerrs))*100)
}
}

return nil
}

func printProgress(w io.Writer, c dnsbench.Counters) {
fmt.Fprintf(w, "\nTotal requests:\t\t%s\n", printutils.HighlightStr(c.Total))
printutils.NeutralFprintf(w, "\nTotal requests:\t\t%s\n", printutils.HighlightSprint(c.Total))

if c.IOError > 0 {
printutils.ErrPrint(w, "Read/Write errors:\t%d\n", c.IOError)
printutils.ErrFprintf(w, "Read/Write errors:\t%d\n", c.IOError)
}

if c.IDmismatch > 0 {
printutils.ErrPrint(w, "ID mismatch errors:\t%d\n", c.IDmismatch)
printutils.ErrFprintf(w, "ID mismatch errors:\t%d\n", c.IDmismatch)
}

if c.Success > 0 {
printutils.SuccessPrint(w, "DNS success responses:\t%d\n", c.Success)
printutils.SuccessFprintf(w, "DNS success responses:\t%d\n", c.Success)
}
if c.Negative > 0 {
fmt.Fprintf(w, "DNS negative responses:\t%d\n", c.Negative)
printutils.NeutralFprintf(w, "DNS negative responses:\t%d\n", c.Negative)
}
if c.Error > 0 {
printutils.ErrPrint(w, "DNS error responses:\t%d\n", c.Error)
printutils.ErrFprintf(w, "DNS error responses:\t%d\n", c.Error)
}

if c.Truncated > 0 {
printutils.ErrPrint(w, "Truncated responses:\t%d\n", c.Truncated)
printutils.ErrFprintf(w, "Truncated responses:\t%d\n", c.Truncated)
}
}

Expand Down Expand Up @@ -184,7 +182,7 @@ func makeBar(c int64, max int64) string {
return ""
}
t := int((43 * float64(c) / float64(max)) + 0.5)
return strings.Repeat(printutils.HighlightStr("▄"), t)
return strings.Repeat(printutils.HighlightSprint("▄"), t)
}

func roundDuration(dur time.Duration) time.Duration {
Expand Down
22 changes: 11 additions & 11 deletions pkg/reporter/testdata/dnssecReport
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ DNS question types:

Number of domains secured using DNSSEC: 1

Time taken for tests: 1s
Questions per second: 1.0
Time taken for tests: 1s
Questions per second: 1.0
DNS timings, 2 datapoints
min: 5ns
mean: 7ns
[+/-sd]: 2ns
max: 10ns
p99: 10ns
p95: 10ns
p90: 10ns
p75: 10ns
p50: 5ns
min: 5ns
mean: 7ns
[+/-sd]: 2ns
max: 10ns
p99: 10ns
p95: 10ns
p90: 10ns
p75: 10ns
p50: 5ns

Total Errors: 6
Top errors:
Expand Down
Loading

0 comments on commit f14a6d3

Please sign in to comment.