diff --git a/gui/drawer.go b/gui/drawer.go index 81aaf69..d8ac7ab 100644 --- a/gui/drawer.go +++ b/gui/drawer.go @@ -23,7 +23,6 @@ type drawer struct { gridOpts *gridOpts chartCh chan *attacker.Result - gaugeCh chan struct{} metricsCh chan *attacker.Metrics doneCh chan struct{} @@ -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) @@ -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) @@ -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) + 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) } } } diff --git a/gui/drawer_test.go b/gui/drawer_test.go index f78957d..a84a8b9 100644 --- a/gui/drawer_test.go +++ b/gui/drawer_test.go @@ -21,6 +21,7 @@ func TestAppendChartValues(t *testing.T) { tests := []struct { name string results []*attacker.Result + duration time.Duration progressGauge Gauge }{ { @@ -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() @@ -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() @@ -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 } @@ -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{ @@ -155,7 +156,7 @@ func TestRedrawGauge(t *testing.T) { tests := []struct { name string - size int + size time.Duration count int gauge Gauge }{ @@ -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 }(), }, @@ -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{}{} - } }) } } diff --git a/gui/gui.go b/gui/gui.go index 660f3fd..52e9a3b 100644 --- a/gui/gui.go +++ b/gui/gui.go @@ -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{}, diff --git a/gui/keybinds.go b/gui/keybinds.go index 9b3f797..df2ebd5 100644 --- a/gui/keybinds.go +++ b/gui/keybinds.go @@ -2,7 +2,6 @@ package gui import ( "context" - "time" "github.com/mum4k/termdash/container" "github.com/mum4k/termdash/keyboard" @@ -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