Skip to content

Commit 0ae372d

Browse files
committed
feat(cli): integrate chromedp actions into scaletest dashboard
This commit integrates the chromedp actions in the previous commit into the scaletest dashboard command, and re-enables the previously disabled unit test. Note that this unit test does not actually run headless chrome, nor does it test the actual scaletest actions yet, as coderdtest only exposes an API and does not expose the actual site.
1 parent d4ceee7 commit 0ae372d

File tree

5 files changed

+108
-135
lines changed

5 files changed

+108
-135
lines changed

cli/exp_scaletest.go

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,9 +1046,10 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *clibase.Cmd {
10461046

10471047
func (r *RootCmd) scaletestDashboard() *clibase.Cmd {
10481048
var (
1049-
count int64
1050-
minWait time.Duration
1051-
maxWait time.Duration
1049+
count int64
1050+
minWait time.Duration
1051+
maxWait time.Duration
1052+
headless bool
10521053

10531054
client = &codersdk.Client{}
10541055
tracingFlags = &scaletestTracingFlags{}
@@ -1094,19 +1095,38 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd {
10941095

10951096
th := harness.NewTestHarness(strategy.toStrategy(), cleanupStrategy.toStrategy())
10961097

1097-
for i := int64(0); i < count; i++ {
1098-
name := fmt.Sprintf("dashboard-%d", i)
1098+
users, err := getScaletestUsers(ctx, client)
1099+
if err != nil {
1100+
return xerrors.Errorf("get scaletest users")
1101+
}
1102+
1103+
for _, usr := range users {
1104+
name := fmt.Sprintf("dashboard-%s", usr.Username)
1105+
userTokResp, err := client.CreateToken(ctx, usr.ID.String(), codersdk.CreateTokenRequest{
1106+
Lifetime: 30 * 24 * time.Hour,
1107+
Scope: "",
1108+
TokenName: fmt.Sprintf("scaletest-%d", time.Now().Unix()),
1109+
})
1110+
if err != nil {
1111+
return xerrors.Errorf("create token for user: %w", err)
1112+
}
1113+
1114+
userClient := codersdk.New(client.URL)
1115+
userClient.SetSessionToken(userTokResp.Key)
1116+
10991117
config := dashboard.Config{
1100-
MinWait: minWait,
1101-
MaxWait: maxWait,
1102-
Trace: tracingEnabled,
1103-
Logger: logger.Named(name),
1104-
RollTable: dashboard.DefaultActions,
1118+
MinWait: minWait,
1119+
MaxWait: maxWait,
1120+
Trace: tracingEnabled,
1121+
Logger: logger.Named(name),
1122+
Headless: headless,
1123+
ActionFunc: dashboard.ClickRandomElement,
11051124
}
1125+
logger.Info(ctx, "runner config", slog.F("min_wait", minWait), slog.F("max_wait", maxWait), slog.F("headless", headless))
11061126
if err := config.Validate(); err != nil {
11071127
return err
11081128
}
1109-
var runner harness.Runnable = dashboard.NewRunner(client, metrics, config)
1129+
var runner harness.Runnable = dashboard.NewRunner(userClient, metrics, config)
11101130
if tracingEnabled {
11111131
runner = &runnableTraceWrapper{
11121132
tracer: tracer,
@@ -1152,17 +1172,24 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd {
11521172
{
11531173
Flag: "min-wait",
11541174
Env: "CODER_SCALETEST_DASHBOARD_MIN_WAIT",
1155-
Default: "100ms",
1175+
Default: "1s",
11561176
Description: "Minimum wait between fetches.",
11571177
Value: clibase.DurationOf(&minWait),
11581178
},
11591179
{
11601180
Flag: "max-wait",
11611181
Env: "CODER_SCALETEST_DASHBOARD_MAX_WAIT",
1162-
Default: "1s",
1182+
Default: "10s",
11631183
Description: "Maximum wait between fetches.",
11641184
Value: clibase.DurationOf(&maxWait),
11651185
},
1186+
{
1187+
Flag: "headless",
1188+
Env: "CODER_SCALETEST_DASHBOARD_HEADLESS",
1189+
Default: "true",
1190+
Description: "Controls headless mode. Setting to false is useful for debugging.",
1191+
Value: clibase.BoolOf(&headless),
1192+
},
11661193
}
11671194

11681195
tracingFlags.attach(&cmd.Options)

scaletest/dashboard/config.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package dashboard
22

33
import (
4+
"context"
45
"time"
56

6-
"golang.org/x/xerrors"
7-
87
"cdr.dev/slog"
8+
9+
"golang.org/x/xerrors"
910
)
1011

1112
type Config struct {
@@ -17,8 +18,10 @@ type Config struct {
1718
Trace bool `json:"trace"`
1819
// Logger is the logger to use.
1920
Logger slog.Logger `json:"-"`
20-
// RollTable is the set of actions to perform
21-
RollTable RollTable `json:"roll_table"`
21+
// Headless controls headless mode for chromedp.
22+
Headless bool `json:"no_headless"`
23+
// ActionFunc is a function that returns an action to run.
24+
ActionFunc func(ctx context.Context) (Label, Action, error) `json:"-"`
2225
}
2326

2427
func (c Config) Validate() error {
@@ -34,5 +37,9 @@ func (c Config) Validate() error {
3437
return xerrors.Errorf("validate duration_min: must be less than duration_max")
3538
}
3639

40+
if c.ActionFunc == nil {
41+
return xerrors.Errorf("validate action func: must not be nil")
42+
}
43+
3744
return nil
3845
}

scaletest/dashboard/metrics.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,11 @@ import (
99
type Metrics interface {
1010
ObserveDuration(action string, d time.Duration)
1111
IncErrors(action string)
12-
IncStatuses(action string, code string)
1312
}
1413

1514
type PromMetrics struct {
1615
durationSeconds *prometheus.HistogramVec
1716
errors *prometheus.CounterVec
18-
statuses *prometheus.CounterVec
1917
}
2018

2119
func NewMetrics(reg prometheus.Registerer) *PromMetrics {
@@ -30,16 +28,10 @@ func NewMetrics(reg prometheus.Registerer) *PromMetrics {
3028
Subsystem: "scaletest_dashboard",
3129
Name: "errors_total",
3230
}, []string{"action"}),
33-
statuses: prometheus.NewCounterVec(prometheus.CounterOpts{
34-
Namespace: "coderd",
35-
Subsystem: "scaletest_dashboard",
36-
Name: "statuses_total",
37-
}, []string{"action", "code"}),
3831
}
3932

4033
reg.MustRegister(m.durationSeconds)
4134
reg.MustRegister(m.errors)
42-
reg.MustRegister(m.statuses)
4335
return m
4436
}
4537

@@ -50,7 +42,3 @@ func (p *PromMetrics) ObserveDuration(action string, d time.Duration) {
5042
func (p *PromMetrics) IncErrors(action string) {
5143
p.errors.WithLabelValues(action).Inc()
5244
}
53-
54-
func (p *PromMetrics) IncStatuses(action string, code string) {
55-
p.statuses.WithLabelValues(action, code).Inc()
56-
}

scaletest/dashboard/run.go

Lines changed: 34 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package dashboard
22

33
import (
44
"context"
5-
"fmt"
65
"io"
76
"math/rand"
87
"time"
@@ -35,46 +34,46 @@ func NewRunner(client *codersdk.Client, metrics Metrics, cfg Config) *Runner {
3534
}
3635

3736
func (r *Runner) Run(ctx context.Context, _ string, _ io.Writer) error {
37+
if r.client == nil {
38+
return xerrors.Errorf("client is nil")
39+
}
3840
me, err := r.client.User(ctx, codersdk.Me)
3941
if err != nil {
40-
return err
42+
return xerrors.Errorf("get scaletest user: %w", err)
4143
}
44+
r.cfg.Logger.Info(ctx, "running as user", slog.F("username", me.Username))
4245
if len(me.OrganizationIDs) == 0 {
4346
return xerrors.Errorf("user has no organizations")
4447
}
4548

46-
c := &cache{}
47-
if err := c.fill(ctx, r.client); err != nil {
48-
return err
49-
}
50-
51-
p := &Params{
52-
client: r.client,
53-
me: me,
54-
c: c,
49+
cdpCtx, cdpCancel, err := initChromeDPCtx(ctx, r.client.URL, r.client.SessionToken(), r.cfg.Headless)
50+
if err != nil {
51+
return xerrors.Errorf("init chromedp ctx: %w", err)
5552
}
56-
rolls := make(chan int)
57-
go func() {
58-
t := time.NewTicker(r.randWait())
59-
defer t.Stop()
60-
for {
61-
select {
62-
case <-ctx.Done():
63-
return
64-
case <-t.C:
65-
rolls <- rand.Intn(r.cfg.RollTable.max() + 1) // nolint:gosec
66-
t.Reset(r.randWait())
67-
}
68-
}
69-
}()
70-
53+
defer cdpCancel()
54+
t := time.NewTicker(1) // First one should be immediate
55+
defer t.Stop()
7156
for {
7257
select {
73-
case <-ctx.Done():
58+
case <-cdpCtx.Done():
7459
return nil
75-
case n := <-rolls:
76-
act := r.cfg.RollTable.choose(n)
77-
go r.do(ctx, act, p)
60+
case <-t.C:
61+
t.Reset(r.randWait())
62+
l, act, err := r.cfg.ActionFunc(cdpCtx)
63+
if err != nil {
64+
r.cfg.Logger.Error(ctx, "calling ActionFunc", slog.Error(err))
65+
continue
66+
}
67+
start := time.Now()
68+
err = act(cdpCtx)
69+
elapsed := time.Since(start)
70+
r.metrics.ObserveDuration(string(l), elapsed)
71+
if err != nil {
72+
r.metrics.IncErrors(string(l))
73+
r.cfg.Logger.Error(ctx, "action failed", slog.F("label", l), slog.Error(err))
74+
} else {
75+
r.cfg.Logger.Info(ctx, "action success", slog.F("label", l))
76+
}
7877
}
7978
}
8079
}
@@ -83,49 +82,12 @@ func (*Runner) Cleanup(_ context.Context, _ string) error {
8382
return nil
8483
}
8584

86-
func (r *Runner) do(ctx context.Context, act RollTableEntry, p *Params) {
87-
select {
88-
case <-ctx.Done():
89-
r.cfg.Logger.Info(ctx, "context done, stopping")
90-
return
91-
default:
92-
var errored bool
93-
cancelCtx, cancel := context.WithTimeout(ctx, r.cfg.MaxWait)
94-
defer cancel()
95-
start := time.Now()
96-
err := act.Fn(cancelCtx, p)
97-
cancel()
98-
elapsed := time.Since(start)
99-
if err != nil {
100-
errored = true
101-
r.cfg.Logger.Error( //nolint:gocritic
102-
ctx, "action failed",
103-
slog.Error(err),
104-
slog.F("action", act.Label),
105-
slog.F("elapsed", elapsed),
106-
)
107-
} else {
108-
r.cfg.Logger.Info(ctx, "completed successfully",
109-
slog.F("action", act.Label),
110-
slog.F("elapsed", elapsed),
111-
)
112-
}
113-
codeLabel := "200"
114-
if apiErr, ok := codersdk.AsError(err); ok {
115-
codeLabel = fmt.Sprintf("%d", apiErr.StatusCode())
116-
} else if xerrors.Is(err, context.Canceled) {
117-
codeLabel = "timeout"
118-
}
119-
r.metrics.ObserveDuration(act.Label, elapsed)
120-
r.metrics.IncStatuses(act.Label, codeLabel)
121-
if errored {
122-
r.metrics.IncErrors(act.Label)
123-
}
124-
}
125-
}
126-
12785
func (r *Runner) randWait() time.Duration {
12886
// nolint:gosec // This is not for cryptographic purposes. Chill, gosec. Chill.
129-
wait := time.Duration(rand.Intn(int(r.cfg.MaxWait) - int(r.cfg.MinWait)))
87+
var wait time.Duration
88+
if r.cfg.MaxWait > r.cfg.MinWait {
89+
wait = time.Duration(rand.Intn(int(r.cfg.MaxWait) - int(r.cfg.MinWait)))
90+
}
91+
13092
return r.cfg.MinWait + wait
13193
}

0 commit comments

Comments
 (0)