wasp

package module
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Apr 20, 2023 License: MIT Imports: 36 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 VU load testing for streaming protocols/scenario tests in Go with VU bound load (ws, scenario, 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

Loki debug

You can check all the messages the tool sends with env var WASP_LOG_LEVEL=trace

Documentation

Index

Constants

View Source
const (
	DefaultStatTextSize       = 12
	DefaultStatValueSize      = 20
	DefaultAlertEvaluateEvery = "10s"
	DefaultAlertFor           = "10s"
	DefaultDashboardUUID      = "wasp"

	DefaultRequirementLabelKey = "requirement_name"
)
View Source
const (
	AlertTypeQuantile99 = "quantile_99"
	AlertTypeErrors     = "errors"
)
View Source
const (
	DefaultCallTimeout       = 1 * time.Minute
	DefaultStatsPollInterval = 5 * time.Second
	DefaultCallResultBufLen  = 50000
	DefaultGenName           = "Generator"
)
View Source
const (
	RPSScheduleType string = "rps_schedule"
	VUScheduleType  string = "vu_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 \"vu\" implementation must provided")
	ErrNoSchedule        = errors.New("no schedule segments were provided")
	ErrWrongScheduleType = errors.New("schedule type must be RPSScheduleType or VUScheduleType, 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")
	ErrNoVU              = errors.New("vu load scheduleSegments selected but vu 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

func LokiAlertParams added in v0.1.1

func LokiAlertParams(queryType, testName, genName string) string

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
	RequirementLabelKey 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) *AlertChecker

func (*AlertChecker) AnyAlerts

func (m *AlertChecker) AnyAlerts(dashboardUUID, requirementLabelValue string) ([]AlertGroupsResponse, 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
	VU                VirtualUser
	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 Dashboard added in v0.1.1

type Dashboard struct{}

Dashboard is a Wasp dashboard

func NewDashboard added in v0.1.1

func NewDashboard() *Dashboard

NewDashboard creates new dashboard

func (*Dashboard) Dashboard added in v0.1.1

func (m *Dashboard) Dashboard(datasourceName string, requirements []WaspAlert) (dashboard.Builder, error)

Dashboard creates dashboard instance

func (*Dashboard) Deploy added in v0.1.1

func (m *Dashboard) Deploy(reqs []WaspAlert) (*grabana.Dashboard, error)

Deploy deploys this dashboard to some Grafana folder

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 generator, shoots for scheduled RPS until timeout, test logic is defined through Gun or VirtualUser

func (*Generator) Errors

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

Errors get all calls errors

func (*Generator) GetData

func (g *Generator) GetData() *ResponseData

GetData get all calls data

func (*Generator) InputSharedData

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

InputSharedData returns the SharedData passed in Generator config

func (*Generator) Run

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

Run runs load loop until timeout or stop

func (*Generator) Stats

func (g *Generator) Stats() *Stats

Stats get all load stats

func (*Generator) StatsJSON

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

StatsJSON get all load stats for export

func (*Generator) Stop

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

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

func (*Generator) Wait

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

Wait waits until test ends

type Gun

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

Gun is basic interface to some synthetic load test Call one request with some RPS schedule

type HTTPMockServer

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

func NewHTTPMockServer

func NewHTTPMockServer(cfg *HTTPMockServerConfig) *HTTPMockServer

func (*HTTPMockServer) Run

func (s *HTTPMockServer) Run()

func (*HTTPMockServer) URL

func (s *HTTPMockServer) URL() string

type HTTPMockServerConfig added in v0.1.1

type HTTPMockServerConfig struct {
	FirstAPILatency   time.Duration
	FirstAPIHTTPCode  int
	SecondAPILatency  time.Duration
	SecondAPIHTTPCode int
}

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 Promtail client

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) 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
	// BatchSize size of a messages batch
	BatchSize int
	// Timeout is batch send timeout
	Timeout time.Duration
	// BackoffConfig backoff configuration
	BackoffConfig backoff.Config
	// Headers are additional request headers
	Headers map[string]string
	// The tenant ID to use when pushing logs to Loki (empty string means
	// single tenant mode)
	TenantID string
	// When enabled, Promtail will not retry batches that get a
	// 429 'Too Many Requests' response from the distributor. Helps
	// prevent HOL blocking in multitenant deployments.
	DropRateLimitedBatches bool
	// ExposePrometheusMetrics if enabled exposes Promtail Prometheus metrics
	ExposePrometheusMetrics bool
	MaxStreams              int
	MaxLineSize             int
	MaxLineSizeTruncate     bool
}

LokiConfig is simplified subset of a Promtail client configuration

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 MockVirtualUser added in v0.1.2

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

MockVirtualUser is a mock virtual user

func NewMockVU added in v0.1.2

NewMockVU create a mock virtual user

func (MockVirtualUser) Call added in v0.1.2

func (m MockVirtualUser) Call(l *Generator)

func (MockVirtualUser) Clone added in v0.1.2

func (MockVirtualUser) Setup added in v0.1.2

func (m MockVirtualUser) Setup(_ *Generator) error

func (MockVirtualUser) Stop added in v0.1.2

func (m MockVirtualUser) Stop(_ *Generator)

func (MockVirtualUser) StopChan added in v0.1.2

func (m MockVirtualUser) StopChan() chan struct{}

func (MockVirtualUser) Teardown added in v0.1.2

func (m MockVirtualUser) Teardown(_ *Generator) error

type MockVirtualUserConfig added in v0.1.2

type MockVirtualUserConfig 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
}

MockVirtualUserConfig configures a mock virtual user

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 NewRPSProfile

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

NewRPSProfile creates new RPSProfile from parts

func NewVUProfile added in v0.1.2

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

NewVUProfile creates new virtual user profile 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 ProfileVUPart added in v0.1.2

type ProfileVUPart struct {
	Name     string
	Schedule []*Segment
	VU       VirtualUser
}

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

Combine combines profile segments

func CombineAndRepeat

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

CombineAndRepeat combines and repeats profile segments

func Line

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

Line creates a series of increasing/decreasing Segments

func Plain

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

Plain create a constant workload Segment

func (*Segment) Validate

func (ls *Segment) Validate() 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"`
	CurrentVUs     atomic.Int64 `json:"currentVUs"`
	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 VirtualUser added in v0.1.2

type VirtualUser interface {
	Call(l *Generator)
	Stop(l *Generator)
	Clone(l *Generator) VirtualUser
	Setup(l *Generator) error
	Teardown(l *Generator) error
	StopChan() chan struct{}
}

VirtualUser is basic interface to run virtual users load you should use it if: - your protocol is stateful, ex.: ws, grpc - you'd like to have some VirtualUser modelling

type WSMockVU added in v0.1.2

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

WSMockVU ws mock virtual user

func NewWSMockVU added in v0.1.2

func NewWSMockVU(cfg WSMockVUConfig) WSMockVU

NewWSMockVU create a ws mock virtual user

func (WSMockVU) Call added in v0.1.2

func (m WSMockVU) Call(l *Generator)

Call create a virtual user firing read requests against mock ws server

func (WSMockVU) Clone added in v0.1.2

func (m WSMockVU) Clone(_ *Generator) VirtualUser

func (WSMockVU) Setup added in v0.1.2

func (m WSMockVU) Setup(l *Generator) error

func (WSMockVU) Stop added in v0.1.2

func (m WSMockVU) Stop(_ *Generator)

func (WSMockVU) StopChan added in v0.1.2

func (m WSMockVU) StopChan() chan struct{}

func (WSMockVU) Teardown added in v0.1.2

func (m WSMockVU) Teardown(_ *Generator) error

type WSMockVUConfig added in v0.1.2

type WSMockVUConfig struct {
	TargetURl string
}

WSMockVUConfig ws mock config

type WaspAlert added in v0.1.1

type WaspAlert struct {
	Name                 string
	AlertType            string
	TestName             string
	GenName              string
	RequirementGroupName string
	AlertIf              alert.ConditionEvaluator
	CustomAlert          timeseries.Option
}

Directories

Path Synopsis
examples
alerts command
go_test command
profiles command
scenario command
simple_rps command
simple_vu command

Jump to

Keyboard shortcuts

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