From 0816053f278080ef949e90894bc73f3b1448de82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kuffel?= Date: Mon, 15 Nov 2021 19:43:49 +0100 Subject: [PATCH 1/3] feat: json and yaml test report format --- args.go | 30 +++++++++++++----------- common/config.go | 1 + go.mod | 1 + go.sum | 2 ++ gocannon.go | 18 +++++++++----- hist/histogram.go | 52 +++++++++++++++++++++++++++++------------ hist/histogram_print.go | 4 ++-- integration_test.go | 4 ++++ plugin_loader.go | 6 +++-- reqlog/request.go | 33 +++++++++++++++++++++----- reqlog/stats.go | 34 +++++++++++++-------------- reqlog/stats_print.go | 12 +++++----- reqlog/stats_test.go | 25 ++++++++++---------- rescodes/rescodes.go | 11 +++++++++ stats_collector.go | 2 +- 15 files changed, 154 insertions(+), 81 deletions(-) diff --git a/args.go b/args.go index 39932a8..b1e8cf3 100644 --- a/args.go +++ b/args.go @@ -19,42 +19,46 @@ func parseRequestHeaders(s kingpin.Settings) *common.RequestHeaders { return r } -var app = kingpin.New("gocannon", "Performance-focused HTTP benchmarking tool") +var app = kingpin.New("gocannon", "Performance-focused HTTP load testing tool.") var config = common.Config{ - Duration: app.Flag("duration", "Load test duration"). + Duration: app.Flag("duration", "Load test duration."). Short('d'). Default("10s"). Duration(), - Connections: app.Flag("connections", "Maximum number of concurrent connections"). + Connections: app.Flag("connections", "Maximum number of concurrent connections."). Short('c'). Default("50"). Int(), - Timeout: app.Flag("timeout", "HTTP client timeout"). + Timeout: app.Flag("timeout", "HTTP client timeout."). Short('t'). Default("200ms"). Duration(), - Mode: app.Flag("mode", "Statistics collection mode: reqlog (logs each request) or hist (stores histogram of completed requests latencies)"). + Mode: app.Flag("mode", "Statistics collection mode: reqlog (logs each request) or hist (stores histogram of completed requests latencies)."). Default("reqlog"). Short('m'). String(), - OutputFile: app.Flag("output", "File to save the request log in CSV format (reqlog mode) or a text file with raw histogram data (hist mode)"). + OutputFile: app.Flag("output", "File to save the request log in CSV format (reqlog mode) or a text file with raw histogram data (hist mode)."). PlaceHolder("file.csv"). Short('o'). String(), - Interval: app.Flag("interval", "Interval for statistics calculation (reqlog mode)"). + Interval: app.Flag("interval", "Interval for statistics calculation (reqlog mode)."). Default("250ms"). Short('i'). Duration(), - Preallocate: app.Flag("preallocate", "Number of requests in req log to preallocate memory for per connection (reqlog mode)"). + Preallocate: app.Flag("preallocate", "Number of requests in req log to preallocate memory for per connection (reqlog mode)."). Default("1000"). Int(), - Method: app.Flag("method", "The HTTP request method (GET, POST, PUT, PATCH or DELETE)").Default("GET").Enum("GET", "POST", "PUT", "PATCH", "DELETE"), - Body: parseRequestBody(app.Flag("body", "HTTP request body").Short('b').PlaceHolder("\"{data...\"")), + Method: app.Flag("method", "The HTTP request method (GET, POST, PUT, PATCH or DELETE).").Default("GET").Enum("GET", "POST", "PUT", "PATCH", "DELETE"), + Body: parseRequestBody(app.Flag("body", "HTTP request body.").Short('b').PlaceHolder("\"{data...\"")), Headers: parseRequestHeaders(kingpin.Flag("header", "HTTP request header(s). You can set more than one header by repeating this flag.").Short('h').PlaceHolder("\"k:v\"")), - TrustAll: app.Flag("trust-all", "Omit SSL certificate validation").Bool(), - Plugin: app.Flag("plugin", "Plugin to run Gocannon with").PlaceHolder("/to/p.so").ExistingFile(), - Target: app.Arg("target", "HTTP target URL with port (i.e. http://localhost:80/test or https://host:443/x)").Required().String(), + TrustAll: app.Flag("trust-all", "Omit SSL certificate validation.").Bool(), + Format: app.Flag("format", "Load test report format. Either 'default' (verbose), 'json' or 'yaml'. When json or yaml is specified, apart from the load test results, no additional info will be written to std out."). + Short('f'). + Default("default"). + Enum("default", "json", "yaml"), + Plugin: app.Flag("plugin", "Plugin to run Gocannon with (path to .so file).").PlaceHolder("/to/p.so").ExistingFile(), + Target: app.Arg("target", "HTTP target URL with port (i.e. http://localhost:80/test or https://host:443/x)").Required().String(), } func parseArgs() error { diff --git a/common/config.go b/common/config.go index 727eb50..86f3c5a 100644 --- a/common/config.go +++ b/common/config.go @@ -15,6 +15,7 @@ type Config struct { Body *RawRequestBody Headers *RequestHeaders TrustAll *bool + Format *string Plugin *string Target *string } diff --git a/go.mod b/go.mod index ceff3c1..0766fc4 100644 --- a/go.mod +++ b/go.mod @@ -12,4 +12,5 @@ require ( github.com/valyala/fasthttp v1.31.0 gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 7bff2e5..ab5f8aa 100644 --- a/go.sum +++ b/go.sum @@ -43,5 +43,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/gocannon.go b/gocannon.go index 29e7835..c141a05 100644 --- a/gocannon.go +++ b/gocannon.go @@ -18,8 +18,12 @@ func runGocannon(cfg common.Config) error { var gocannonPlugin common.GocannonPlugin var err error + if *config.Format == "default" { + printHeader(config) + } + if *cfg.Plugin != "" { - gocannonPlugin, err = loadPlugin(*cfg.Plugin) + gocannonPlugin, err = loadPlugin(*cfg.Plugin, *cfg.Format != "default") if err != nil { return err } @@ -47,7 +51,9 @@ func runGocannon(cfg common.Config) error { start := makeTimestamp() stop := start + cfg.Duration.Nanoseconds() - fmt.Printf("gocannon goes brr...\n") + if *cfg.Format == "default" { + fmt.Printf("gocannon goes brr...\n") + } for connectionID := 0; connectionID < n; connectionID++ { go func(c *fasthttp.HostClient, cid int, p common.GocannonPlugin) { @@ -79,8 +85,10 @@ func runGocannon(cfg common.Config) error { return err } - printSummary(stats) - stats.PrintReport() + if *cfg.Format == "default" { + printSummary(stats) + } + stats.PrintReport(*cfg.Format) return nil } @@ -91,8 +99,6 @@ func main() { exitWithError(err) } - printHeader(config) - err = runGocannon(config) if err != nil { diff --git a/hist/histogram.go b/hist/histogram.go index 781ece6..e3cb9db 100644 --- a/hist/histogram.go +++ b/hist/histogram.go @@ -2,6 +2,7 @@ package hist import ( "bufio" + "encoding/json" "errors" "fmt" "os" @@ -9,15 +10,16 @@ import ( "time" "github.com/kffl/gocannon/rescodes" + "gopkg.in/yaml.v2" ) type histogram []int64 type summary struct { - reqCount int64 - reqPerSec float64 - latencyAvg float64 - latencyPercentiles []int64 + ReqCount int64 + ReqPerSec float64 + LatencyAvg float64 + LatencyPercentiles []int64 } type requestHist struct { @@ -75,16 +77,36 @@ func (h *requestHist) CalculateStats( return nil } -func (h *requestHist) PrintReport() { - h.results.print() - if h.didNotFit > 0 { - fmt.Fprintf( - os.Stderr, - "WARNING: some recorded responses (%d) did not fit in the histogram potentially skewing the resulting stats. Consider increasing timeout duration.\n", +func (h *requestHist) PrintReport(format string) { + if format == "default" { + h.results.print() + if h.didNotFit > 0 { + fmt.Fprintf( + os.Stderr, + "WARNING: some recorded responses (%d) did not fit in the histogram potentially skewing the resulting stats. Consider increasing timeout duration.\n", + h.didNotFit, + ) + } + h.resCodes.PrintRescodes() + } else { + obj := struct { + Report *summary + DidNotFit int64 + ResCodes map[int]int64 + }{ + &h.results, h.didNotFit, - ) + h.resCodes.AsMap(), + } + var output []byte + if format == "json" { + output, _ = json.MarshalIndent(obj, "", " ") + } + if format == "yaml" { + output, _ = yaml.Marshal(obj) + } + fmt.Printf("%s", output) } - h.resCodes.PrintRescodes() } func (h *requestHist) saveRawData(fileName string) error { @@ -109,13 +131,13 @@ func (h *requestHist) saveRawData(fileName string) error { } func (h *requestHist) GetReqCount() int64 { - return h.results.reqCount + return h.results.ReqCount } func (h *requestHist) GetReqPerSec() float64 { - return h.results.reqPerSec + return h.results.ReqPerSec } func (h *requestHist) GetLatencyAvg() float64 { - return h.results.latencyAvg * 1000.0 + return h.results.LatencyAvg * 1000.0 } diff --git a/hist/histogram_print.go b/hist/histogram_print.go index 4274325..fe19544 100644 --- a/hist/histogram_print.go +++ b/hist/histogram_print.go @@ -5,8 +5,8 @@ import "fmt" func (r *summary) print() { fmt.Println("|------------------------LATENCY (μs)-----------------------|") fmt.Println(" AVG P50 P75 P90 P99") - fmt.Printf("%13.2f", r.latencyAvg) - for _, v := range r.latencyPercentiles { + fmt.Printf("%13.2f", r.LatencyAvg) + for _, v := range r.LatencyPercentiles { fmt.Printf(" %11v", v) } fmt.Printf("\n") diff --git a/integration_test.go b/integration_test.go index 1db0adb..4735132 100644 --- a/integration_test.go +++ b/integration_test.go @@ -105,6 +105,7 @@ func TestGocannonDefaultValues(t *testing.T) { body := common.RawRequestBody{} header := common.RequestHeaders{} trustAll := false + format := "default" plugin := "" target := "http://localhost:3000/hello" @@ -120,6 +121,7 @@ func TestGocannonDefaultValues(t *testing.T) { Body: &body, Headers: &header, TrustAll: &trustAll, + Format: &format, Plugin: &plugin, Target: &target, } @@ -144,6 +146,7 @@ func TestGocanonWithPlugin(t *testing.T) { body := common.RawRequestBody{} header := common.RequestHeaders{} trustAll := true + format := "json" plugin := "_example_plugin/plugin.so" target := "http://localhost:3000/hello" @@ -159,6 +162,7 @@ func TestGocanonWithPlugin(t *testing.T) { Body: &body, Headers: &header, TrustAll: &trustAll, + Format: &format, Plugin: &plugin, Target: &target, } diff --git a/plugin_loader.go b/plugin_loader.go index 4f4934b..86495f6 100644 --- a/plugin_loader.go +++ b/plugin_loader.go @@ -14,7 +14,7 @@ var ( ErrPluginInterface = errors.New("module symbol doesn't match GocannonPlugin") ) -func loadPlugin(file string) (common.GocannonPlugin, error) { +func loadPlugin(file string, silentOutput bool) (common.GocannonPlugin, error) { p, err := plugin.Open(file) if err != nil { @@ -32,7 +32,9 @@ func loadPlugin(file string) (common.GocannonPlugin, error) { return nil, ErrPluginInterface } - fmt.Printf("Plugin %s loaded.\n", gocannonPlugin.GetName()) + if !silentOutput { + fmt.Printf("Plugin %s loaded.\n", gocannonPlugin.GetName()) + } return gocannonPlugin, nil } diff --git a/reqlog/request.go b/reqlog/request.go index ca276d0..9ecaf07 100644 --- a/reqlog/request.go +++ b/reqlog/request.go @@ -1,10 +1,13 @@ package reqlog import ( + "encoding/json" + "fmt" "sort" "time" "github.com/kffl/gocannon/rescodes" + "gopkg.in/yaml.v2" ) type request struct { @@ -76,9 +79,27 @@ func (r *requestLogCollector) CalculateStats( return err } -func (r *requestLogCollector) PrintReport() { - r.results.print() - r.resCodes.PrintRescodes() +func (r *requestLogCollector) PrintReport(format string) { + if format == "default" { + r.results.print() + r.resCodes.PrintRescodes() + } else { + obj := struct { + Report *fullStatistics + ResCodes map[int]int64 + }{ + r.results, + r.resCodes.AsMap(), + } + var output []byte + if format == "json" { + output, _ = json.MarshalIndent(obj, "", " ") + } + if format == "yaml" { + output, _ = yaml.Marshal(obj) + } + fmt.Printf("%s", output) + } } func (r *requestLogCollector) saveRawData(outputFile string) error { @@ -90,15 +111,15 @@ func (r *requestLogCollector) saveRawData(outputFile string) error { } func (r *requestLogCollector) GetReqCount() int64 { - return r.results.reqCount + return int64(r.results.Summary.Count) } func (r *requestLogCollector) GetReqPerSec() float64 { - return r.results.reqPerSec + return r.results.Summary.ReqPerSec } func (r *requestLogCollector) GetLatencyAvg() float64 { - return r.results.summary.latencyAVG + return r.results.Summary.LatencyAVG } func (r *requestLogCollector) saveResCodes() { diff --git a/reqlog/stats.go b/reqlog/stats.go index 8f2dee7..ef9fcdb 100644 --- a/reqlog/stats.go +++ b/reqlog/stats.go @@ -10,19 +10,18 @@ var percentiles = []float64{50, 75, 90, 99} type requestLatencies []int64 type statistics struct { - count int - latencyAVG float64 - latencyPercentiles []int64 + Count int + LatencyAVG float64 + LatencyPercentiles []int64 + ReqPerSec float64 } type intervalStatistics []statistics type fullStatistics struct { - summary statistics - detailed intervalStatistics - interval time.Duration - reqCount int64 - reqPerSec float64 + Summary statistics + Detailed intervalStatistics + Interval time.Duration } func (sortedReqs *flatRequestLog) calculateStats( @@ -30,7 +29,7 @@ func (sortedReqs *flatRequestLog) calculateStats( stop int64, intervalDuration time.Duration, ) fullStatistics { - summaryStats := sortedReqs.calculateIntervalStats() + summaryStats := sortedReqs.calculateIntervalStats(stop - start) var detailedStats intervalStatistics @@ -47,17 +46,14 @@ func (sortedReqs *flatRequestLog) calculateStats( detailedStats = append( detailedStats, - slicedRequests.calculateIntervalStats(), + slicedRequests.calculateIntervalStats(int64(intervalDuration)), ) } - reqCount := int64(len(*sortedReqs)) - reqPerSec := float64(reqCount) / float64((stop-start)/int64(time.Second)) - - return fullStatistics{summaryStats, detailedStats, intervalDuration, reqCount, reqPerSec} + return fullStatistics{summaryStats, detailedStats, intervalDuration} } -func (sortedReqs *flatRequestLog) calculateIntervalStats() statistics { +func (sortedReqs *flatRequestLog) calculateIntervalStats(timespan int64) statistics { latencies := make(requestLatencies, 0, len(*sortedReqs)) for i := 0; i < len(*sortedReqs); i++ { @@ -69,11 +65,13 @@ func (sortedReqs *flatRequestLog) calculateIntervalStats() statistics { latencies.sort() for _, p := range percentiles { - r.latencyPercentiles = append(r.latencyPercentiles, latencies.calculatePercentile(p)) + r.LatencyPercentiles = append(r.LatencyPercentiles, latencies.calculatePercentile(p)) } - r.count = len(latencies) - r.latencyAVG = latencies.calculateAVG() + c := len(latencies) + r.Count = c + r.LatencyAVG = latencies.calculateAVG() + r.ReqPerSec = float64(c) / (float64(timespan) / float64(time.Second)) return r } diff --git a/reqlog/stats_print.go b/reqlog/stats_print.go index f89d73a..24e742e 100644 --- a/reqlog/stats_print.go +++ b/reqlog/stats_print.go @@ -34,26 +34,26 @@ func formatLatencyI64(latency int64) string { } func (s *statistics) print() { - fmt.Printf("%10d %13v", s.count, formatLatency(s.latencyAVG)) - for _, v := range s.latencyPercentiles { + fmt.Printf("%10d %13v", s.Count, formatLatency(s.LatencyAVG)) + for _, v := range s.LatencyPercentiles { fmt.Printf(" %11v", formatLatencyI64(v)) } fmt.Printf("\n") } func (s *fullStatistics) print() { - fmt.Printf("Interval stats: (interval = %v) \n", s.interval) + fmt.Printf("Interval stats: (interval = %v) \n", s.Interval) printStatsHeader() - for _, stats := range s.detailed { + for _, stats := range s.Detailed { stats.print() } fmt.Println("----------") - s.summary.print() + s.Summary.print() } func (s *fullStatistics) GetReqCount() int64 { - return s.reqCount + return int64(s.Summary.Count) } diff --git a/reqlog/stats_test.go b/reqlog/stats_test.go index e002f57..3d5f5e8 100644 --- a/reqlog/stats_test.go +++ b/reqlog/stats_test.go @@ -38,11 +38,11 @@ func TestSort(t *testing.T) { func TestCalculateIntervalStatsEmpty(t *testing.T) { reqs := flatRequestLog{} - stats := reqs.calculateIntervalStats() + stats := reqs.calculateIntervalStats(250000000) - assert.Equal(t, 0, stats.count) - assert.Equal(t, float64(-1), stats.latencyAVG) - assert.ElementsMatch(t, stats.latencyPercentiles, []int64{-1, -1, -1, -1}) + assert.Equal(t, 0, stats.Count) + assert.Equal(t, float64(-1), stats.LatencyAVG) + assert.ElementsMatch(t, stats.LatencyPercentiles, []int64{-1, -1, -1, -1}) } func TestCalculateIntervalStatsPopulated(t *testing.T) { @@ -54,11 +54,12 @@ func TestCalculateIntervalStatsPopulated(t *testing.T) { {200, 534, 535}, } - stats := reqs.calculateIntervalStats() + stats := reqs.calculateIntervalStats(1000) - assert.Equal(t, 5, stats.count) - assert.Equal(t, float64(40.6), stats.latencyAVG) - assert.ElementsMatch(t, stats.latencyPercentiles, []int64{1, 100, 100, 100}) + assert.Equal(t, 5, stats.Count) + assert.Equal(t, float64(40.6), stats.LatencyAVG) + assert.Equal(t, float64(5000000), stats.ReqPerSec) + assert.ElementsMatch(t, stats.LatencyPercentiles, []int64{1, 100, 100, 100}) } func TestCalculateStats(t *testing.T) { @@ -71,14 +72,14 @@ func TestCalculateStats(t *testing.T) { } full := reqs.calculateStats(100, 600, 100) - summary := full.summary - detailed := full.detailed + summary := full.Summary + detailed := full.Detailed - assert.Equal(t, 5, summary.count) + assert.Equal(t, 5, summary.Count) assert.Len(t, detailed, 5, "5 intervals should fit in the specified range") assert.Equal( t, - detailed[4].count, + detailed[4].Count, 1, "request should be assigned to an interval based on the response timestamp", ) diff --git a/rescodes/rescodes.go b/rescodes/rescodes.go index 4cd79d9..f3f7246 100644 --- a/rescodes/rescodes.go +++ b/rescodes/rescodes.go @@ -32,3 +32,14 @@ func (r *Rescodes) PrintRescodes() { fmt.Printf("Requests ended with timeout/socket error: %d\n", r[0]) } } + +func (r *Rescodes) AsMap() map[int]int64 { + m := make(map[int]int64) + for code := 1; code < 600; code++ { + hits := r[code] + if hits > 0 { + m[code] = hits + } + } + return m +} diff --git a/stats_collector.go b/stats_collector.go index 4b5b91a..3f0dacf 100644 --- a/stats_collector.go +++ b/stats_collector.go @@ -11,7 +11,7 @@ import ( type statsCollector interface { RecordResponse(conn int, code int, start int64, end int64) CalculateStats(start int64, stop int64, interval time.Duration, fileName string) error - PrintReport() + PrintReport(format string) GetReqCount() int64 GetReqPerSec() float64 GetLatencyAvg() float64 From 6b7dbc2113b3e9befc412378769a6e1e2a88873d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kuffel?= Date: Mon, 15 Nov 2021 19:49:51 +0100 Subject: [PATCH 2/3] docs: update help flag output --- README.md | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index bef3845..a1e9568 100644 --- a/README.md +++ b/README.md @@ -12,26 +12,32 @@ usage: gocannon [] Flags: --help Show context-sensitive help (also try --help-long and --help-man). - -d, --duration=10s Load test duration - -c, --connections=50 Maximum number of concurrent connections - -t, --timeout=200ms HTTP client timeout - -m, --mode="reqlog" Statistics collection mode: reqlog (logs each request) or hist - (stores histogram of completed requests latencies) - -o, --output=file.csv File to save the request log in CSV format (reqlog mode) or a - text file with raw histogram data (hist mode) - -i, --interval=250ms Interval for statistics calculation (reqlog mode) - --preallocate=1000 Number of requests in req log to preallocate memory for per - connection (reqlog mode) - --method=GET The HTTP request method (GET, POST, PUT, PATCH or DELETE) - -b, --body="{data..." HTTP request body - -h, --header="k:v" ... HTTP request header(s). You can set more than one header by - repeating this flag. - --trust-all Omit SSL certificate validation - --plugin=/to/p.so Plugin to run Gocannon with + -d, --duration=10s Load test duration. + -c, --connections=50 Maximum number of concurrent connections. + -t, --timeout=200ms HTTP client timeout. + -m, --mode="reqlog" Statistics collection mode: reqlog (logs each request) + or hist (stores histogram of completed requests + latencies). + -o, --output=file.csv File to save the request log in CSV format (reqlog + mode) or a text file with raw histogram data (hist + mode). + -i, --interval=250ms Interval for statistics calculation (reqlog mode). + --preallocate=1000 Number of requests in req log to preallocate memory + for per connection (reqlog mode). + --method=GET The HTTP request method (GET, POST, PUT, PATCH or + DELETE). + -b, --body="{data..." HTTP request body. + --trust-all Omit SSL certificate validation. + -f, --format=default Load test report format. Either 'default' (verbose), + 'json' or 'yaml'. When json or yaml is specified, + apart from the load test results, no additional info + will be written to std out. + --plugin=/to/p.so Plugin to run Gocannon with (path to .so file). --version Show application version. Args: - HTTP target URL with port (i.e. http://localhost:80/test or https://host:443/x) + HTTP target URL with port (i.e. http://localhost:80/test or + https://host:443/x) ``` Below is an example of a load test conducted using gocannon against an Express.js server (notice the performance improvement over time under sustained load): From 9bcc02fc5d3fc528dc4d8873396242b1615fdb61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kuffel?= Date: Mon, 15 Nov 2021 19:50:56 +0100 Subject: [PATCH 3/3] chore: bump gocannon version --- CITATION.cff | 2 +- args.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index b9f11ab..0668591 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -6,4 +6,4 @@ license: "Apache-2.0" message: "Please use the following citation metadata." repository-code: "https://github.com/kffl/gocannon" title: "Gocannon - Performance-focused HTTP benchmarking tool" -version: "1.0.0" +version: "1.1.0" diff --git a/args.go b/args.go index b1e8cf3..372b9e3 100644 --- a/args.go +++ b/args.go @@ -62,7 +62,7 @@ var config = common.Config{ } func parseArgs() error { - app.Version("1.0.0") + app.Version("1.1.0") _, err := app.Parse(os.Args[1:]) return err }