diff --git a/cmd/cassowary/cli.go b/cmd/cassowary/cli.go index 742f71a..2226007 100644 --- a/cmd/cassowary/cli.go +++ b/cmd/cassowary/cli.go @@ -203,6 +203,7 @@ func validateCLI(c *cli.Context) error { TLSConfig: tlsConfig, PromURL: c.String("prompushgwurl"), Cloudwatch: c.Bool("cloudwatch"), + Boxplot: c.Bool("boxplot"), Histogram: c.Bool("histogram"), ExportMetrics: c.Bool("json-metrics"), ExportMetricsFile: c.String("json-metrics-file"), @@ -283,6 +284,11 @@ func runCLI(args []string) { Aliases: []string{"json-metrics"}, Usage: "outputs metrics to a json file by setting flag to true", }, + &cli.BoolFlag{ + Name: "b", + Aliases: []string{"boxplot"}, + Usage: "enable to generate a boxplot as png", + }, &cli.BoolFlag{ Aliases: []string{"histogram"}, Usage: "enable to generate a histogram as png", diff --git a/go.mod b/go.mod index 8c5186e..90ccc71 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,6 @@ require ( github.com/mattn/go-isatty v0.0.10 // indirect github.com/prometheus/client_golang v1.3.0 github.com/schollz/progressbar v1.0.0 - github.com/urfave/cli/v2 v2.2.0 + github.com/urfave/cli/v2 v2.3.0 gonum.org/v1/plot v0.8.1 ) diff --git a/go.sum b/go.sum index 1bd2148..3156139 100644 --- a/go.sum +++ b/go.sum @@ -108,8 +108,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= -github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -171,5 +171,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/pkg/client/boxplot.go b/pkg/client/boxplot.go new file mode 100644 index 0000000..50664ea --- /dev/null +++ b/pkg/client/boxplot.go @@ -0,0 +1,33 @@ +package client + +import ( + "gonum.org/v1/plot" + "gonum.org/v1/plot/plotter" + "gonum.org/v1/plot/vg" +) + +// PlotBoxplot outputs a boxplot png +func (c *Cassowary) PlotBoxplot(durations []float64) error { + p, err := plot.New() + if err != nil { + panic(err) + } + p.Title.Text = "Box Plot" + + vs := make(plotter.Values, len(durations)) + for i, d := range durations { + vs[i] = d + } + + box, err := plotter.NewBoxPlot(vg.Length(20), 0.0, vs) + if err != nil { + panic(err) + } + p.Add(box) + + if err := p.Save(512, 512, "boxplot.png"); err != nil { + return err + } + + return nil +} diff --git a/pkg/client/boxplot_test.go b/pkg/client/boxplot_test.go new file mode 100644 index 0000000..0772439 --- /dev/null +++ b/pkg/client/boxplot_test.go @@ -0,0 +1,31 @@ +package client + +import ( + "math/rand" + "os" + "testing" + "time" +) + +func TestBoxPlot(t *testing.T) { + num := 100 + res := make([]float64, num) + filename := "boxplot.png" + cass := &Cassowary{} + rand.Seed(time.Now().UnixNano()) + + for i := 0; i < num; i++ { + res = append(res, rand.Float64()*(200.0-10.0)) + } + + err := cass.PlotBoxplot(res) + if err != nil { + t.Errorf("Expected ok but got: %v", err) + } + _, err = os.Stat(filename) + if os.IsNotExist(err) { + t.Errorf("Expected %s in current dir but got: %v", filename, err) + } + + _ = os.Remove(filename) +} diff --git a/pkg/client/histogram.go b/pkg/client/histogram.go index e849b1b..e0d15f0 100644 --- a/pkg/client/histogram.go +++ b/pkg/client/histogram.go @@ -13,7 +13,7 @@ func getBins(dataPoints int) int { return b } -// PlotHistogram blabla +// PlotHistogram outputs a histogram png func (c *Cassowary) PlotHistogram(durations []float64) error { bins := getBins(len(durations)) diff --git a/pkg/client/load.go b/pkg/client/load.go index 59a8137..4a7ace1 100644 --- a/pkg/client/load.go +++ b/pkg/client/load.go @@ -308,7 +308,16 @@ func (c *Cassowary) Coordinate() (ResultMetrics, error) { // output histogram if c.Histogram { - _ = c.PlotHistogram(totalDur) + err := c.PlotHistogram(totalDur) + if err != nil { + } + } + + // output boxplot + if c.Boxplot { + err := c.PlotBoxplot(totalDur) + if err != nil { + } } return outPut, nil } diff --git a/pkg/client/types.go b/pkg/client/types.go index f584d6d..71389af 100644 --- a/pkg/client/types.go +++ b/pkg/client/types.go @@ -20,6 +20,7 @@ type Cassowary struct { PromExport bool Cloudwatch bool Histogram bool + Boxplot bool TLSConfig *tls.Config PromURL string RequestHeader []string