wasp

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Apr 13, 2023 License: MIT Imports: 24 Imported by: 3

README

Wasp

Go Tests

A simple protocol-agnostic load testing tool for Go

Goals
  • Easy to reuse any custom client Go code
  • Easy to grasp
  • Have slim codebase (500-1k loc)
  • Have predictable performance footprint when tested with protocol mocks
  • Be able to perform synthetic load testing for request-based protocols in Go with RPS bound load (http, etc.)
  • Be able to perform synthetic load testing for streaming protocols in Go with Instances bound load (ws, etc.)
  • Scalable in k8s without complicated configuration or vendored UI interfaces
  • Non-opinionated reporting, push any data to Loki

Setup

make install_deps

Run example tests with Grafana + Loki

make start

Insert GRAFANA_TOKEN created in previous command

export LOKI_URL=http://localhost:3030/loki/api/v1/push
export GRAFANA_URL=http://localhost:3000
export GRAFANA_TOKEN=...
export DATA_SOURCE_NAME=Loki
export DASHBOARD_FOLDER=LoadTests
export WASP_LOG_LEVEL=info
make dashboard

Run some tests:

make test_loki

Open your Grafana dashboard

Basic dashboard: dashboard_img

Remove environment:

make stop

Run pyroscope test

make pyro_start
make test_pyro
make pyro_stop

Open pyroscope

You can also use trace.out in the root folder with Go default tracing UI

Tutorial

Check tutorial for more examples

Documentation

Index

Constants

View Source
const (
	DefaultCallTimeout       = 1 * time.Minute
	DefaultStatsPollInterval = 5 * time.Second
	DefaultCallResultBufLen  = 50000
)
View Source
const (
	RPSScheduleType       string = "rps_schedule"
	InstancesScheduleType string = "instance_schedule"
)
View Source
const (
	// DefaultStepChangePrecision is default amount of steps in which we split a schedule
	DefaultStepChangePrecision = 10
)
View Source
const (
	LogLevelEnvVar = "WASP_LOG_LEVEL"
)

Variables

View Source
var (
	ErrNoCfg             = errors.New("config is nil")
	ErrNoImpl            = errors.New("either \"gun\" or \"instanceTemplate\" implementation must provided")
	ErrNoSched           = errors.New("no schedule segments were provided")
	ErrWrongScheduleType = errors.New("schedule type must be RPSScheduleType or InstancesScheduleType, use package constants")
	ErrCallTimeout       = errors.New("generator request call timeout")
	ErrStartFrom         = errors.New("from must be > 0")
	ErrInvalidSteps      = errors.New("both \"Steps\" and \"StepsDuration\" must be defined in a schedule segment")
	ErrNoGun             = errors.New("rps load scheduleSegments selected but gun implementation is nil")
	ErrNoInstance        = errors.New("instanceTemplate load scheduleSegments selected but instanceTemplate implementation is nil")
)

Functions

func GetLogger

func GetLogger(t *testing.T, componentName string) zerolog.Logger

GetLogger instantiates a logger that takes into account the test context and the log level

func InitDefaultLogging

func InitDefaultLogging()

func LabelsMapToModel

func LabelsMapToModel(m map[string]string) model.LabelSet

LabelsMapToModel create model.LabelSet from map of labels

Types

type Alert

type Alert struct {
	Annotations struct {
		DashboardUID string `json:"__dashboardUid__"`
		OrgID        string `json:"__orgId__"`
		PanelID      string `json:"__panelId__"`
		Description  string `json:"description"`
		RunbookURL   string `json:"runbook_url"`
		Summary      string `json:"summary"`
	} `json:"annotations"`
	EndsAt      time.Time `json:"endsAt"`
	Fingerprint string    `json:"fingerprint"`
	Receivers   []struct {
		Active       interface{} `json:"active"`
		Integrations interface{} `json:"integrations"`
		Name         string      `json:"name"`
	} `json:"receivers"`
	StartsAt time.Time `json:"startsAt"`
	Status   struct {
		InhibitedBy []interface{} `json:"inhibitedBy"`
		SilencedBy  []interface{} `json:"silencedBy"`
		State       string        `json:"state"`
	} `json:"status"`
	UpdatedAt    time.Time         `json:"updatedAt"`
	GeneratorURL string            `json:"generatorURL"`
	Labels       map[string]string `json:"labels"`
}

type AlertChecker

type AlertChecker struct {
	URL                  string
	APIKey               string
	RequirementLabelName string
	T                    *testing.T
	// contains filtered or unexported fields
}

AlertChecker is checking alerts according to dashboardUUID and requirements labels

func NewAlertChecker

func NewAlertChecker(t *testing.T, requirenemtLabelName string) *AlertChecker

func (*AlertChecker) AnyAlerts

func (m *AlertChecker) AnyAlerts(dashboardUUID, requirementLabelValue string) error

AnyAlerts check if any alerts with dashboardUUID have been raised

type AlertGroupsResponse

type AlertGroupsResponse struct {
	Alerts []Alert `json:"alerts"`
	Labels struct {
		Alertname     string `json:"alertname"`
		GrafanaFolder string `json:"grafana_folder"`
	} `json:"labels"`
	Receiver struct {
		Active       interface{} `json:"active"`
		Integrations interface{} `json:"integrations"`
		Name         string      `json:"name"`
	} `json:"receiver"`
}

AlertGroupsResponse is response body for "api/alertmanager/grafana/api/v2/alerts/groups"

type CallResult

type CallResult struct {
	Failed     bool          `json:"failed,omitempty"`
	Timeout    bool          `json:"timeout,omitempty"`
	Duration   time.Duration `json:"duration"`
	StartedAt  *time.Time    `json:"started_at,omitempty"`
	FinishedAt *time.Time    `json:"finished_at,omitempty"`
	Data       interface{}   `json:"data,omitempty"`
	Error      string        `json:"error,omitempty"`
}

CallResult represents basic call result info

type Config

type Config struct {
	T                 *testing.T
	GenName           string
	LoadType          string
	Labels            map[string]string
	LokiConfig        *LokiConfig
	Schedule          []*Segment
	CallResultBufLen  int
	StatsPollInterval time.Duration
	CallTimeout       time.Duration
	Gun               Gun
	Instance          Instance
	Logger            zerolog.Logger
	SharedData        interface{}
	// contains filtered or unexported fields
}

Config is for shared load test data and configuration

func (*Config) Validate

func (lgc *Config) Validate() error

type ExtendedLokiClient

type ExtendedLokiClient interface {
	lokiClient.Client
	api.EntryHandler
	HandleStruct(ls model.LabelSet, t time.Time, st interface{}) error
	LastHandleResult() PromtailSendResult
	AllHandleResults() []PromtailSendResult
}

ExtendedLokiClient an extended Loki/Promtail client used for testing last results in batch

func NewMockPromtailClient

func NewMockPromtailClient() ExtendedLokiClient

type Generator

type Generator struct {
	Log zerolog.Logger

	ResponsesWaitGroup *sync.WaitGroup

	ResponsesCtx context.Context

	ResponsesChan chan CallResult
	// contains filtered or unexported fields
}

Generator generates load with some RPS

func NewGenerator

func NewGenerator(cfg *Config) (*Generator, error)

NewGenerator creates a new instanceTemplate for a contract, shoots for scheduled RPS until timeout, test logic is defined through Gun

func (*Generator) Errors

func (l *Generator) Errors() []string

Errors get all calls errors

func (*Generator) GetData

func (l *Generator) GetData() *ResponseData

GetData get all calls data

func (*Generator) InputSharedData

func (l *Generator) InputSharedData() interface{}

InputSharedData returns the SharedData passed in Generator config

func (*Generator) Run

func (l *Generator) Run(wait bool) (interface{}, bool)

Run runs load loop until timeout or stop

func (*Generator) Stats

func (l *Generator) Stats() *Stats

Stats get all load stats

func (*Generator) StatsJSON

func (l *Generator) StatsJSON() map[string]interface{}

StatsJSON get all load stats for export

func (*Generator) Stop

func (l *Generator) Stop() (interface{}, bool)

Stop stops load generator, waiting for all calls for either finish or timeout

func (*Generator) Wait

func (l *Generator) Wait() (interface{}, bool)

Wait waits until test ends

type Gun

type Gun interface {
	Call(l *Generator) CallResult
}

Gun is basic interface to run limited load with a contract call and save all transactions

type HTTPMockServer

type HTTPMockServer struct {
	Sleep time.Duration
	// contains filtered or unexported fields
}

func NewHTTPMockServer

func NewHTTPMockServer(sleep time.Duration) *HTTPMockServer

func (*HTTPMockServer) Run

func (s *HTTPMockServer) Run()

func (*HTTPMockServer) URL

func (s *HTTPMockServer) URL() string

type Instance

type Instance interface {
	Run(l *Generator)
	Stop(l *Generator)
	Clone(l *Generator) Instance
}

Instance is basic interface to run load instances

type LocalLogger

type LocalLogger struct{}

func (*LocalLogger) Log

func (m *LocalLogger) Log(kvars ...interface{}) error

type LokiClient

type LokiClient struct {
	lokiClient.Client
}

LokiClient is a Loki/Promtail client wrapper

func NewLokiClient

func NewLokiClient(extCfg *LokiConfig) (*LokiClient, error)

NewLokiClient creates a new Loki/Promtail client

func (*LokiClient) AllHandleResults

func (m *LokiClient) AllHandleResults() []PromtailSendResult

func (*LokiClient) Handle

func (m *LokiClient) Handle(ls model.LabelSet, t time.Time, s string) error

Handle handles adding a new label set and a message to the batch

func (*LokiClient) HandleStruct

func (m *LokiClient) HandleStruct(ls model.LabelSet, t time.Time, st interface{}) error

HandleStruct handles adding a new label set and a message to the batch, marshalling JSON from struct

func (*LokiClient) LastHandleResult

func (m *LokiClient) LastHandleResult() PromtailSendResult

func (*LokiClient) Stop

func (m *LokiClient) Stop()

Stop stops the client goroutine

type LokiConfig

type LokiConfig struct {
	// URL url to Loki endpoint
	URL string `yaml:"url"`
	// Token is Loki authorization token
	Token string `yaml:"token"`
	// BatchWait max time to wait until sending a new batch
	BatchWait time.Duration `yaml:"batch_wait"`
	// BatchSize size of a messages batch
	BatchSize int `yaml:"batch_size"`
	// Timeout is a batch send timeout
	Timeout time.Duration `yaml:"timeout"`
}

LokiConfig Loki/Promtail client configuration

func NewDefaultLokiConfig

func NewDefaultLokiConfig(url string, token string) *LokiConfig

func NewEnvLokiConfig

func NewEnvLokiConfig() *LokiConfig

type MockGun

type MockGun struct {
	Data []string
	// contains filtered or unexported fields
}

MockGun is a mock gun

func NewMockGun

func NewMockGun(cfg *MockGunConfig) *MockGun

NewMockGun create a mock gun

func (*MockGun) Call

func (m *MockGun) Call(l *Generator) CallResult

Call implements example gun call, assertions on response bodies should be done here

type MockGunConfig

type MockGunConfig struct {
	// FailRatio in percentage, 0-100
	FailRatio int
	// TimeoutRatio in percentage, 0-100
	TimeoutRatio int
	// CallSleep time spent waiting inside a call
	CallSleep time.Duration
}

MockGunConfig configures a mock gun

type MockHTTPGun

type MockHTTPGun struct {
	Data []string
	// contains filtered or unexported fields
}

MockHTTPGun is a mock gun

func NewHTTPMockGun

func NewHTTPMockGun(cfg *MockHTTPGunConfig) *MockHTTPGun

NewHTTPMockGun create an HTTP mock gun

func (*MockHTTPGun) Call

func (m *MockHTTPGun) Call(l *Generator) CallResult

Call implements example gun call, assertions on response bodies should be done here

type MockHTTPGunConfig

type MockHTTPGunConfig struct {
	TargetURL string
}

MockHTTPGunConfig configures a mock HTTP gun

type MockInstance

type MockInstance struct {
	Data []string
	// contains filtered or unexported fields
}

MockInstance is a mock instanceTemplate

func NewMockInstance

func NewMockInstance(cfg MockInstanceConfig) MockInstance

NewMockInstance create a mock instanceTemplate

func (MockInstance) Clone

func (m MockInstance) Clone(l *Generator) Instance

func (MockInstance) Run

func (m MockInstance) Run(l *Generator)

func (MockInstance) Stop

func (m MockInstance) Stop(l *Generator)

type MockInstanceConfig

type MockInstanceConfig struct {
	// FailRatio in percentage, 0-100
	FailRatio int
	// TimeoutRatio in percentage, 0-100
	TimeoutRatio int
	// CallSleep time spent waiting inside a call
	CallSleep time.Duration
}

MockInstanceConfig configures a mock instanceTemplate

type MockPromtailClient

type MockPromtailClient struct {
	Results       []PromtailSendResult
	OnHandleEntry api.EntryHandlerFunc
}

func (*MockPromtailClient) AllHandleResults

func (c *MockPromtailClient) AllHandleResults() []PromtailSendResult

func (*MockPromtailClient) Handle

func (c *MockPromtailClient) Handle(labels model.LabelSet, time time.Time, entry string) error

Handle implements client.Client

func (*MockPromtailClient) HandleStruct

func (c *MockPromtailClient) HandleStruct(ls model.LabelSet, t time.Time, st interface{}) error

func (*MockPromtailClient) LastHandleResult

func (c *MockPromtailClient) LastHandleResult() PromtailSendResult

func (*MockPromtailClient) Stop

func (c *MockPromtailClient) Stop()

Stop implements client.Client

type MockWSServer

type MockWSServer struct {
	// Logf controls where logs are sent.
	Logf  func(f string, v ...interface{})
	Sleep time.Duration
}

func (MockWSServer) ServeHTTP

func (s MockWSServer) ServeHTTP(w http.ResponseWriter, r *http.Request)

type Profile

type Profile struct {
	Generators []*Generator
}

Profile is a set of concurrent generators forming some workload profile

func NewInstanceProfile

func NewInstanceProfile(t *testing.T, labels map[string]string, pp []*ProfileInstancePart) (*Profile, error)

NewInstanceProfile creates new InstanceProfile from parts

func NewRPSProfile

func NewRPSProfile(t *testing.T, labels map[string]string, pp []*ProfileGunPart) (*Profile, error)

NewRPSProfile creates new RPSProfile from parts

func (*Profile) Run

func (m *Profile) Run(wait bool) error

Run runs all generators and wait until they finish

func (*Profile) Wait

func (m *Profile) Wait()

Wait waits until all generators have finished the workload

type ProfileGunPart

type ProfileGunPart struct {
	Name     string
	Schedule []*Segment
	Gun      Gun
}

type ProfileInstancePart

type ProfileInstancePart struct {
	Name     string
	Schedule []*Segment
	Instance Instance
}

type PromtailSendResult

type PromtailSendResult struct {
	Labels model.LabelSet
	Time   time.Time
	Entry  string
}

type ResponseData

type ResponseData struct {
	OKData *SliceBuffer[any]

	OKResponses *SliceBuffer[CallResult]

	FailResponses *SliceBuffer[CallResult]
	// contains filtered or unexported fields
}

ResponseData includes any request/response data that a gun might store ok* slices usually contains successful responses and their verifications if their done async fail* slices contains CallResult with response data and an error

type Segment

type Segment struct {
	From         int64
	Increase     int64
	Steps        int64
	StepDuration time.Duration
	// contains filtered or unexported fields
}

Segment load test schedule segment

func Combine

func Combine(segs ...[]*Segment) []*Segment

func CombineAndRepeat

func CombineAndRepeat(times int, segs ...[]*Segment) []*Segment

func Line

func Line(from, to int64, duration time.Duration) []*Segment

func Plain

func Plain(from int64, duration time.Duration) []*Segment

func (*Segment) Validate

func (ls *Segment) Validate(cfg *Config) error

type SliceBuffer

type SliceBuffer[T any] struct {
	Idx      int
	Capacity int
	Data     []T
}

SliceBuffer keeps Capacity of type T, after len => cap overrides old data

func NewSliceBuffer

func NewSliceBuffer[T any](cap int) *SliceBuffer[T]

NewSliceBuffer creates new limited capacity slice

func (*SliceBuffer[T]) Append

func (m *SliceBuffer[T]) Append(s T)

Append appends T if len <= cap, overrides old data otherwise

type Stats

type Stats struct {
	CurrentRPS       atomic.Int64 `json:"currentRPS"`
	CurrentInstances atomic.Int64 `json:"currentInstances"`
	LastSegment      atomic.Int64 `json:"last_segment"`
	CurrentSegment   atomic.Int64 `json:"current_schedule_segment"`
	CurrentStep      atomic.Int64 `json:"current_schedule_step"`
	RunStopped       atomic.Bool  `json:"runStopped"`
	RunFailed        atomic.Bool  `json:"runFailed"`
	Success          atomic.Int64 `json:"success"`
	Failed           atomic.Int64 `json:"failed"`
	CallTimeout      atomic.Int64 `json:"callTimeout"`
}

Stats basic generator load stats

type WSMockConfig

type WSMockConfig struct {
	TargetURl string
}

WSMockConfig ws mock config

type WSMockInstance

type WSMockInstance struct {
	Data []string
	// contains filtered or unexported fields
}

WSMockInstance ws mock mock

func NewWSMockInstance

func NewWSMockInstance(cfg WSMockConfig) WSMockInstance

NewWSMockInstance create a ws mock instanceTemplate

func (WSMockInstance) Clone

func (m WSMockInstance) Clone(l *Generator) Instance

func (WSMockInstance) Run

func (m WSMockInstance) Run(l *Generator)

Run create an instanceTemplate firing read requests against mock ws server

func (WSMockInstance) Stop

func (m WSMockInstance) Stop(l *Generator)

Directories

Path Synopsis
examples
alerts command
go_test command
profiles command
simple_rps command

Jump to

Keyboard shortcuts

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