blaster

package
v0.0.0-...-f3afebf Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 1, 2018 License: MIT Imports: 24 Imported by: 1

Documentation

Overview

Package blaster provides the back-end for blast - a tool for load testing and sending api requests in bulk.

Blast
=====

* Blast makes API requests at a fixed rate.
* The number of concurrent workers is configurable.
* The rate may be changed interactively during execution.
* Blast is protocol agnostic, and adding a new worker type is trivial.
* For load testing: random data can be added to API requests.
* For batch jobs: CSV data can be loaded from local file or GCS bucket, and successful items from previous runs are skipped.

Installation
============
## Mac
```
brew tap dave/blast
brew install blast
```

## Linux
See the [releases page](https://github.com/dave/blast/releases)

## From source
```
go get -u github.com/dave/blast
```

Examples
========
Using the dummy worker to send at 20,000 requests per second (the dummy worker returns after a random wait, and occasionally returns errors):
```
blast --rate=20000 --workers=1000 --worker-type="dummy" --worker-template='{"min":25,"max":50}'
```

Using the http worker to request Google's homepage at one request per second (warning: this is making real http requests - don't turn the rate up!):
```
blast --rate=1 --worker-type="http" --payload-template='{"method":"GET","url":"http://www.google.com/"}'
```

Status
======

Blast prints a summary every ten seconds. While blast is running, you can hit enter for an updated
summary, or enter a number to change the sending rate. Each time you change the rate a new column
of metrics is created. If the worker returns a field named `status` in it's response, the values
are summarised as rows.

Here's an example of the output:

```
Metrics
=======
Concurrency:      1999 / 2000 workers in use

Desired rate:     (all)        10000        1000         100
Actual rate:      2112         5354         989          100
Avg concurrency:  1733         1976         367          37
Duration:         00:40        00:12        00:14        00:12

Total
-----
Started:          84525        69004        14249        1272
Finished:         82525        67004        14249        1272
Mean:             376.0 ms     374.8 ms     379.3 ms     377.9 ms
95th:             491.1 ms     488.1 ms     488.2 ms     489.6 ms

200
---
Count:            79208 (96%)  64320 (96%)  13663 (96%)  1225 (96%)
Mean:             376.2 ms     381.9 ms     374.7 ms     378.1 ms
95th:             487.6 ms     489.0 ms     487.2 ms     490.5 ms

404
---
Count:            2467 (3%)    2002 (3%)    430 (3%)     35 (3%)
Mean:             371.4 ms     371.0 ms     377.2 ms     358.9 ms
95th:             487.1 ms     487.1 ms     486.0 ms     480.4 ms

500
---
Count:            853 (1%)     685 (1%)     156 (1%)     12 (1%)
Mean:             371.2 ms     370.4 ms     374.5 ms     374.3 ms
95th:             487.6 ms     487.1 ms     488.2 ms     466.3 ms

Current rate is 10000 requests / second. Enter a new rate or press enter to view status.

Rate?
```

Config
======
Blast is configured by config file, command line flags or environment variables. The `--config` flag specifies the config file to load, and can be `json`, `yaml`, `toml` or anything else that [viper](https://github.com/spf13/viper) can read. If the config flag is omitted, blast searches for `blast-config.xxx` in the current directory, `$HOME/.config/blast/` and `/etc/blast/`.

Environment variables and command line flags override config file options. Environment variables are upper case and prefixed with "BLAST" e.g. `BLAST_PAYLOAD_TEMPLATE`.

Templates
=========
The `payload-template` and `worker-template` options accept values that are rendered using the Go text/template system. Variables of the form `{{ .name }}` or `{{ "name" }}` are replaced with data.

Additionally, several simple functions are available to inject random data which is useful in load testing scenarios:

* `{{ rand_int -5 5 }}` - a random integer between -5 and 5.
* `{{ rand_float -5 5 }}` - a random float between -5 and 5.
* `{{ rand_string 10 }}` - a random string, length 10.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Blaster

type Blaster struct {
	// Quiet disables the status output.
	Quiet bool

	// Resume sets the resume option. See Config.Resume for more details.
	Resume bool

	// Rate sets the initial sending rate. Do not change this during a run - use the ChangeRate method instead. See Config.Resume for more details.
	Rate float64

	// Workers sets the number of workers. See Config.Workers for more details.
	Workers int

	// LogData sets the data fields to be logged. See Config.LogData for more details.
	LogData []string

	// LogOutput sets the output fields to be logged. See Config.LogOutput for more details.
	LogOutput []string

	// Headers sets the data headers. See Config.Headers for more details.
	Headers []string

	// PayloadVariants sets the payload variants. See Config.PayloadVariants for more details.
	PayloadVariants []map[string]string

	// WorkerVariants sets the worker variants. See Config.WorkerVariants for more details.
	WorkerVariants []map[string]string
	// contains filtered or unexported fields
}

Blaster provides the back-end blast: a simple tool for API load testing and batch jobs. Use the New function to create a Blaster with default values.

func New

func New(ctx context.Context, cancel context.CancelFunc) *Blaster

New creates a new Blaster with defaults.

func (*Blaster) ChangeRate

func (b *Blaster) ChangeRate(rate float64)

ChangeRate changes the sending rate during execution.

func (*Blaster) Command

func (b *Blaster) Command(ctx context.Context) error

Command processes command line flags, loads the config and starts the blast run.

func (*Blaster) Exit

func (b *Blaster) Exit()

Exit cancels any goroutines that are still processing, and closes all files.

func (*Blaster) Initialise

func (b *Blaster) Initialise(ctx context.Context, c Config) error

Initialise configures the Blaster with config options in a provided Config

func (*Blaster) LoadConfig

func (b *Blaster) LoadConfig() (Config, error)

LoadConfig parses command line flags and loads a config file from disk. A Config is returned which may be used with the Initialise method to complete configuration.

func (*Blaster) LoadLogs

func (b *Blaster) LoadLogs(r io.Reader) error

LoadLogs loads the logs from a previous run, and stores successfully completed items so they can be skipped in the current run.

func (*Blaster) PrintStatus

func (b *Blaster) PrintStatus(writer io.Writer)

PrintStatus prints the status message to the output writer

func (*Blaster) ReadHeaders

func (b *Blaster) ReadHeaders() error

ReadHeaders reads one row from the data source and stores that in Headers

func (*Blaster) RegisterWorkerType

func (b *Blaster) RegisterWorkerType(key string, workerFunc func() Worker)

RegisterWorkerType registers a new worker function that can be referenced in config file by the worker-type string field.

func (*Blaster) SetData

func (b *Blaster) SetData(r io.Reader)

SetData sets the CSV data source. If the provided io.Reader also satisfies io.Closer it will be closed on exit.

func (*Blaster) SetInput

func (b *Blaster) SetInput(r io.Reader)

SetInput sets the rate adjustment reader, and allows testing rate adjustments. The Command method sets this to os.Stdin for interactive command line usage.

func (*Blaster) SetLog

func (b *Blaster) SetLog(w io.Writer)

SetLog sets the log output. If the provided writer also satisfies io.Closer, it will be closed on exit.

func (*Blaster) SetOutput

func (b *Blaster) SetOutput(w io.Writer)

SetOutput sets the summary output writer, and allows the output to be redirected. The Command method sets this to os.Stdout for command line usage.

func (*Blaster) SetPayloadTemplate

func (b *Blaster) SetPayloadTemplate(t map[string]interface{}) error

SetPayloadTemplate sets the payload template. See Config.PayloadTemplate for more details.

func (*Blaster) SetTimeout

func (b *Blaster) SetTimeout(timeout time.Duration)

SetTimeout sets the timeout. See Config.Timeout for more details.

func (*Blaster) SetWorker

func (b *Blaster) SetWorker(wf func() Worker)

SetWorker sets the worker creation function. See httpworker for a simple example.

func (*Blaster) SetWorkerTemplate

func (b *Blaster) SetWorkerTemplate(t map[string]interface{}) error

SetWorkerTemplate sets the worker template. See Config.WorkerTemplate for more details.

func (*Blaster) Start

func (b *Blaster) Start(ctx context.Context) (Stats, error)

Start starts the blast run without processing any config.

Example (BatchJob)
package main

import (
	"context"

	"fmt"

	"strings"

	"github.com/dave/blast/blaster"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	b := blaster.New(ctx, cancel)
	defer b.Exit()
	b.SetWorker(func() blaster.Worker {
		return &blaster.ExampleWorker{
			SendFunc: func(ctx context.Context, self *blaster.ExampleWorker, in map[string]interface{}) (map[string]interface{}, error) {
				return map[string]interface{}{"status": 200}, nil
			},
		}
	})
	b.Headers = []string{"header"}
	b.SetData(strings.NewReader("foo\nbar"))
	stats, err := b.Start(ctx)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	fmt.Printf("Success == 2: %v\n", stats.All.Summary.Success == 2)
	fmt.Printf("Fail == 0: %v", stats.All.Summary.Fail == 0)
}
Output:

Success == 2: true
Fail == 0: true
Example (LoadTest)
package main

import (
	"context"

	"fmt"

	"time"

	"sync"

	"github.com/dave/blast/blaster"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	b := blaster.New(ctx, cancel)
	defer b.Exit()
	b.SetWorker(func() blaster.Worker {
		return &blaster.ExampleWorker{
			SendFunc: func(ctx context.Context, self *blaster.ExampleWorker, in map[string]interface{}) (map[string]interface{}, error) {
				return map[string]interface{}{"status": 200}, nil
			},
		}
	})
	b.Rate = 1000
	wg := &sync.WaitGroup{}
	wg.Add(1)
	go func() {
		stats, err := b.Start(ctx)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		fmt.Printf("Success > 10: %v\n", stats.All.Summary.Success > 10)
		fmt.Printf("Fail == 0: %v", stats.All.Summary.Fail == 0)
		wg.Done()
	}()
	<-time.After(time.Millisecond * 100)
	b.Exit()
	wg.Wait()
}
Output:

Success > 10: true
Fail == 0: true

func (*Blaster) Stats

func (b *Blaster) Stats() Stats

Stats returns a snapshot of the metrics (as is printed during interactive execution).

func (*Blaster) WriteLogHeaders

func (b *Blaster) WriteLogHeaders() error

WriteLogHeaders writes the log headers to the log writer.

type Config

type Config struct {
	// Data sets the the data file to load. If none is specified, the worker will be called repeatedly until interrupted (useful for load testing). Load a local file or stream directly from a GCS bucket with `gs://{bucket}/{filename}.csv`. Data should be in csv format, and if `headers` is not specified the first record will be used as the headers. If a newline character is found, this string is read as the data.
	Data string `mapstructure:"data" json:"data"`

	// Log sets the filename of the log file to create / append to.
	Log string `mapstructure:"log" json:"log"`

	// Resume instructs the tool to load the log file and skip previously successful items. Failed items will be retried.
	Resume bool `mapstructure:"resume" json:"resume"`

	// Rate sets the initial rate in requests per second. Simply enter a new rate during execution to adjust this. (Default: 10 requests / second).
	Rate float64 `mapstructure:"rate" json:"rate"`

	// Workers sets the number of concurrent workers. (Default: 10 workers).
	Workers int `mapstructure:"workers" json:"workers"`

	// WorkerType sets the selected worker type. Register new worker types with the `RegisterWorkerType` method.
	WorkerType string `mapstructure:"worker-type" json:"worker-type"`

	// PayloadTemplate sets the template that is rendered and passed to the worker `Send` method. When setting this by command line flag or environment variable, use a json encoded string.
	PayloadTemplate map[string]interface{} `mapstructure:"payload-template" json:"payload-template"`

	// Timeout sets the deadline in the context passed to the worker. Workers must respect this the context cancellation. We exit with an error if any worker is processing for timeout + 1 second. (Default: 1 second).
	Timeout int `mapstructure:"timeout" json:"timeout"`

	// LogData sets an array of data fields to include in the output log. When setting this by command line flag or environment variable, use a json encoded string.
	LogData []string `mapstructure:"log-data" json:"log-data"`

	// LogOutput sets an array of worker response fields to include in the output log. When setting this by command line flag or environment variable, use a json encoded string.
	LogOutput []string `mapstructure:"log-output" json:"log-output"`

	// PayloadVariants sets an array of maps that will cause each data item to be repeated with the provided data. When setting this by command line flag or environment variable, use a json encoded string.
	PayloadVariants []map[string]string `mapstructure:"payload-variants" json:"payload-variants"`

	// WorkerVariants sets an array of maps that will cause each worker to be initialised with different data. When setting this by command line flag or environment variable, use a json encoded string.
	WorkerVariants []map[string]string `mapstructure:"worker-variants" json:"worker-variants"`

	// WorkerTemplate sets a template to render and pass to the worker `Start` or `Stop` methods if the worker satisfies the `Starter` or `Stopper` interfaces. Use with `worker-variants` to configure several workers differently to spread load. When setting this by command line flag or environment variable, use a json encoded string.
	WorkerTemplate map[string]interface{} `mapstructure:"worker-template" json:"worker-template"`

	// Headers sets the data file headers. If omitted, the first record of the csv data source is used. When setting this by command line flag or environment variable, use a json encoded string.
	Headers []string `mapstructure:"headers" json:"headers"`

	// Quiet instructs the tool to prevent interactive features. No summary is printed during operation and the rate cannot be changed interactively.
	Quiet bool `mapstructure:"quiet" json:"quiet"`
}

Config provides all the standard config options. Use the Initialise method to configure with a provided Config.

type ExampleWorker

type ExampleWorker struct {
	SendFunc  func(ctx context.Context, self *ExampleWorker, in map[string]interface{}) (map[string]interface{}, error)
	StartFunc func(ctx context.Context, self *ExampleWorker, payload map[string]interface{}) error
	StopFunc  func(ctx context.Context, self *ExampleWorker, payload map[string]interface{}) error
	Local     map[string]interface{}
}

ExampleWorker facilitates code examples by satisfying the Worker, Starter and Stopper interfaces with provided functions.

func (*ExampleWorker) Send

func (e *ExampleWorker) Send(ctx context.Context, in map[string]interface{}) (map[string]interface{}, error)

Send satisfies the Worker interface.

func (*ExampleWorker) Start

func (e *ExampleWorker) Start(ctx context.Context, payload map[string]interface{}) error

Start satisfies the Starter interface.

func (*ExampleWorker) Stop

func (e *ExampleWorker) Stop(ctx context.Context, payload map[string]interface{}) error

Stop satisfies the Stopper interface.

type Segment

type Segment struct {
	DesiredRate        float64
	ActualRate         float64
	AverageConcurrency float64
	Duration           time.Duration
	Summary            *Total
	Status             []*Status
}

Segment is a rate segment - a new segment is created each time the rate is changed.

type Starter

type Starter interface {
	Start(ctx context.Context, payload map[string]interface{}) error
}

Starter and Stopper are interfaces a worker can optionally satisfy to provide initialization or finalization logic. See `httpworker` and `dummyworker` for simple examples.

type Stats

type Stats struct {
	ConcurrencyCurrent int
	ConcurrencyMaximum int
	Skipped            int64
	All                *Segment
	Segments           []*Segment
}

Stats is a snapshot of the metrics (as is printed during interactive execution).

func (Stats) String

func (s Stats) String() string

String returns a string representation of the stats (as is printed during interactive execution).

type Status

type Status struct {
	Status      string
	Count       int64
	Fraction    float64
	Mean        time.Duration
	NinetyFifth time.Duration
}

Status is a summary of all requests that returned a specific status

type Stopper

type Stopper interface {
	Stop(ctx context.Context, payload map[string]interface{}) error
}

Stopper is an interface a worker can optionally satisfy to provide finalization logic.

type Total

type Total struct {
	Started     int64
	Finished    int64
	Success     int64
	Fail        int64
	Mean        time.Duration
	NinetyFifth time.Duration
}

Total is the summary of all requests in this segment

type Worker

type Worker interface {
	Send(ctx context.Context, payload map[string]interface{}) (response map[string]interface{}, err error)
}

Worker is an interface that allows blast to easily be extended to support any protocol. See `main.go` for an example of how to build a command with your custom worker type.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL