Skip to content

Commit

Permalink
Add progress donut
Browse files Browse the repository at this point in the history
  • Loading branch information
nakabonne committed Sep 14, 2020
1 parent e2b1d39 commit 7fa23dd
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 15 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,25 @@ Another load testing tool, inspired by [vegeta](https://github.com/tsenart/veget

Executables are available through the [releases page](https://github.com/nakabonne/ali/releases).

## Usage

## Features
#### Mouse support

#### Visualizes the attack progress

[gif animations]

## Alternatives
## Built with
- [mum4k/termdash](https://github.com/mum4k/termdash/wiki/Termbox-API)
- [nsf/termbox-go](https://github.com/nsf/termbox-go)
- [vegeta](https://github.com/tsenart/vegeta)


## LoadMap
- Plot Bytes In and Bytes Out (Press `Ctrl-w` to switch between charts)
- Plot status codes
- Better UI

## Alternatives

12 changes: 6 additions & 6 deletions attacker/attacker.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (
)

const (
defaultRate = 50
defaultDuration = 10 * time.Second
defaultMethod = http.MethodGet
DefaultRate = 50
DefaultDuration = 10 * time.Second
DefaultMethod = http.MethodGet
)

type Attacker interface {
Expand Down Expand Up @@ -44,13 +44,13 @@ func Attack(ctx context.Context, target string, resCh chan *Result, opts Options
return nil
}
if opts.Rate == 0 {
opts.Rate = defaultRate
opts.Rate = DefaultRate
}
if opts.Duration == 0 {
opts.Duration = defaultDuration
opts.Duration = DefaultDuration
}
if opts.Method == "" {
opts.Method = defaultMethod
opts.Method = DefaultMethod
}
if opts.Attacker == nil {
opts.Attacker = vegeta.NewAttacker()
Expand Down
25 changes: 23 additions & 2 deletions gui/drawer.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import (

type drawer struct {
widgets *widgets
chartsCh chan *attacker.Result
chartCh chan *attacker.Result
donutCh chan bool
reportCh chan string

// aims to avoid to perform multiple `redrawChart`.
Expand All @@ -31,10 +32,12 @@ L:
select {
case <-ctx.Done():
break L
case res := <-d.chartsCh:
case res := <-d.chartCh:
if res.End {
d.donutCh <- true
break L
}
d.donutCh <- false
values = append(values, float64(res.Latency/time.Millisecond))
d.widgets.latencyChart.Series("latency", values,
linechart.SeriesCellOpts(cell.FgColor(cell.ColorNumber(87))),
Expand All @@ -47,6 +50,24 @@ L:
d.chartDrawing = false
}

func (d *drawer) redrawDonut(ctx context.Context, maxSize int) {
var count float64
size := float64(maxSize)
d.widgets.progressDonut.Percent(0)
for {
select {
case <-ctx.Done():
return
case end := <-d.donutCh:
if end {
return
}
count++
d.widgets.progressDonut.Percent(int(count / size * 100))
}
}
}

func (d *drawer) redrawReport(ctx context.Context) {
for {
select {
Expand Down
22 changes: 16 additions & 6 deletions gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ func Run() error {

d := &drawer{
widgets: w,
chartsCh: make(chan *attacker.Result),
chartCh: make(chan *attacker.Result),
donutCh: make(chan bool),
reportCh: make(chan string),
}
go d.redrawReport(ctx)
Expand All @@ -76,7 +77,8 @@ func gridLayout(w *widgets) ([]container.Option, error) {
grid.ColWidthPerc(49, grid.Widget(w.bodyInput, container.Border(linestyle.None))),
),
),
grid.ColWidthPerc(69, grid.Widget(w.reportText, container.Border(linestyle.Light), container.BorderTitle("Report"))),
grid.ColWidthPerc(35, grid.Widget(w.reportText, container.Border(linestyle.Light), container.BorderTitle("Report"))),
grid.ColWidthPerc(34, grid.Widget(w.progressDonut, container.Border(linestyle.Light), container.BorderTitle("Progress"))),
)
raw3 := grid.RowHeightFixed(1,
grid.ColWidthFixed(100, grid.Widget(w.navi, container.Border(linestyle.Light))),
Expand Down Expand Up @@ -112,21 +114,26 @@ func attack(ctx context.Context, dr *drawer) {
rate int
duration time.Duration
method string
body string
err error
)
target = dr.widgets.urlInput.Read()
if _, err := url.ParseRequestURI(target); err != nil {
dr.reportCh <- fmt.Sprintf("Bad URL: %v", err)
return
}
if s := dr.widgets.rateLimitInput.Read(); s != "" {
if s := dr.widgets.rateLimitInput.Read(); s == "" {
rate = attacker.DefaultRate
} else {
rate, err = strconv.Atoi(s)
if err != nil {
dr.reportCh <- fmt.Sprintf("Given rate limit %q isn't integer: %v", s, err)
return
}
}
if s := dr.widgets.durationInput.Read(); s != "" {
if s := dr.widgets.durationInput.Read(); s == "" {
duration = attacker.DefaultDuration
} else {
duration, err = time.ParseDuration(s)
if err != nil {
dr.reportCh <- fmt.Sprintf("Unparseable duration %q: %v", s, err)
Expand All @@ -139,13 +146,16 @@ func attack(ctx context.Context, dr *drawer) {
return
}
}
body = dr.widgets.bodyInput.Read()
requestNum := rate * int(duration/time.Second)

// To pre-allocate, run redrawChart on a per-attack basis.
go dr.redrawChart(ctx, requestNum)
go dr.redrawDonut(ctx, requestNum)
go func(ctx context.Context, d *drawer, t string, r int, du time.Duration) {
metrics := attacker.Attack(ctx, t, d.chartsCh, attacker.Options{Rate: r, Duration: du, Method: method})
metrics := attacker.Attack(ctx, t, d.chartCh, attacker.Options{Rate: r, Duration: du, Method: method, Body: []byte(body)})
d.reportCh <- metrics.String()
d.chartsCh <- &attacker.Result{End: true}
d.chartCh <- &attacker.Result{End: true}
}(ctx, dr, target, rate, duration)
}

Expand Down
13 changes: 13 additions & 0 deletions gui/widgets.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"time"

"github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/widgets/donut"
"github.com/mum4k/termdash/widgets/linechart"
"github.com/mum4k/termdash/widgets/text"
"github.com/mum4k/termdash/widgets/textinput"
Expand All @@ -22,6 +23,7 @@ type widgets struct {
bodyInput *textinput.TextInput
latencyChart *linechart.LineChart
reportText *text.Text
progressDonut *donut.Donut
navi *text.Text
}

Expand Down Expand Up @@ -58,6 +60,10 @@ func newWidgets() (*widgets, error) {
if err != nil {
return nil, err
}
progressDonut, err := newDonut()
if err != nil {
return nil, err
}
return &widgets{
urlInput: urlInput,
rateLimitInput: rateLimitInput,
Expand All @@ -66,6 +72,7 @@ func newWidgets() (*widgets, error) {
bodyInput: bodyInput,
latencyChart: latencyChart,
reportText: reportText,
progressDonut: progressDonut,
navi: navi,
}, nil
}
Expand Down Expand Up @@ -96,3 +103,9 @@ func newTextInput(label, placeHolder string, cells int) (*textinput.TextInput, e
textinput.PlaceHolder(placeHolder),
)
}

func newDonut() (*donut.Donut, error) {
return donut.New(
donut.CellOpts(cell.FgColor(cell.ColorGreen)),
)
}

0 comments on commit 7fa23dd

Please sign in to comment.