Files
nezha_domains/integration/agentcompat/internal/scenario/stress_plan.go
T
2026-07-20 04:54:03 +00:00

121 lines
4.1 KiB
Go

//go:build linux
package scenario
import (
"errors"
"fmt"
"github.com/nezhahq/nezha/integration/agentcompat/internal/contract"
)
var ErrStressPlan = errors.New("stress plan is invalid")
type StressOperationPlan struct {
ID StressOperationID `json:"id"`
Round int `json:"round"`
Agent StressAgentOrdinal `json:"agent"`
PAT StressPATID `json:"pat_id"`
Kind StressOperationKind `json:"kind"`
}
type StressRoundPlan struct {
Round int `json:"round"`
Operations []StressOperationPlan `json:"operations"`
}
type StressSessionPlan struct {
ID StressSessionID `json:"id"`
Kind StressSessionKind `json:"kind"`
Ordinal int `json:"ordinal"`
Agent StressAgentOrdinal `json:"agent"`
}
type StressPlan struct {
Profile contract.ProfileName `json:"profile"`
Seed contract.Seed `json:"seed"`
Rounds []StressRoundPlan `json:"rounds"`
Sessions []StressSessionPlan `json:"sessions"`
}
func GenerateStressPlan(profile contract.Profile, seed contract.Seed) (StressPlan, error) {
if seed == 0 || profile.AgentCount() < 1 || profile.StressRounds() < 1 || profile.ConcurrentSessions() < 1 {
return StressPlan{}, ErrStressPlan
}
expectedOperations := profile.AgentCount() * profile.StressRounds() * 2
if profile.ConcurrentOperations() != expectedOperations {
return StressPlan{}, fmt.Errorf("concurrent operations=%d want=%d: %w", profile.ConcurrentOperations(), expectedOperations, ErrStressPlan)
}
plan := StressPlan{Profile: profile.Name(), Seed: seed}
plan.Rounds = make([]StressRoundPlan, profile.StressRounds())
for roundIndex := range plan.Rounds {
round := roundIndex + 1
operations, err := stressRoundOperations(seed, round, profile.AgentCount())
if err != nil {
return StressPlan{}, err
}
plan.Rounds[roundIndex] = StressRoundPlan{Round: round, Operations: operations}
}
sessions, err := stressSessionPlans(seed, profile.AgentCount(), profile.ConcurrentSessions())
if err != nil {
return StressPlan{}, err
}
plan.Sessions = sessions
return plan, nil
}
func stressRoundOperations(seed contract.Seed, round, agentCount int) ([]StressOperationPlan, error) {
operations := make([]StressOperationPlan, 0, agentCount*2)
for agentValue := 1; agentValue <= agentCount; agentValue++ {
agent, err := NewStressAgentOrdinal(agentValue)
if err != nil {
return nil, err
}
pat, err := NewStressPATID(fmt.Sprintf("pat-%016x-a%02d", uint64(seed), agentValue))
if err != nil {
return nil, err
}
for _, kind := range []StressOperationKind{StressOperationExec, StressOperationFilesystem} {
identity, idErr := NewStressOperationID(fmt.Sprintf("op-%016x-r%02d-a%02d-%s", uint64(seed), round, agentValue, kind))
if idErr != nil {
return nil, idErr
}
operations = append(operations, StressOperationPlan{ID: identity, Round: round, Agent: agent, PAT: pat, Kind: kind})
}
}
random := stressRandom(uint64(seed) ^ uint64(round)*0x9e3779b97f4a7c15)
for index := len(operations) - 1; index > 0; index-- {
swap := int(random.next() % uint64(index+1))
operations[index], operations[swap] = operations[swap], operations[index]
}
return operations, nil
}
func stressSessionPlans(seed contract.Seed, agentCount, countPerKind int) ([]StressSessionPlan, error) {
sessions := make([]StressSessionPlan, 0, countPerKind*3)
for _, kind := range []StressSessionKind{StressSessionTerminal, StressSessionNAT, StressSessionFM} {
for index := 1; index <= countPerKind; index++ {
agent, err := NewStressAgentOrdinal((index-1)%agentCount + 1)
if err != nil {
return nil, err
}
identity, err := NewStressSessionID(fmt.Sprintf("session-%016x-%s-%02d", uint64(seed), kind, index))
if err != nil {
return nil, err
}
sessions = append(sessions, StressSessionPlan{ID: identity, Kind: kind, Ordinal: index, Agent: agent})
}
}
return sessions, nil
}
type stressRandom uint64
func (random *stressRandom) next() uint64 {
*random += 0x9e3779b97f4a7c15
value := uint64(*random)
value = (value ^ (value >> 30)) * 0xbf58476d1ce4e5b9
value = (value ^ (value >> 27)) * 0x94d049bb133111eb
return value ^ (value >> 31)
}