mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
* chore: add /v2 to import module path go mod requires semantic versioning with versions greater than 1.x This was a mechanical update by running: ``` go install github.com/marwan-at-work/mod/cmd/mod@latest mod upgrade ``` Migrate generated files to import /v2 * Fix gen
132 lines
2.6 KiB
Go
132 lines
2.6 KiB
Go
package dashboard
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"time"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
"cdr.dev/slog"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/coder/v2/scaletest/harness"
|
|
)
|
|
|
|
type Runner struct {
|
|
client *codersdk.Client
|
|
cfg Config
|
|
metrics Metrics
|
|
}
|
|
|
|
var (
|
|
_ harness.Runnable = &Runner{}
|
|
_ harness.Cleanable = &Runner{}
|
|
)
|
|
|
|
func NewRunner(client *codersdk.Client, metrics Metrics, cfg Config) *Runner {
|
|
client.Trace = cfg.Trace
|
|
return &Runner{
|
|
client: client,
|
|
cfg: cfg,
|
|
metrics: metrics,
|
|
}
|
|
}
|
|
|
|
func (r *Runner) Run(ctx context.Context, _ string, _ io.Writer) error {
|
|
me, err := r.client.User(ctx, codersdk.Me)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(me.OrganizationIDs) == 0 {
|
|
return xerrors.Errorf("user has no organizations")
|
|
}
|
|
|
|
c := &cache{}
|
|
if err := c.fill(ctx, r.client); err != nil {
|
|
return err
|
|
}
|
|
|
|
p := &Params{
|
|
client: r.client,
|
|
me: me,
|
|
c: c,
|
|
}
|
|
rolls := make(chan int)
|
|
go func() {
|
|
t := time.NewTicker(r.randWait())
|
|
defer t.Stop()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-t.C:
|
|
rolls <- rand.Intn(r.cfg.RollTable.max() + 1) // nolint:gosec
|
|
t.Reset(r.randWait())
|
|
}
|
|
}
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case n := <-rolls:
|
|
act := r.cfg.RollTable.choose(n)
|
|
go r.do(ctx, act, p)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (*Runner) Cleanup(_ context.Context, _ string) error {
|
|
return nil
|
|
}
|
|
|
|
func (r *Runner) do(ctx context.Context, act RollTableEntry, p *Params) {
|
|
select {
|
|
case <-ctx.Done():
|
|
r.cfg.Logger.Info(ctx, "context done, stopping")
|
|
return
|
|
default:
|
|
var errored bool
|
|
cancelCtx, cancel := context.WithTimeout(ctx, r.cfg.MaxWait)
|
|
defer cancel()
|
|
start := time.Now()
|
|
err := act.Fn(cancelCtx, p)
|
|
cancel()
|
|
elapsed := time.Since(start)
|
|
if err != nil {
|
|
errored = true
|
|
r.cfg.Logger.Error( //nolint:gocritic
|
|
ctx, "action failed",
|
|
slog.Error(err),
|
|
slog.F("action", act.Label),
|
|
slog.F("elapsed", elapsed),
|
|
)
|
|
} else {
|
|
r.cfg.Logger.Info(ctx, "completed successfully",
|
|
slog.F("action", act.Label),
|
|
slog.F("elapsed", elapsed),
|
|
)
|
|
}
|
|
codeLabel := "200"
|
|
if apiErr, ok := codersdk.AsError(err); ok {
|
|
codeLabel = fmt.Sprintf("%d", apiErr.StatusCode())
|
|
} else if xerrors.Is(err, context.Canceled) {
|
|
codeLabel = "timeout"
|
|
}
|
|
r.metrics.ObserveDuration(act.Label, elapsed)
|
|
r.metrics.IncStatuses(act.Label, codeLabel)
|
|
if errored {
|
|
r.metrics.IncErrors(act.Label)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *Runner) randWait() time.Duration {
|
|
// nolint:gosec // This is not for cryptographic purposes. Chill, gosec. Chill.
|
|
wait := time.Duration(rand.Intn(int(r.cfg.MaxWait) - int(r.cfg.MinWait)))
|
|
return r.cfg.MinWait + wait
|
|
}
|