mirror of
https://github.com/coder/coder.git
synced 2025-07-09 11:45:56 +00:00
198 lines
5.3 KiB
Go
198 lines
5.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"time"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/coder/coder/coderd/httpapi"
|
|
"github.com/coder/coder/codersdk"
|
|
"github.com/coder/coder/loadtest/agentconn"
|
|
"github.com/coder/coder/loadtest/harness"
|
|
"github.com/coder/coder/loadtest/placebo"
|
|
"github.com/coder/coder/loadtest/workspacebuild"
|
|
)
|
|
|
|
// LoadTestConfig is the overall configuration for a call to `coder loadtest`.
|
|
type LoadTestConfig struct {
|
|
Strategy LoadTestStrategy `json:"strategy"`
|
|
Tests []LoadTest `json:"tests"`
|
|
// Timeout sets a timeout for the entire test run, to control the timeout
|
|
// for each individual run use strategy.timeout.
|
|
Timeout httpapi.Duration `json:"timeout"`
|
|
}
|
|
|
|
type LoadTestStrategyType string
|
|
|
|
const (
|
|
LoadTestStrategyTypeLinear LoadTestStrategyType = "linear"
|
|
LoadTestStrategyTypeConcurrent LoadTestStrategyType = "concurrent"
|
|
)
|
|
|
|
type LoadTestStrategy struct {
|
|
// Type is the type of load test strategy to use. Strategies determine how
|
|
// to run tests concurrently.
|
|
Type LoadTestStrategyType `json:"type"`
|
|
|
|
// ConcurrencyLimit is the maximum number of concurrent runs. This only
|
|
// applies if type == "concurrent". Negative values disable the concurrency
|
|
// limit and attempts to perform all runs concurrently. The default value is
|
|
// 100.
|
|
ConcurrencyLimit int `json:"concurrency_limit"`
|
|
|
|
// Shuffle determines whether or not to shuffle the test runs before
|
|
// executing them.
|
|
Shuffle bool `json:"shuffle"`
|
|
// Timeout is the maximum amount of time to run each test for. This is
|
|
// independent of the timeout specified in the test run. A timeout of 0
|
|
// disables the timeout.
|
|
Timeout httpapi.Duration `json:"timeout"`
|
|
}
|
|
|
|
func (s LoadTestStrategy) ExecutionStrategy() harness.ExecutionStrategy {
|
|
var strategy harness.ExecutionStrategy
|
|
switch s.Type {
|
|
case LoadTestStrategyTypeLinear:
|
|
strategy = harness.LinearExecutionStrategy{}
|
|
case LoadTestStrategyTypeConcurrent:
|
|
limit := s.ConcurrencyLimit
|
|
if limit < 0 {
|
|
return harness.ConcurrentExecutionStrategy{}
|
|
}
|
|
if limit == 0 {
|
|
limit = 100
|
|
}
|
|
strategy = harness.ParallelExecutionStrategy{
|
|
Limit: limit,
|
|
}
|
|
default:
|
|
panic("unreachable, unknown strategy type " + s.Type)
|
|
}
|
|
|
|
if s.Timeout > 0 {
|
|
strategy = harness.TimeoutExecutionStrategyWrapper{
|
|
Timeout: time.Duration(s.Timeout),
|
|
Inner: strategy,
|
|
}
|
|
}
|
|
if s.Shuffle {
|
|
strategy = harness.ShuffleExecutionStrategyWrapper{
|
|
Inner: strategy,
|
|
}
|
|
}
|
|
|
|
return strategy
|
|
}
|
|
|
|
type LoadTestType string
|
|
|
|
const (
|
|
LoadTestTypeAgentConn LoadTestType = "agentconn"
|
|
LoadTestTypePlacebo LoadTestType = "placebo"
|
|
LoadTestTypeWorkspaceBuild LoadTestType = "workspacebuild"
|
|
)
|
|
|
|
type LoadTest struct {
|
|
// Type is the type of load test to run.
|
|
Type LoadTestType `json:"type"`
|
|
// Count is the number of test runs to execute with this configuration. If
|
|
// the count is 0 or negative, defaults to 1.
|
|
Count int `json:"count"`
|
|
|
|
// AgentConn must be set if type == "agentconn".
|
|
AgentConn *agentconn.Config `json:"agentconn,omitempty"`
|
|
// Placebo must be set if type == "placebo".
|
|
Placebo *placebo.Config `json:"placebo,omitempty"`
|
|
// WorkspaceBuild must be set if type == "workspacebuild".
|
|
WorkspaceBuild *workspacebuild.Config `json:"workspacebuild,omitempty"`
|
|
}
|
|
|
|
func (t LoadTest) NewRunner(client *codersdk.Client) (harness.Runnable, error) {
|
|
switch t.Type {
|
|
case LoadTestTypeAgentConn:
|
|
if t.AgentConn == nil {
|
|
return nil, xerrors.New("agentconn config must be set")
|
|
}
|
|
return agentconn.NewRunner(client, *t.AgentConn), nil
|
|
case LoadTestTypePlacebo:
|
|
if t.Placebo == nil {
|
|
return nil, xerrors.New("placebo config must be set")
|
|
}
|
|
return placebo.NewRunner(*t.Placebo), nil
|
|
case LoadTestTypeWorkspaceBuild:
|
|
if t.WorkspaceBuild == nil {
|
|
return nil, xerrors.Errorf("workspacebuild config must be set")
|
|
}
|
|
return workspacebuild.NewRunner(client, *t.WorkspaceBuild), nil
|
|
default:
|
|
return nil, xerrors.Errorf("unknown test type %q", t.Type)
|
|
}
|
|
}
|
|
|
|
func (c *LoadTestConfig) Validate() error {
|
|
err := c.Strategy.Validate()
|
|
if err != nil {
|
|
return xerrors.Errorf("validate strategy: %w", err)
|
|
}
|
|
|
|
for i, test := range c.Tests {
|
|
err := test.Validate()
|
|
if err != nil {
|
|
return xerrors.Errorf("validate test %d: %w", i, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *LoadTestStrategy) Validate() error {
|
|
switch s.Type {
|
|
case LoadTestStrategyTypeLinear:
|
|
case LoadTestStrategyTypeConcurrent:
|
|
default:
|
|
return xerrors.Errorf("invalid load test strategy type: %q", s.Type)
|
|
}
|
|
|
|
if s.Timeout < 0 {
|
|
return xerrors.Errorf("invalid load test strategy timeout: %q", s.Timeout)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *LoadTest) Validate() error {
|
|
switch t.Type {
|
|
case LoadTestTypeAgentConn:
|
|
if t.AgentConn == nil {
|
|
return xerrors.Errorf("agentconn test type must specify agentconn")
|
|
}
|
|
|
|
err := t.AgentConn.Validate()
|
|
if err != nil {
|
|
return xerrors.Errorf("validate agentconn: %w", err)
|
|
}
|
|
case LoadTestTypePlacebo:
|
|
if t.Placebo == nil {
|
|
return xerrors.Errorf("placebo test type must specify placebo")
|
|
}
|
|
|
|
err := t.Placebo.Validate()
|
|
if err != nil {
|
|
return xerrors.Errorf("validate placebo: %w", err)
|
|
}
|
|
case LoadTestTypeWorkspaceBuild:
|
|
if t.WorkspaceBuild == nil {
|
|
return xerrors.New("workspacebuild test type must specify workspacebuild")
|
|
}
|
|
|
|
err := t.WorkspaceBuild.Validate()
|
|
if err != nil {
|
|
return xerrors.Errorf("validate workspacebuild: %w", err)
|
|
}
|
|
default:
|
|
return xerrors.Errorf("invalid load test type: %q", t.Type)
|
|
}
|
|
|
|
return nil
|
|
}
|