diff --git a/.gitignore b/.gitignore index a6fdc84..00b42c0 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +ali-debug.log # IDE .idea diff --git a/attacker/attacker.go b/attacker/attacker.go index 046cefb..561d512 100644 --- a/attacker/attacker.go +++ b/attacker/attacker.go @@ -1,9 +1,12 @@ package attacker import ( + "context" "net/http" "time" + "github.com/k0kubun/pp" + vegeta "github.com/tsenart/vegeta/v12/lib" ) @@ -32,7 +35,8 @@ type Result struct { // Attack keeps the request running for the specified period of time. // Results are sent to the given channel as soon as they arrive. // When the attack is over, it gives back final statistics. -func Attack(target string, resCh chan *Result, opts Options) *Metrics { +func Attack(ctx context.Context, target string, resCh chan *Result, opts Options) *Metrics { + pp.Println("start attacking") if opts.Rate == 0 { opts.Rate = defaultRate } @@ -53,16 +57,23 @@ func Attack(target string, resCh chan *Result, opts Options) *Metrics { attacker := vegeta.NewAttacker() var metrics vegeta.Metrics + for res := range attacker.Attack(targeter, rate, opts.Duration, "main") { + // TODO: Stop if ctx is expired. + /*if <-ctx.Done() { + return newMetrics(&metrics) + }*/ resCh <- &Result{Latency: res.Latency} metrics.Add(res) } metrics.Close() + pp.Println("vegeta metrics", metrics) return newMetrics(&metrics) } type ( + // Metrics wraps vegeta.Metrics to avoid dependency on it. Metrics struct { Latencies LatencyMetrics } diff --git a/go.mod b/go.mod index d5ba24b..7f7e481 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,9 @@ module github.com/nakabonne/ali go 1.15 require ( + github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect + github.com/k0kubun/pp v3.0.1+incompatible + github.com/mattn/go-colorable v0.1.7 // indirect github.com/mum4k/termdash v0.12.2 github.com/spf13/pflag v1.0.5 github.com/tsenart/vegeta/v12 v12.8.3 diff --git a/go.sum b/go.sum index 39d054e..288f42d 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/alecthomas/jsonschema v0.0.0-20180308105923-f2c93856175a/go.mod h1:qp github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b h1:AP/Y7sqYicnjGDfD5VcY4CIfh1hRXBUavxrvELjTiOE= github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q= github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-gk v0.0.0-20140819190930-201884a44051 h1:ByJUvQYyTtNNCVfYNM48q6uYUT4fAlN0wNmd3th4BSo= github.com/dgryski/go-gk v0.0.0-20140819190930-201884a44051/go.mod h1:qm+vckxRlDt0aOla0RYJJVeqHZlWfOm2UIxHaqPB46E= github.com/dgryski/go-lttb v0.0.0-20180810165845-318fcdf10a77/go.mod h1:Va5MyIzkU0rAM92tn3hb3Anb7oz7KcnixF49+2wOMe4= @@ -30,11 +32,21 @@ github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/influxdata/tdigest v0.0.0-20180711151920-a7d76c6f093a h1:vMqgISSVkIqWxCIZs8m1L4096temR7IbYyNdMiBxSPA= github.com/influxdata/tdigest v0.0.0-20180711151920-a7d76c6f093a/go.mod h1:9GkyshztGufsdPQWjH+ifgnIr3xNUL5syI70g2dzU1o= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40= +github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -43,13 +55,22 @@ github.com/mum4k/termdash v0.12.2 h1:S2frz71OrXUKIVVZ3snYBEzyYlUNRTu0ElV6d5Pf6gI github.com/mum4k/termdash v0.12.2/go.mod h1:haerPCSO0U8pehROAecmuOHDF+2UXw2KaCTxdWooDFE= github.com/nsf/termbox-go v0.0.0-20200204031403-4d2b513ad8be h1:yzmWtPyxEUIKdZg4RcPq64MfS8NA6A5fNOJgYhpR9EQ= github.com/nsf/termbox-go v0.0.0-20200204031403-4d2b513ad8be/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25 h1:7z3LSn867ex6VSaahyKadf4WtSsJIgne6A1WLOAGM8A= github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/tsenart/go-tsz v0.0.0-20180814232043-cdeb9e1e981e/go.mod h1:SWZznP1z5Ki7hDT2ioqiFKEse8K9tU2OUvaRI0NeGQo= github.com/tsenart/vegeta/v12 v12.8.3 h1:UEsDkSrEJojMKW/xr7KUv4H/bYykX+V48KCsPZPqEfk= github.com/tsenart/vegeta/v12 v12.8.3/go.mod h1:ZiJtwLn/9M4fTPdMY7bdbIeyNeFVE8/AHbWFqCsUuho= +github.com/yudai/pp v1.3.0 h1:8qxHWJnYPF9HC9nq1U4N/8toJNBWO4DxaMpDlzrcE6I= +github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -59,8 +80,13 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/gui/widgets.go b/gui/widgets.go index 378bf4f..6112d36 100644 --- a/gui/widgets.go +++ b/gui/widgets.go @@ -2,14 +2,16 @@ package gui import ( "context" - "math" "time" + "github.com/k0kubun/pp" "github.com/mum4k/termdash/cell" "github.com/mum4k/termdash/container" "github.com/mum4k/termdash/widgets/button" "github.com/mum4k/termdash/widgets/linechart" "github.com/mum4k/termdash/widgets/textinput" + + "github.com/nakabonne/ali/attacker" ) // redrawInterval is how often termdash redraws the screen. @@ -38,12 +40,6 @@ func newWidgets(ctx context.Context, c *container.Container) (*widgets, error) { // newLineChart returns a line plotChart that displays a heartbeat-like progression. func newLineChart(ctx context.Context) (*linechart.LineChart, error) { - var inputs []float64 - for i := 0; i < 100; i++ { - v := math.Pow(math.Sin(float64(i)), 63) * math.Sin(float64(i)+1.5) * 8 - inputs = append(inputs, v) - } - lc, err := linechart.New( linechart.AxesCellOpts(cell.FgColor(cell.ColorRed)), linechart.YLabelCellOpts(cell.FgColor(cell.ColorGreen)), @@ -52,41 +48,33 @@ func newLineChart(ctx context.Context) (*linechart.LineChart, error) { if err != nil { return nil, err } - step := 0 - go periodic(ctx, redrawInterval/3, func() error { - step = (step + 1) % len(inputs) - return lc.Series("heartbeat", rotateFloats(inputs, step), - linechart.SeriesCellOpts(cell.FgColor(cell.ColorNumber(87))), - linechart.SeriesXLabels(map[int]string{ - 0: "zero", - }), - ) - }) + + resultCh := make(chan *attacker.Result) + go func() { + // TODO: Enalble to poplulate from input + metrics := attacker.Attack(ctx, "http://34.84.111.163:9898", resultCh, attacker.Options{Rate: 50, Duration: 10 * time.Second}) + pp.Println(metrics) + }() + go redrawChart(ctx, lc, resultCh) + return lc, nil } -// periodic executes the provided closure periodically every interval. -// Exits when the context expires. -func periodic(ctx context.Context, interval time.Duration, fn func() error) { - ticker := time.NewTicker(interval) - defer ticker.Stop() +func redrawChart(ctx context.Context, lineChart *linechart.LineChart, resultCh chan *attacker.Result) { + values := []float64{} for { select { - case <-ticker.C: - if err := fn(); err != nil { - panic(err) - } case <-ctx.Done(): return + case res := <-resultCh: + pp.Println("latency", res.Latency/time.Millisecond) + values = append(values, float64(res.Latency/time.Millisecond)) + lineChart.Series("plot", values, + linechart.SeriesCellOpts(cell.FgColor(cell.ColorNumber(87))), + linechart.SeriesXLabels(map[int]string{ + 0: "zero", + }), + ) } } } - -// rotateFloats returns a new slice with inputs rotated by step. -// I.e. for a step of one: -// inputs[0] -> inputs[len(inputs)-1] -// inputs[1] -> inputs[0] -// And so on. -func rotateFloats(inputs []float64, step int) []float64 { - return append(inputs[step:], inputs[:step]...) -} diff --git a/main.go b/main.go index 1e469bb..3c555f2 100644 --- a/main.go +++ b/main.go @@ -4,9 +4,12 @@ import ( "errors" "fmt" "io" + "io/ioutil" "os" + "path/filepath" "runtime" + "github.com/k0kubun/pp" flag "github.com/spf13/pflag" "github.com/nakabonne/ali/gui" @@ -55,6 +58,7 @@ func (c *cli) run() int { fmt.Fprintf(c.stderr, "version=%s, commit=%s, buildDate=%s, os=%s, arch=%s\n", version, commit, date, runtime.GOOS, runtime.GOARCH) return 0 } + setDebug(nil, c.debug) if err := gui.Run(); err != nil { fmt.Fprintf(c.stderr, "failed to start application: %s", err.Error()) @@ -63,3 +67,18 @@ func (c *cli) run() int { return 0 } + +// Makes a new file under the working directory only when debug use. +func setDebug(w io.Writer, debug bool) { + if !debug { + pp.SetDefaultOutput(ioutil.Discard) + } + if w == nil { + var err error + w, err = os.OpenFile(filepath.Join(".", "ali-debug.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + panic(err) + } + } + pp.SetDefaultOutput(w) +}