test(load): add idle and tunable group scenarios

This commit is contained in:
richjohnrambo
2026-10-05 17:36:18 +08:00
parent 965f94f154
commit d3054f62b5
8 changed files with 385 additions and 43 deletions
+79 -11
View File
@@ -49,7 +49,7 @@ type agentRun struct {
}
func validScenario(scenario string) bool {
return scenario == "p2p" || scenario == "group" || scenario == "mixed"
return scenario == "p2p" || scenario == "group" || scenario == "mixed" || scenario == "idle"
}
func preflightP2P(ctx context.Context, cfg *Config, manifest *Manifest, local []AccountRecord, apiKey string) error {
@@ -680,10 +680,25 @@ func (r *agentRun) connect(ctx context.Context, accounts []AccountRecord, groupT
}
func (r *agentRun) maintainConnections(ctx context.Context, accounts []AccountRecord, groupTopic, apiKey, password string) {
type reconnectState struct {
attempt int
nextAt time.Time
}
byIndex := make(map[int]AccountRecord, len(accounts))
for _, account := range accounts {
byIndex[account.Index] = account
}
states := make(map[int]reconnectState, len(accounts))
var stateMu sync.Mutex
minWait := mustDuration(r.cfg.ReconnectMinWait)
maxWait := mustDuration(r.cfg.ReconnectMaxWait)
if maxWait < minWait {
maxWait = minWait
}
reconnectWorkers := r.cfg.ReconnectWorkers
if reconnectWorkers <= 0 {
reconnectWorkers = 8
}
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
@@ -692,7 +707,7 @@ func (r *agentRun) maintainConnections(ctx context.Context, accounts []AccountRe
}
jobs := make(chan AccountRecord)
var wg sync.WaitGroup
workers := 16
workers := reconnectWorkers
if workers > len(accounts) {
workers = len(accounts)
}
@@ -714,8 +729,17 @@ func (r *agentRun) maintainConnections(ctx context.Context, accounts []AccountRe
if ctx.Err() == nil {
log.Printf("account=%s reconnect failed: %v", account.Username, err)
}
stateMu.Lock()
state := states[index]
state.attempt++
state.nextAt = time.Now().Add(reconnectBackoff(minWait, maxWait, state.attempt, index))
states[index] = state
stateMu.Unlock()
continue
}
stateMu.Lock()
delete(states, index)
stateMu.Unlock()
r.setClient(index, client)
log.Printf("account=%s connection restored", account.Username)
}
@@ -732,6 +756,12 @@ func (r *agentRun) maintainConnections(ctx context.Context, accounts []AccountRe
if current != nil && current.alive() {
continue
}
stateMu.Lock()
nextAt := states[index].nextAt
stateMu.Unlock()
if !nextAt.IsZero() && time.Now().Before(nextAt) {
continue
}
select {
case jobs <- account:
case <-ctx.Done():
@@ -750,6 +780,25 @@ func (r *agentRun) maintainConnections(ctx context.Context, accounts []AccountRe
}
}
func reconnectBackoff(minWait, maxWait time.Duration, attempt, index int) time.Duration {
if attempt < 1 {
attempt = 1
}
delay := minWait
for i := 1; i < attempt && delay < maxWait; i++ {
delay *= 2
}
if delay > maxWait {
delay = maxWait
}
jitterBase := delay / 4
if jitterBase <= 0 {
return delay
}
jitter := time.Duration((index*97+attempt*53)%1000) * jitterBase / 1000
return delay + jitter
}
func (r *agentRun) connectAccountWithRetry(ctx context.Context, account AccountRecord, groupTopic, apiKey, password string) (*WSClient, error) {
var lastErr error
for attempt := 1; attempt <= 6; attempt++ {
@@ -787,12 +836,25 @@ func (r *agentRun) execute(ctx context.Context, start time.Time, groupTopic stri
case "p2p":
return r.scheduleP2P(ctx, start, r.cfg.UsersPerMachine, r.cfg.P2PMessages, mustDuration(r.cfg.P2PInterval))
case "group":
return r.scheduleGroup(ctx, start, groupTopic, 1, scaledUsers(r.cfg.UsersPerMachine, 1, 2), 2)
senders := r.cfg.GroupSenders
if senders <= 0 {
senders = scaledUsers(r.cfg.UsersPerMachine, 1, 2)
}
if senders > r.cfg.UsersPerMachine {
return fmt.Errorf("group_senders_per_machine=%d exceeds users_per_machine=%d", senders, r.cfg.UsersPerMachine)
}
return r.scheduleGroup(ctx, start, groupTopic, 1, senders, r.cfg.GroupRounds, mustDuration(r.cfg.GroupInterval))
case "mixed":
p2pUsers := scaledUsers(r.cfg.UsersPerMachine, 3, 10)
groupSenders := scaledUsers(r.cfg.UsersPerMachine, 1, 4)
p2pUsers := r.cfg.MixedP2PUsers
if p2pUsers <= 0 {
p2pUsers = scaledUsers(r.cfg.UsersPerMachine, 3, 10)
}
groupSenders := r.cfg.MixedGroupSenders
if groupSenders <= 0 {
groupSenders = scaledUsers(r.cfg.UsersPerMachine, 1, 4)
}
if p2pUsers+groupSenders > r.cfg.UsersPerMachine {
groupSenders = r.cfg.UsersPerMachine - p2pUsers
return fmt.Errorf("mixed_p2p_users_per_machine + mixed_group_senders_per_machine exceeds users_per_machine")
}
if groupSenders <= 0 {
return fmt.Errorf("users_per_machine=%d is too small for mixed scenario", r.cfg.UsersPerMachine)
@@ -803,7 +865,7 @@ func (r *agentRun) execute(ctx context.Context, start time.Time, groupTopic stri
go func() { defer wg.Done(); errCh <- r.scheduleP2P(ctx, start, p2pUsers, 5, 20*time.Second) }()
go func() {
defer wg.Done()
errCh <- r.scheduleGroup(ctx, start, groupTopic, p2pUsers+1, p2pUsers+groupSenders, 2)
errCh <- r.scheduleGroup(ctx, start, groupTopic, p2pUsers+1, p2pUsers+groupSenders, r.cfg.GroupRounds, mustDuration(r.cfg.GroupInterval))
}()
wg.Wait()
close(errCh)
@@ -813,6 +875,8 @@ func (r *agentRun) execute(ctx context.Context, start time.Time, groupTopic stri
}
}
return nil
case "idle":
return waitContext(ctx, mustDuration(r.cfg.IdleDuration))
default:
return fmt.Errorf("unknown scenario %s", r.scenario)
}
@@ -867,15 +931,19 @@ func (r *agentRun) scheduleP2P(ctx context.Context, start time.Time, users, mess
return ctx.Err()
}
func (r *agentRun) scheduleGroup(ctx context.Context, start time.Time, topic string, first, last, rounds int) error {
func (r *agentRun) scheduleGroup(ctx context.Context, start time.Time, topic string, first, last, rounds int, globalInterval time.Duration) error {
if first < 1 || last > r.clientCount() || first > last {
return fmt.Errorf("invalid group publisher range %d-%d", first, last)
}
count := last - first + 1
roundDuration := time.Duration(count) * 200 * time.Millisecond
if globalInterval <= 0 {
return fmt.Errorf("group publish interval must be positive")
}
perMachineInterval := 2 * globalInterval
roundDuration := time.Duration(count) * perMachineInterval
offset := time.Duration(0)
if r.machine == "b" {
offset = 100 * time.Millisecond
offset = globalInterval
}
var wg sync.WaitGroup
for index := first; index <= last; index++ {
@@ -884,7 +952,7 @@ func (r *agentRun) scheduleGroup(ctx context.Context, start time.Time, topic str
go func(index, position int) {
defer wg.Done()
for round := 1; round <= rounds; round++ {
target := start.Add(time.Duration(round-1)*roundDuration + time.Duration(position)*200*time.Millisecond + offset)
target := start.Add(time.Duration(round-1)*roundDuration + time.Duration(position)*perMachineInterval + offset)
if waitUntil(ctx, target) != nil {
return
}