Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Progress gauge to reach 100% #86

Merged
merged 2 commits into from
Nov 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions gui/drawer.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ type drawer struct {
gridOpts *gridOpts

chartCh chan *attacker.Result
gaugeCh chan struct{}
metricsCh chan *attacker.Metrics
doneCh chan struct{}

Expand All @@ -45,13 +44,14 @@ type values struct {

// appendChartValues appends entities as soon as a result arrives.
// Given maxSize, then it can be pre-allocated.
func (d *drawer) appendChartValues(ctx context.Context, maxSize int) {
func (d *drawer) appendChartValues(ctx context.Context, rate int, duration time.Duration) {
// TODO: Change how to stop `redrawGauge`.
// We currently use this way to ensure to stop `redrawGauge` after the increase process is complete.
// But, it's preferable to stop goroutine where it's generated.
maxSize := rate * int(duration/time.Second)
child, cancel := context.WithCancel(ctx)
defer cancel()
go d.redrawGauge(child, maxSize)
go d.redrawGauge(child, duration)

d.chartValues.latencies = make([]float64, 0, maxSize)
d.chartValues.p50 = make([]float64, 0, maxSize)
Expand All @@ -74,8 +74,6 @@ L:
if res == nil {
continue
}
// Increment the gauge.
d.gaugeCh <- struct{}{}

d.mu.Lock()
d.chartValues.latencies = appendValue(d.chartValues.latencies, res.Latency)
Expand Down Expand Up @@ -125,17 +123,26 @@ L:
d.chartDrawing.Store(false)
}

func (d *drawer) redrawGauge(ctx context.Context, maxSize int) {
var count float64
size := float64(maxSize)
func (d *drawer) redrawGauge(ctx context.Context, duration time.Duration) {
ticker := time.NewTicker(redrawInterval)
defer ticker.Stop()

totalTime := float64(duration)
shubham14bajpai marked this conversation as resolved.
Show resolved Hide resolved

d.widgets.progressGauge.Percent(0)
for {
for start := time.Now(); ; {
select {
case <-ctx.Done():
return
case <-d.gaugeCh:
count++
d.widgets.progressGauge.Percent(int(count / size * 100))
case <-ticker.C:
passed := float64(time.Since(start))
percent := int(passed / totalTime * 100)
// as time.Duration is the unit of nanoseconds
// small duration can exceed 100 on slow machines
if percent > 100 {
continue
}
d.widgets.progressGauge.Percent(percent)
}
}
}
Expand Down
17 changes: 7 additions & 10 deletions gui/drawer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func TestAppendChartValues(t *testing.T) {
tests := []struct {
name string
results []*attacker.Result
duration time.Duration
progressGauge Gauge
}{
{
Expand All @@ -34,6 +35,7 @@ func TestAppendChartValues(t *testing.T) {
P99: 990000,
},
},
duration: 1,
progressGauge: func() Gauge {
g := NewMockGauge(ctrl)
g.EXPECT().Percent(gomock.Any()).AnyTimes()
Expand All @@ -58,6 +60,7 @@ func TestAppendChartValues(t *testing.T) {
P99: 1980000,
},
},
duration: 2,
progressGauge: func() Gauge {
g := NewMockGauge(ctrl)
g.EXPECT().Percent(gomock.Any()).AnyTimes()
Expand All @@ -71,11 +74,10 @@ func TestAppendChartValues(t *testing.T) {
d := &drawer{
widgets: &widgets{progressGauge: tt.progressGauge},
chartCh: make(chan *attacker.Result),
gaugeCh: make(chan struct{}, 100),
doneCh: make(chan struct{}),
chartDrawing: atomic.NewBool(false),
}
go d.appendChartValues(ctx, len(tt.results))
go d.appendChartValues(ctx, len(tt.results), tt.duration)
for _, res := range tt.results {
d.chartCh <- res
}
Expand Down Expand Up @@ -129,7 +131,6 @@ func TestRedrawCharts(t *testing.T) {
d := &drawer{
widgets: &widgets{latencyChart: tt.latencyChart, percentilesChart: tt.percentilesChart},
chartCh: make(chan *attacker.Result),
gaugeCh: make(chan struct{}, 100),
doneCh: make(chan struct{}),
chartDrawing: atomic.NewBool(false),
chartValues: values{
Expand All @@ -155,7 +156,7 @@ func TestRedrawGauge(t *testing.T) {

tests := []struct {
name string
size int
size time.Duration
count int
gauge Gauge
}{
Expand All @@ -164,8 +165,8 @@ func TestRedrawGauge(t *testing.T) {
size: 1,
gauge: func() Gauge {
g := NewMockGauge(ctrl)
g.EXPECT().Percent(0)
g.EXPECT().Percent(100)
g.EXPECT().Percent(0, gomock.Any()).AnyTimes()
g.EXPECT().Percent(100, gomock.Any()).AnyTimes()
return g
}(),
},
Expand All @@ -175,12 +176,8 @@ func TestRedrawGauge(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
d := &drawer{
widgets: &widgets{progressGauge: tt.gauge},
gaugeCh: make(chan struct{}),
}
go d.redrawGauge(ctx, tt.size)
for i := 0; i < tt.size; i++ {
d.gaugeCh <- struct{}{}
}
})
}
}
Expand Down
1 change: 0 additions & 1 deletion gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ func run(t terminalapi.Terminal, r runner, targetURL string, opts *attacker.Opti
widgets: w,
gridOpts: gridOpts,
chartCh: make(chan *attacker.Result, 10000),
gaugeCh: make(chan struct{}),
metricsCh: make(chan *attacker.Metrics),
chartDrawing: atomic.NewBool(false),
metrics: &attacker.Metrics{},
Expand Down
4 changes: 1 addition & 3 deletions gui/keybinds.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package gui

import (
"context"
"time"

"github.com/mum4k/termdash/container"
"github.com/mum4k/termdash/keyboard"
Expand Down Expand Up @@ -50,11 +49,10 @@ func attack(ctx context.Context, d *drawer, target string, opts attacker.Options
if d.chartDrawing.Load() {
return
}
requestNum := opts.Rate * int(opts.Duration/time.Second)
d.doneCh = make(chan struct{})

// To initialize, run redrawChart on a per-attack basis.
go d.appendChartValues(ctx, requestNum)
go d.appendChartValues(ctx, opts.Rate, opts.Duration)
go d.redrawCharts(ctx)
go func(ctx context.Context, d *drawer, t string, o attacker.Options) {
attacker.Attack(ctx, t, d.chartCh, d.metricsCh, o) // this blocks until attack finishes
Expand Down