Skip to content

Commit

Permalink
Make redraw interval changeable (#106)
Browse files Browse the repository at this point in the history
  • Loading branch information
nakabonne authored Jun 1, 2021
1 parent e3f49a4 commit d517ba7
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 66 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This guide walks you through how to contribute step-by-step.

1. It's typically best to open a new issue describing the bug or feature you're intending to work on. Even if you think it's relatively minor, it could be helpful to know what people are working on at first.
1. Fork this repository by following [the typical process](https://docs.github.com/en/github/getting-started-with-github/quickstart/fork-a-repo), and setup a new branch to work in.
1. Modify code.
1. Modify code. It requires Go version 1.16 or greater.
1. Run `go fmt` on your code before committing it.
1. If you want to debug your change, just run `go build` then execute it with `./ali`.

45 changes: 23 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,28 +67,29 @@ Usage:
ali [flags] <target URL>
Flags:
-b, --body string A request body to be sent.
-B, --body-file string The path to file whose content will be set as the http request body.
--cacert string PEM ca certificate file
--cert string PEM encoded tls certificate file to use
-c, --connections int Amount of maximum open idle connections per target host (default 10000)
--debug Run in debug mode.
-d, --duration duration The amount of time to issue requests to the targets. Give 0s for an infinite attack. (default 10s)
-H, --header stringArray A request header to be sent. Can be used multiple times to send multiple headers.
--insecure Skip TLS verification
--key string PEM encoded tls private key file to use
--local-addr string Local IP address. (default "0.0.0.0")
-M, --max-body int Max bytes to capture from response bodies. Give -1 for no limit. (default -1)
-W, --max-workers uint Amount of maximum workers to spawn. (default 18446744073709551615)
-m, --method string An HTTP request method for each request. (default "GET")
--no-http2 Don't issue HTTP/2 requests to servers which support it.
-K, --no-keepalive Don't use HTTP persistent connection.
--query-range duration The time range to display data points on the UI (default 1m0s)
-r, --rate int The request rate per second to issue against the targets. Give 0 then it will send requests as fast as possible. (default 50)
--resolvers string Custom DNS resolver addresses; comma-separated list.
-t, --timeout duration The timeout for each request. 0s means to disable timeouts. (default 30s)
-v, --version Print the current version.
-w, --workers uint Amount of initial workers to spawn. (default 10)
-b, --body string A request body to be sent.
-B, --body-file string The path to file whose content will be set as the http request body.
--cacert string PEM ca certificate file
--cert string PEM encoded tls certificate file to use
-c, --connections int Amount of maximum open idle connections per target host (default 10000)
--debug Run in debug mode.
-d, --duration duration The amount of time to issue requests to the targets. Give 0s for an infinite attack. (default 10s)
-H, --header stringArray A request header to be sent. Can be used multiple times to send multiple headers.
--insecure Skip TLS verification
--key string PEM encoded tls private key file to use
--local-addr string Local IP address. (default "0.0.0.0")
-M, --max-body int Max bytes to capture from response bodies. Give -1 for no limit. (default -1)
-W, --max-workers uint Amount of maximum workers to spawn. (default 18446744073709551615)
-m, --method string An HTTP request method for each request. (default "GET")
--no-http2 Don't issue HTTP/2 requests to servers which support it.
-K, --no-keepalive Don't use HTTP persistent connection.
--query-range duration The results within the given time range will be drawn on the charts (default 30s)
-r, --rate int The request rate per second to issue against the targets. Give 0 then it will send requests as fast as possible. (default 50)
--redraw-interval duration The time interval to redraw charts (default 250ms)
--resolvers string Custom DNS resolver addresses; comma-separated list.
-t, --timeout duration The timeout for each request. 0s means to disable timeouts. (default 30s)
-v, --version Print the current version.
-w, --workers uint Amount of initial workers to spawn. (default 10)
Examples:
ali --duration=10m --rate=100 http://host.xz
Expand Down
13 changes: 7 additions & 6 deletions gui/drawer.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import (
// drawer periodically queries data points from the storage and passes them to the termdash API.
type drawer struct {
// specify the data points range to show on the UI
queryRange time.Duration
widgets *widgets
gridOpts *gridOpts
queryRange time.Duration
redrawInterval time.Duration
widgets *widgets
gridOpts *gridOpts

metricsCh chan *attacker.Metrics

Expand All @@ -36,7 +37,7 @@ type drawer struct {

// redrawCharts sets the values held by itself as chart values, at the specified interval as redrawInterval.
func (d *drawer) redrawCharts(ctx context.Context) {
ticker := time.NewTicker(redrawInterval)
ticker := time.NewTicker(d.redrawInterval)
defer ticker.Stop()

d.chartDrawing.Store(true)
Expand Down Expand Up @@ -97,7 +98,7 @@ L:
}

func (d *drawer) redrawGauge(ctx context.Context, duration time.Duration) {
ticker := time.NewTicker(redrawInterval)
ticker := time.NewTicker(d.redrawInterval)
defer ticker.Stop()

totalTime := float64(duration)
Expand Down Expand Up @@ -150,7 +151,7 @@ End: %v`

// redrawMetrics writes the metrics held by itself into the widgets, at the specified interval as redrawInterval.
func (d *drawer) redrawMetrics(ctx context.Context) {
ticker := time.NewTicker(redrawInterval)
ticker := time.NewTicker(d.redrawInterval)
defer ticker.Stop()

for {
Expand Down
11 changes: 7 additions & 4 deletions gui/drawer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ func TestRedrawCharts(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
d := &drawer{
widgets: &widgets{latencyChart: tt.latencyChart, percentilesChart: tt.percentilesChart},
chartDrawing: atomic.NewBool(false),
storage: tt.storage,
redrawInterval: DefaultRedrawInterval,
widgets: &widgets{latencyChart: tt.latencyChart, percentilesChart: tt.percentilesChart},
chartDrawing: atomic.NewBool(false),
storage: tt.storage,
}
go d.redrawCharts(ctx)
})
Expand Down Expand Up @@ -83,7 +84,8 @@ func TestRedrawGauge(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &drawer{
widgets: &widgets{progressGauge: tt.gauge},
redrawInterval: DefaultRedrawInterval,
widgets: &widgets{progressGauge: tt.gauge},
}
go d.redrawGauge(ctx, tt.size)
})
Expand Down Expand Up @@ -195,6 +197,7 @@ End: 2009-11-10T23:00:00Z`, gomock.Any()).AnyTimes()
t.Run(tt.name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
d := &drawer{
redrawInterval: DefaultRedrawInterval,
widgets: &widgets{
latenciesText: tt.latenciesText,
bytesText: tt.bytesText,
Expand Down
28 changes: 16 additions & 12 deletions gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import (

const (
// How often termdash redraws the screen.
redrawInterval = 250 * time.Millisecond
DefaultQueryRange = 30 * time.Second
rootID = "root"
chartID = "chart"
DefaultRedrawInterval = 250 * time.Millisecond
DefaultQueryRange = 30 * time.Second
rootID = "root"
chartID = "chart"
)

type Option struct {
Expand Down Expand Up @@ -74,22 +74,26 @@ func run(t terminalapi.Terminal, r runner, targetURL string, storage storage.Rea
if opts.QueryRange == 0 {
opts.QueryRange = DefaultQueryRange
}
if opts.RedrawInternal == 0 {
opts.RedrawInternal = DefaultRedrawInterval
}

d := &drawer{
queryRange: opts.QueryRange,
widgets: w,
gridOpts: gridOpts,
metricsCh: make(chan *attacker.Metrics),
chartDrawing: atomic.NewBool(false),
metrics: &attacker.Metrics{},
storage: storage,
queryRange: opts.QueryRange,
redrawInterval: opts.RedrawInternal,
widgets: w,
gridOpts: gridOpts,
metricsCh: make(chan *attacker.Metrics),
chartDrawing: atomic.NewBool(false),
metrics: &attacker.Metrics{},
storage: storage,
}
go d.updateMetrics(ctx)
go d.redrawMetrics(ctx)

k := keybinds(ctx, cancel, c, d, a)

return r(ctx, t, c, termdash.KeyboardSubscriber(k), termdash.RedrawInterval(redrawInterval))
return r(ctx, t, c, termdash.KeyboardSubscriber(k), termdash.RedrawInterval(opts.RedrawInternal))
}

// newChartWithLegends creates a chart with legends at the bottom.
Expand Down
15 changes: 11 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ type cli struct {
caCert string

//options for gui
queryRange time.Duration
queryRange time.Duration
redrawInterval time.Duration

debug bool
version bool
Expand Down Expand Up @@ -104,7 +105,8 @@ func parseFlags(stdout, stderr io.Writer) (*cli, error) {
// TODO: Re-enable when making it capable of drawing histogram bar chart.
//flagSet.StringVar(&c.buckets, "buckets", "", "Histogram buckets; comma-separated list.")
flagSet.StringVar(&c.resolvers, "resolvers", "", "Custom DNS resolver addresses; comma-separated list.")
flagSet.DurationVar(&c.queryRange, "query-range", gui.DefaultQueryRange, "The time range to display data points on the UI")
flagSet.DurationVar(&c.queryRange, "query-range", gui.DefaultQueryRange, "The results within the given time range will be drawn on the charts")
flagSet.DurationVar(&c.redrawInterval, "redraw-interval", gui.DefaultRedrawInterval, "The time interval to redraw charts")
flagSet.Usage = c.usage
if err := flagSet.Parse(os.Args[1:]); err != nil {
if !errors.Is(err, flag.ErrHelp) {
Expand Down Expand Up @@ -152,8 +154,12 @@ func (c *cli) run(args []string) int {
}
setDebug(nil, c.debug)

// FIXME: Add redrewInterval option
if err := gui.Run(target, s, a, gui.Option{QueryRange: c.queryRange}); err != nil {
if err := gui.Run(target, s, a,
gui.Option{
QueryRange: c.queryRange,
RedrawInternal: c.redrawInterval,
},
); err != nil {
fmt.Fprintf(c.stderr, "failed to start application: %s\n", err.Error())
c.usage()
return 1
Expand Down Expand Up @@ -337,6 +343,7 @@ func parseResolvers(addrs string) ([]string, error) {
// Makes a new file under the ~/.config/ali only when debug use.
func setDebug(w io.Writer, debug bool) {
if !debug {
log.SetOutput(io.Discard)
return
}
if w == nil {
Expand Down
46 changes: 29 additions & 17 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package main

import (
"bytes"
"log"
"math"
"net/http"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -46,22 +48,23 @@ func TestParseFlags(t *testing.T) {
{
name: "with default options",
want: &cli{
rate: 50,
duration: time.Second * 10,
timeout: time.Second * 30,
method: "GET",
headers: []string{},
maxBody: -1,
noKeepAlive: false,
workers: 10,
maxWorkers: math.MaxUint64,
connections: 10000,
stdout: new(bytes.Buffer),
stderr: new(bytes.Buffer),
noHTTP2: false,
localAddress: "0.0.0.0",
resolvers: "",
queryRange: 30 * time.Second,
rate: 50,
duration: time.Second * 10,
timeout: time.Second * 30,
method: "GET",
headers: []string{},
maxBody: -1,
noKeepAlive: false,
workers: 10,
maxWorkers: math.MaxUint64,
connections: 10000,
stdout: new(bytes.Buffer),
stderr: new(bytes.Buffer),
noHTTP2: false,
localAddress: "0.0.0.0",
resolvers: "",
queryRange: 30 * time.Second,
redrawInterval: 250 * time.Millisecond,
},
wantErr: false,
},
Expand Down Expand Up @@ -367,19 +370,28 @@ func TestSetDebug(t *testing.T) {
tests := []struct {
name string
debug bool
input string
want string
}{
{
name: "in non-debug use",
debug: false,
input: "text",
want: "",
},
{
name: "in debug use",
debug: true,
input: "text",
want: "text",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setDebug(nil, tt.debug)
b := &bytes.Buffer{}
setDebug(b, tt.debug)
log.Print(tt.input)
assert.Equal(t, true, strings.Contains(b.String(), tt.want))
})
}
}

0 comments on commit d517ba7

Please sign in to comment.