Skip to content

skudasov/loaderbot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Loaderbot

Minimalistic load tool

How to use

Implement attacker, example with one client sharing connections

package attackers

import (
	"context"
	"net/http"
    "io"
    "io/ioutil"

	"github.com/skudasov/loaderbot"
)

func init() {
	loaderbot.RegisterAttacker("HTTPAttackerExample", &AttackerExample{})
}

type AttackerExample struct {
	*loaderbot.Runner
	client *http.Client
}

func (a *AttackerExample) Clone(r *loaderbot.Runner) loaderbot.Attack {
	return &AttackerExample{Runner: r}
}

func (a *AttackerExample) Setup(c loaderbot.RunnerConfig) error {
	a.client = loaderbot.NewLoggingHTTPClient(c.DumpTransport, 10)
	return nil
}

func (a *AttackerExample) Do(ctx context.Context) loaderbot.DoResult {
	req, _ := http.NewRequestWithContext(ctx, "GET", a.Cfg.TargetUrl, nil)
    	res, err := a.HTTPClient.Do(req)
    	if res != nil {
    		if _, err = io.Copy(ioutil.Discard, res.Body); err != nil {
    			return loaderbot.DoResult{
    				RequestLabel: a.Name,
    				Error:        err.Error(),
    			}
    		}
    		defer res.Body.Close()
    	}
    	if err != nil {
    		return loaderbot.DoResult{
    			RequestLabel: a.Name,
    			Error:        err.Error(),
    		}
    	}
    	return loaderbot.DoResult{RequestLabel: a.Name}
}

func (a *AttackerExample) Teardown() error {
	return nil
}

Alternatively Fasthttp can be used in Do():

func (a *FastHTTPAttackerExample) Do(_ context.Context) DoResult {
	req := fasthttp.AcquireRequest()
	req.SetRequestURI(a.Cfg.TargetUrl)
	resp := fasthttp.AcquireResponse()
	defer fasthttp.ReleaseRequest(req)
	defer fasthttp.ReleaseResponse(resp)
	err := a.FastHTTPClient.Do(req, resp)
	if resp.StatusCode() >= 400 {
		return DoResult{
			Error: "request failed",
		}
	}
	if err != nil {
		return DoResult{
			Error: err.Error(),
		}
	}
	return DoResult{RequestLabel: a.Name}
}

Run test with constant amount of attackers (clients) using BoundRPS mode

cfg := &loaderbot.RunnerConfig{
		TargetUrl:       "https://clients5.google.com/pagead/drt/dn/",
		Name:            "abc",
		SystemMode:      loaderbot.BoundRPS,
		Attackers:       100,
		AttackerTimeout: 5,
		StartRPS:        100,
		StepDurationSec: 10,
		StepRPS:         300,
		TestTimeSec:     200,
	}
lt := loaderbot.NewRunner(cfg, &loaderbot.HTTPAttackerExample{}, nil)
maxRPS, _ := lt.Run()

Another option is to use BoundRPSAutoscale to add attackers if target rps isn't met during the test, which is more realistic in "open world" systems like search engines

cfg := &loaderbot.RunnerConfig{
		TargetUrl:       "https://clients5.google.com/pagead/drt/dn/",
		Name:            "abc",
		SystemMode:      loaderbot.BoundRPSAutoscale,
		Attackers:       100,
		AttackerTimeout: 5,
		StartRPS:        100,
		StepDurationSec: 10,
		StepRPS:         300,
		TestTimeSec:     200,
	}
lt := loaderbot.NewRunner(cfg, &loaderbot.HTTPAttackerExample{}, nil)
maxRPS, _ := lt.Run()

Or use UnboundRPS to check system scalability

r := NewRunner(&loaderbot.RunnerConfig{
		TargetUrl:       "http://127.0.0.1:9031/json_body",
		Name:            "nginx_test",
		SystemMode:      loaderbot.UnboundRPS,
		Attackers:       20,
		AttackerTimeout: 25,
		TestTimeSec:     30,
		SuccessRatio:    0.95,
		Prometheus:      &Prometheus{Enable: true},
	}, &HTTPAttackerExample{}, nil)
_, _ = r.Run(context.TODO())

see more examples

Config options

// RunnerConfig runner configuration
type RunnerConfig struct {
	// TargetUrl target base url
	TargetUrl string
	// Name of a runner instance
	Name string
	// InstanceType attacker type instance, used only in cluster mode
	InstanceType string
	// SystemMode BoundRPS
	// BoundRPS:
	// if application under test is a private system sync runner attackers will wait for response
	// in case your system is private and you know how many sync clients can act
	// BoundRPSAutoscale:
	// try to scale attackers when all attackers are blocked
	// UnboundRPS:
	// attack as fast as we can with N attackers
	SystemMode SystemMode
	// Attackers constant amount of attackers,
	Attackers int
	// AttackersScaleFactor how much attackers to add when rps is not met, default is 100
	AttackersScaleAmount int
	// AttackersScaleThreshold scale if current rate is less than target rate * threshold,
	// interval of values = [0, 1], default is 0.90
	AttackersScaleThreshold float64
	// AttackerTimeout timeout of attacker
	AttackerTimeout int
	// StartRPS initial requests per seconds rate
	StartRPS int
	// StepDurationSec duration of step in which rps is increased by StepRPS
	StepDurationSec int
	// StepRPS amount of requests per second which will be added in next step,
	// if StepRPS = 0 rate is constant, default StepDurationSec is 30 sec is applied,
	// just to keep 30s aggregation metrics
	StepRPS int
	// TestTimeSec test timeout
	TestTimeSec int
	// WaitBeforeSec time to wait before start in case we didn't know start criteria
	WaitBeforeSec int
	// Dumptransport dumps http requests to stdout
	DumpTransport bool
	// GoroutinesDump dumps goroutines stack for debug purposes
	GoroutinesDump bool
	// SuccessRatio to fail when below
	SuccessRatio float64
	// Metadata all other data required for test setup
	Metadata map[string]interface{}
	// LogLevel debug|info, etc.
	LogLevel string
	// LogEncoding json|console
	LogEncoding string
	// Reporting options, csv/png/stream
	ReportOptions *ReportOptions
	// ClusterOptions
	ClusterOptions *ClusterOptions
	// Prometheus config
	Prometheus *Prometheus
}

Development

make test
make lint

About

Simple load testing tool in plain go

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages