mirror of
https://github.com/httprunner/httprunner.git
synced 2026-08-04 21:17:01 +08:00
refactor
This commit is contained in:
@@ -71,7 +71,7 @@ var boomCmd = &cobra.Command{
|
|||||||
if boomArgs.autoStart {
|
if boomArgs.autoStart {
|
||||||
hrpBoomer.SetAutoStart()
|
hrpBoomer.SetAutoStart()
|
||||||
hrpBoomer.SetExpectWorkers(boomArgs.expectWorkers, boomArgs.expectWorkersMaxWait)
|
hrpBoomer.SetExpectWorkers(boomArgs.expectWorkers, boomArgs.expectWorkersMaxWait)
|
||||||
hrpBoomer.SetSpawnCount(boomArgs.SpawnCount)
|
hrpBoomer.SetSpawnCount(int64(boomArgs.SpawnCount))
|
||||||
hrpBoomer.SetSpawnRate(boomArgs.SpawnRate)
|
hrpBoomer.SetSpawnRate(boomArgs.SpawnRate)
|
||||||
}
|
}
|
||||||
go hrpBoomer.StartServer()
|
go hrpBoomer.StartServer()
|
||||||
|
|||||||
@@ -39,9 +39,6 @@ type Boomer struct {
|
|||||||
|
|
||||||
testcasePath []string
|
testcasePath []string
|
||||||
|
|
||||||
spawnCount int // target clients to spawn
|
|
||||||
spawnRate float64
|
|
||||||
|
|
||||||
cpuProfile string
|
cpuProfile string
|
||||||
cpuProfileDuration time.Duration
|
cpuProfileDuration time.Duration
|
||||||
|
|
||||||
@@ -86,8 +83,6 @@ func NewStandaloneBoomer(spawnCount int, spawnRate float64) *Boomer {
|
|||||||
return &Boomer{
|
return &Boomer{
|
||||||
mode: StandaloneMode,
|
mode: StandaloneMode,
|
||||||
localRunner: newLocalRunner(spawnCount, spawnRate),
|
localRunner: newLocalRunner(spawnCount, spawnRate),
|
||||||
spawnCount: spawnCount,
|
|
||||||
spawnRate: spawnRate,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,18 +156,26 @@ func (b *Boomer) GetState() int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetSpawnCount sets spawn count
|
// SetSpawnCount sets spawn count
|
||||||
func (b *Boomer) SetSpawnCount(spawnCount int) {
|
func (b *Boomer) SetSpawnCount(spawnCount int64) {
|
||||||
b.spawnCount = spawnCount
|
switch b.mode {
|
||||||
if b.mode == DistributedMasterMode {
|
case DistributedMasterMode:
|
||||||
b.masterRunner.spawn.setSpawn(int64(spawnCount), -1)
|
b.masterRunner.setSpawnCount(spawnCount)
|
||||||
|
case DistributedWorkerMode:
|
||||||
|
b.workerRunner.setSpawnCount(spawnCount)
|
||||||
|
default:
|
||||||
|
b.localRunner.setSpawnCount(spawnCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSpawnRate sets spawn rate
|
// SetSpawnRate sets spawn rate
|
||||||
func (b *Boomer) SetSpawnRate(spawnRate float64) {
|
func (b *Boomer) SetSpawnRate(spawnRate float64) {
|
||||||
b.spawnRate = spawnRate
|
switch b.mode {
|
||||||
if b.mode == DistributedMasterMode {
|
case DistributedMasterMode:
|
||||||
b.masterRunner.spawn.setSpawn(-1, spawnRate)
|
b.masterRunner.setSpawnRate(spawnRate)
|
||||||
|
case DistributedWorkerMode:
|
||||||
|
b.workerRunner.setSpawnRate(spawnRate)
|
||||||
|
default:
|
||||||
|
b.localRunner.setSpawnRate(spawnRate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,11 +245,11 @@ func (b *Boomer) SetLoopCount(loopCount int64) {
|
|||||||
// total loop count for testcase, it will be evenly distributed to each worker
|
// total loop count for testcase, it will be evenly distributed to each worker
|
||||||
switch b.mode {
|
switch b.mode {
|
||||||
case DistributedWorkerMode:
|
case DistributedWorkerMode:
|
||||||
b.workerRunner.loop = &Loop{loopCount: loopCount * b.workerRunner.spawn.getSpawnCount()}
|
b.workerRunner.loop = &Loop{loopCount: loopCount * b.workerRunner.getSpawnCount()}
|
||||||
case DistributedMasterMode:
|
case DistributedMasterMode:
|
||||||
b.masterRunner.loop = &Loop{loopCount: loopCount * b.masterRunner.spawn.getSpawnCount()}
|
b.masterRunner.loop = &Loop{loopCount: loopCount * b.masterRunner.getSpawnCount()}
|
||||||
case StandaloneMode:
|
case StandaloneMode:
|
||||||
b.localRunner.loop = &Loop{loopCount: loopCount * b.localRunner.spawn.getSpawnCount()}
|
b.localRunner.loop = &Loop{loopCount: loopCount * b.localRunner.getSpawnCount()}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,6 +391,9 @@ func (b *Boomer) RecordFailure(requestType, name string, responseTime int64, exc
|
|||||||
|
|
||||||
// Start starts to run
|
// Start starts to run
|
||||||
func (b *Boomer) Start(Args map[string]interface{}) error {
|
func (b *Boomer) Start(Args map[string]interface{}) error {
|
||||||
|
if b.masterRunner.isStarted() {
|
||||||
|
return errors.New("already started")
|
||||||
|
}
|
||||||
spawnCount, ok := Args["spawn_count"]
|
spawnCount, ok := Args["spawn_count"]
|
||||||
if ok {
|
if ok {
|
||||||
v, err := strconv.Atoi(spawnCount.(string))
|
v, err := strconv.Atoi(spawnCount.(string))
|
||||||
@@ -395,7 +401,7 @@ func (b *Boomer) Start(Args map[string]interface{}) error {
|
|||||||
log.Error().Err(err).Msg("spawn_count sets error")
|
log.Error().Err(err).Msg("spawn_count sets error")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.SetSpawnCount(v)
|
b.SetSpawnCount(int64(v))
|
||||||
} else {
|
} else {
|
||||||
return errors.New("spawn count error")
|
return errors.New("spawn count error")
|
||||||
}
|
}
|
||||||
@@ -423,6 +429,9 @@ func (b *Boomer) Start(Args map[string]interface{}) error {
|
|||||||
|
|
||||||
// ReBalance starts to rebalance load test
|
// ReBalance starts to rebalance load test
|
||||||
func (b *Boomer) ReBalance(Args map[string]interface{}) error {
|
func (b *Boomer) ReBalance(Args map[string]interface{}) error {
|
||||||
|
if !b.masterRunner.isStarted() {
|
||||||
|
return errors.New("no start")
|
||||||
|
}
|
||||||
spawnCount, ok := Args["spawn_count"]
|
spawnCount, ok := Args["spawn_count"]
|
||||||
if ok {
|
if ok {
|
||||||
v, err := strconv.Atoi(spawnCount.(string))
|
v, err := strconv.Atoi(spawnCount.(string))
|
||||||
@@ -430,7 +439,7 @@ func (b *Boomer) ReBalance(Args map[string]interface{}) error {
|
|||||||
log.Error().Err(err).Msg("spawn_count sets error")
|
log.Error().Err(err).Msg("spawn_count sets error")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.SetSpawnCount(v)
|
b.SetSpawnCount(int64(v))
|
||||||
}
|
}
|
||||||
spawnRate, ok := Args["spawn_rate"]
|
spawnRate, ok := Args["spawn_rate"]
|
||||||
if ok {
|
if ok {
|
||||||
@@ -441,11 +450,6 @@ func (b *Boomer) ReBalance(Args map[string]interface{}) error {
|
|||||||
}
|
}
|
||||||
b.SetSpawnRate(v)
|
b.SetSpawnRate(v)
|
||||||
}
|
}
|
||||||
path, ok := Args["path"].(string)
|
|
||||||
if ok {
|
|
||||||
paths := strings.Split(path, ",")
|
|
||||||
b.SetTestCasesPath(paths)
|
|
||||||
}
|
|
||||||
err := b.masterRunner.rebalance()
|
err := b.masterRunner.rebalance()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("failed to rebalance")
|
log.Error().Err(err).Msg("failed to rebalance")
|
||||||
@@ -454,12 +458,8 @@ func (b *Boomer) ReBalance(Args map[string]interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops to load test
|
// Stop stops to load test
|
||||||
func (b *Boomer) Stop() {
|
func (b *Boomer) Stop() error {
|
||||||
switch b.mode {
|
return b.masterRunner.stop()
|
||||||
case DistributedMasterMode:
|
|
||||||
b.masterRunner.stop()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWorkersInfo gets workers
|
// GetWorkersInfo gets workers
|
||||||
@@ -493,22 +493,22 @@ func (b *Boomer) Quit() {
|
|||||||
func (b *Boomer) GetSpawnDoneChan() chan struct{} {
|
func (b *Boomer) GetSpawnDoneChan() chan struct{} {
|
||||||
switch b.mode {
|
switch b.mode {
|
||||||
case DistributedWorkerMode:
|
case DistributedWorkerMode:
|
||||||
return b.workerRunner.spawn.getSpawnDone()
|
return b.workerRunner.controller.getSpawnDone()
|
||||||
case DistributedMasterMode:
|
case DistributedMasterMode:
|
||||||
return b.masterRunner.spawn.getSpawnDone()
|
return b.masterRunner.controller.getSpawnDone()
|
||||||
default:
|
default:
|
||||||
return b.localRunner.spawn.getSpawnDone()
|
return b.localRunner.controller.getSpawnDone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Boomer) GetSpawnCount() int {
|
func (b *Boomer) GetSpawnCount() int {
|
||||||
switch b.mode {
|
switch b.mode {
|
||||||
case DistributedWorkerMode:
|
case DistributedWorkerMode:
|
||||||
return int(b.workerRunner.spawn.getSpawnCount())
|
return int(b.workerRunner.getSpawnCount())
|
||||||
case DistributedMasterMode:
|
case DistributedMasterMode:
|
||||||
return int(b.masterRunner.spawn.getSpawnCount())
|
return int(b.masterRunner.getSpawnCount())
|
||||||
default:
|
default:
|
||||||
return int(b.localRunner.spawn.getSpawnCount())
|
return int(b.localRunner.getSpawnCount())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ import (
|
|||||||
func TestNewStandaloneBoomer(t *testing.T) {
|
func TestNewStandaloneBoomer(t *testing.T) {
|
||||||
b := NewStandaloneBoomer(100, 10)
|
b := NewStandaloneBoomer(100, 10)
|
||||||
|
|
||||||
if b.localRunner.spawn.spawnCount != 100 {
|
if b.localRunner.spawnCount != 100 {
|
||||||
t.Error("spawnCount should be 100")
|
t.Error("spawnCount should be 100")
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.localRunner.spawn.spawnRate != 10 {
|
if b.localRunner.spawnRate != 10 {
|
||||||
t.Error("spawnRate should be 10")
|
t.Error("spawnRate should be 10")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ type statsEntryOutput struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type dataOutput struct {
|
type dataOutput struct {
|
||||||
UserCount int32 `json:"user_count"`
|
UserCount int64 `json:"user_count"`
|
||||||
State int32 `json:"state"`
|
State int32 `json:"state"`
|
||||||
TotalStats *statsEntryOutput `json:"stats_total"`
|
TotalStats *statsEntryOutput `json:"stats_total"`
|
||||||
TransactionsPassed int64 `json:"transactions_passed"`
|
TransactionsPassed int64 `json:"transactions_passed"`
|
||||||
@@ -186,7 +186,7 @@ type dataOutput struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func convertData(data map[string]interface{}) (output *dataOutput, err error) {
|
func convertData(data map[string]interface{}) (output *dataOutput, err error) {
|
||||||
userCount, ok := data["user_count"].(int32)
|
userCount, ok := data["user_count"].(int64)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("user_count is not int32")
|
return nil, fmt.Errorf("user_count is not int32")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,82 +57,106 @@ func (l *Loop) increaseFinishedCount() {
|
|||||||
atomic.AddInt64(&l.finishedCount, 1)
|
atomic.AddInt64(&l.finishedCount, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SpawnInfo struct {
|
type Controller struct {
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
spawnCount int64 // target clients to spawn
|
once sync.Once
|
||||||
acquiredCount int64 // count acquired of workers
|
currentClientsNum int64 // current clients count
|
||||||
spawnRate float64
|
spawnCount int64 // target clients to spawn
|
||||||
spawnDone chan struct{}
|
spawnRate float64
|
||||||
|
spawnDone chan struct{}
|
||||||
|
tasks []*Task
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SpawnInfo) setSpawn(spawnCount int64, spawnRate float64) {
|
func (c *Controller) setSpawn(spawnCount int64, spawnRate float64) {
|
||||||
s.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
if spawnCount > 0 {
|
if spawnCount > 0 {
|
||||||
atomic.StoreInt64(&s.spawnCount, spawnCount)
|
atomic.StoreInt64(&c.spawnCount, spawnCount)
|
||||||
}
|
}
|
||||||
if spawnRate > 0 {
|
if spawnRate > 0 {
|
||||||
s.spawnRate = spawnRate
|
c.spawnRate = spawnRate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SpawnInfo) getSpawnCount() int64 {
|
func (c *Controller) setSpawnCount(spawnCount int64) {
|
||||||
s.mutex.RLock()
|
if spawnCount > 0 {
|
||||||
defer s.mutex.RUnlock()
|
atomic.StoreInt64(&c.spawnCount, spawnCount)
|
||||||
return atomic.LoadInt64(&s.spawnCount)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SpawnInfo) getSpawnRate() float64 {
|
func (c *Controller) setSpawnRate(spawnRate float64) {
|
||||||
s.mutex.RLock()
|
c.mutex.Lock()
|
||||||
defer s.mutex.RUnlock()
|
defer c.mutex.Unlock()
|
||||||
return s.spawnRate
|
if spawnRate > 0 {
|
||||||
|
c.spawnRate = spawnRate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SpawnInfo) getSpawnDone() chan struct{} {
|
func (c *Controller) getSpawnCount() int64 {
|
||||||
s.mutex.RLock()
|
c.mutex.RLock()
|
||||||
defer s.mutex.RUnlock()
|
defer c.mutex.RUnlock()
|
||||||
return s.spawnDone
|
return atomic.LoadInt64(&c.spawnCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SpawnInfo) done() {
|
func (c *Controller) getSpawnRate() float64 {
|
||||||
close(s.spawnDone)
|
c.mutex.RLock()
|
||||||
|
defer c.mutex.RUnlock()
|
||||||
|
return c.spawnRate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SpawnInfo) isFinished() bool {
|
func (c *Controller) getSpawnDone() chan struct{} {
|
||||||
|
c.mutex.RLock()
|
||||||
|
defer c.mutex.RUnlock()
|
||||||
|
return c.spawnDone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) getCurrentClientsNum() int64 {
|
||||||
|
c.mutex.RLock()
|
||||||
|
defer c.mutex.RUnlock()
|
||||||
|
return atomic.LoadInt64(&c.currentClientsNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) spawnCompete() {
|
||||||
|
close(c.spawnDone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) isFinished() bool {
|
||||||
// return true when workers acquired
|
// return true when workers acquired
|
||||||
return atomic.LoadInt64(&s.acquiredCount) == atomic.LoadInt64(&s.spawnCount)
|
return atomic.LoadInt64(&c.currentClientsNum) == atomic.LoadInt64(&c.spawnCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SpawnInfo) acquire() bool {
|
func (c *Controller) acquire() bool {
|
||||||
// get one ticket when there are still remaining spawn count to test
|
// get one ticket when there are still remaining spawn count to test
|
||||||
// return true when getting ticket successfully
|
// return true when getting ticket successfully
|
||||||
if atomic.LoadInt64(&s.acquiredCount) < atomic.LoadInt64(&s.spawnCount) {
|
if atomic.LoadInt64(&c.currentClientsNum) < atomic.LoadInt64(&c.spawnCount) {
|
||||||
atomic.AddInt64(&s.acquiredCount, 1)
|
atomic.AddInt64(&c.currentClientsNum, 1)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SpawnInfo) erase() bool {
|
func (c *Controller) erase() bool {
|
||||||
// return true if acquiredCount > spawnCount
|
// return true if acquiredCount > spawnCount
|
||||||
if atomic.LoadInt64(&s.acquiredCount) > atomic.LoadInt64(&s.spawnCount) {
|
if atomic.LoadInt64(&c.currentClientsNum) > atomic.LoadInt64(&c.spawnCount) {
|
||||||
atomic.AddInt64(&s.acquiredCount, -1)
|
atomic.AddInt64(&c.currentClientsNum, -1)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SpawnInfo) increaseFinishedCount() {
|
func (c *Controller) increaseFinishedCount() {
|
||||||
atomic.AddInt64(&s.acquiredCount, -1)
|
atomic.AddInt64(&c.currentClientsNum, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SpawnInfo) reset() {
|
func (c *Controller) reset() {
|
||||||
s.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
s.spawnCount = 0
|
c.spawnCount = 0
|
||||||
s.spawnRate = 0
|
c.spawnRate = 0
|
||||||
s.acquiredCount = 0
|
c.currentClientsNum = 0
|
||||||
s.spawnDone = make(chan struct{})
|
c.spawnDone = make(chan struct{})
|
||||||
|
c.tasks = []*Task{}
|
||||||
|
c.once = sync.Once{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type runner struct {
|
type runner struct {
|
||||||
@@ -146,9 +170,11 @@ type runner struct {
|
|||||||
rateLimitEnabled bool
|
rateLimitEnabled bool
|
||||||
stats *requestStats
|
stats *requestStats
|
||||||
|
|
||||||
currentClientsNum int32 // current clients count
|
spawnCount int64 // target clients to spawn
|
||||||
spawn *SpawnInfo
|
spawnRate float64
|
||||||
loop *Loop // specify loop count for testcase, count = loopCount * spawnCount
|
|
||||||
|
controller *Controller
|
||||||
|
loop *Loop // specify loop count for testcase, count = loopCount * spawnCount
|
||||||
|
|
||||||
// when this channel is closed, all statistics are reported successfully
|
// when this channel is closed, all statistics are reported successfully
|
||||||
reportedChan chan bool
|
reportedChan chan bool
|
||||||
@@ -168,6 +194,28 @@ type runner struct {
|
|||||||
once *sync.Once
|
once *sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *runner) setSpawnRate(spawnRate float64) {
|
||||||
|
r.mutex.Lock()
|
||||||
|
defer r.mutex.Unlock()
|
||||||
|
if spawnRate > 0 {
|
||||||
|
r.spawnRate = spawnRate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) getSpawnRate() float64 {
|
||||||
|
r.mutex.RLock()
|
||||||
|
defer r.mutex.RUnlock()
|
||||||
|
return r.spawnRate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) getSpawnCount() int64 {
|
||||||
|
return atomic.LoadInt64(&r.spawnCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) setSpawnCount(spawnCount int64) {
|
||||||
|
atomic.StoreInt64(&r.spawnCount, spawnCount)
|
||||||
|
}
|
||||||
|
|
||||||
// safeRun runs fn and recovers from unexpected panics.
|
// safeRun runs fn and recovers from unexpected panics.
|
||||||
// it prevents panics from Task.Fn crashing boomer.
|
// it prevents panics from Task.Fn crashing boomer.
|
||||||
func (r *runner) safeRun(fn func()) {
|
func (r *runner) safeRun(fn func()) {
|
||||||
@@ -239,7 +287,7 @@ func (r *runner) outputOnStop() {
|
|||||||
|
|
||||||
func (r *runner) reportStats() {
|
func (r *runner) reportStats() {
|
||||||
data := r.stats.collectReportData()
|
data := r.stats.collectReportData()
|
||||||
data["user_count"] = atomic.LoadInt32(&r.currentClientsNum)
|
data["user_count"] = r.controller.getCurrentClientsNum()
|
||||||
data["state"] = atomic.LoadInt32(&r.state)
|
data["state"] = atomic.LoadInt32(&r.state)
|
||||||
r.outputOnEvent(data)
|
r.outputOnEvent(data)
|
||||||
}
|
}
|
||||||
@@ -255,7 +303,7 @@ func (r *runner) reportTestResult() {
|
|||||||
currentTime := time.Now()
|
currentTime := time.Now()
|
||||||
println(fmt.Sprint("=========================================== Statistics Summary =========================================="))
|
println(fmt.Sprint("=========================================== Statistics Summary =========================================="))
|
||||||
println(fmt.Sprintf("Current time: %s, Users: %v, Duration: %v, Accumulated Transactions: %d Passed, %d Failed",
|
println(fmt.Sprintf("Current time: %s, Users: %v, Duration: %v, Accumulated Transactions: %d Passed, %d Failed",
|
||||||
currentTime.Format("2006/01/02 15:04:05"), atomic.LoadInt32(&r.currentClientsNum), duration, r.stats.transactionPassed, r.stats.transactionFailed))
|
currentTime.Format("2006/01/02 15:04:05"), r.controller.getCurrentClientsNum(), duration, r.stats.transactionPassed, r.stats.transactionFailed))
|
||||||
table := tablewriter.NewWriter(os.Stdout)
|
table := tablewriter.NewWriter(os.Stdout)
|
||||||
table.SetHeader([]string{"Name", "# requests", "# fails", "Median", "Average", "Min", "Max", "Content Size", "# reqs/sec", "# fails/sec"})
|
table.SetHeader([]string{"Name", "# requests", "# fails", "Median", "Average", "Min", "Max", "Content Size", "# reqs/sec", "# fails/sec"})
|
||||||
row := make([]string, 10)
|
row := make([]string, 10)
|
||||||
@@ -274,11 +322,13 @@ func (r *runner) reportTestResult() {
|
|||||||
println()
|
println()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) startSpawning(spawnCount int64, spawnRate float64, spawnCompleteFunc func()) {
|
func (r *runner) reset() {
|
||||||
r.spawn.reset()
|
r.updateState(StateInit)
|
||||||
atomic.StoreInt32(&r.currentClientsNum, 0)
|
r.controller.reset()
|
||||||
|
r.stats.clearAll()
|
||||||
go r.spawnWorkers(spawnCount, spawnRate, r.stopChan, spawnCompleteFunc)
|
r.rebalance = make(chan bool)
|
||||||
|
r.stopChan = make(chan bool)
|
||||||
|
r.reportedChan = make(chan bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) spawnWorkers(spawnCount int64, spawnRate float64, quit chan bool, spawnCompleteFunc func()) {
|
func (r *runner) spawnWorkers(spawnCount int64, spawnRate float64, quit chan bool, spawnCompleteFunc func()) {
|
||||||
@@ -287,7 +337,7 @@ func (r *runner) spawnWorkers(spawnCount int64, spawnRate float64, quit chan boo
|
|||||||
Float64("spawnRate", spawnRate).
|
Float64("spawnRate", spawnRate).
|
||||||
Msg("Spawning workers")
|
Msg("Spawning workers")
|
||||||
|
|
||||||
r.spawn.setSpawn(spawnCount, spawnRate)
|
r.controller.setSpawn(spawnCount, spawnRate)
|
||||||
|
|
||||||
r.updateState(StateSpawning)
|
r.updateState(StateSpawning)
|
||||||
for {
|
for {
|
||||||
@@ -297,23 +347,21 @@ func (r *runner) spawnWorkers(spawnCount int64, spawnRate float64, quit chan boo
|
|||||||
log.Info().Msg("Quitting spawning workers")
|
log.Info().Msg("Quitting spawning workers")
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
if r.isStarted() && r.spawn.acquire() {
|
if r.isStarted() && r.controller.acquire() {
|
||||||
// spawn workers with rate limit
|
// spawn workers with rate limit
|
||||||
sleepTime := time.Duration(1000000/r.spawn.getSpawnRate()) * time.Microsecond
|
sleepTime := time.Duration(1000000/r.controller.getSpawnRate()) * time.Microsecond
|
||||||
time.Sleep(sleepTime)
|
time.Sleep(sleepTime)
|
||||||
|
|
||||||
// loop count per worker
|
// loop count per worker
|
||||||
var workerLoop *Loop
|
var workerLoop *Loop
|
||||||
if r.loop != nil {
|
if r.loop != nil {
|
||||||
workerLoop = &Loop{loopCount: atomic.LoadInt64(&r.loop.loopCount) / r.spawn.spawnCount}
|
workerLoop = &Loop{loopCount: atomic.LoadInt64(&r.loop.loopCount) / r.controller.spawnCount}
|
||||||
}
|
}
|
||||||
atomic.AddInt32(&r.currentClientsNum, 1)
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-quit:
|
case <-quit:
|
||||||
atomic.AddInt64(&r.spawn.acquiredCount, -1)
|
atomic.AddInt64(&r.controller.currentClientsNum, -1)
|
||||||
atomic.AddInt32(&r.currentClientsNum, -1)
|
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
if workerLoop != nil && !workerLoop.acquire() {
|
if workerLoop != nil && !workerLoop.acquire() {
|
||||||
@@ -336,25 +384,31 @@ func (r *runner) spawnWorkers(spawnCount int64, spawnRate float64, quit chan boo
|
|||||||
workerLoop.increaseFinishedCount()
|
workerLoop.increaseFinishedCount()
|
||||||
if r.loop.isFinished() {
|
if r.loop.isFinished() {
|
||||||
r.stop()
|
r.stop()
|
||||||
|
close(r.rebalance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.spawn.erase() {
|
if r.controller.erase() {
|
||||||
atomic.AddInt32(&r.currentClientsNum, -1)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
} else if r.getState() == StateSpawning {
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
r.controller.once.Do(func() {
|
||||||
// spawning compete
|
// spawning compete
|
||||||
r.spawn.done()
|
r.controller.spawnCompete()
|
||||||
if spawnCompleteFunc != nil {
|
if spawnCompleteFunc != nil {
|
||||||
spawnCompleteFunc()
|
spawnCompleteFunc()
|
||||||
}
|
}
|
||||||
r.updateState(StateRunning)
|
r.updateState(StateRunning)
|
||||||
} else {
|
})
|
||||||
// continue if rebalance
|
|
||||||
<-r.rebalance
|
<-r.rebalance
|
||||||
|
if r.isStarted() {
|
||||||
|
// rebalance spawn count
|
||||||
|
r.controller.setSpawn(r.getSpawnCount(), r.getSpawnRate())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -425,6 +479,7 @@ func (r *runner) statsStart() {
|
|||||||
// close reportedChan and return if the last stats is reported successfully
|
// close reportedChan and return if the last stats is reported successfully
|
||||||
if !r.isStarted() {
|
if !r.isStarted() {
|
||||||
close(r.reportedChan)
|
close(r.reportedChan)
|
||||||
|
log.Info().Msg("Quitting statsStart")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -460,38 +515,28 @@ type localRunner struct {
|
|||||||
func newLocalRunner(spawnCount int, spawnRate float64) *localRunner {
|
func newLocalRunner(spawnCount int, spawnRate float64) *localRunner {
|
||||||
return &localRunner{
|
return &localRunner{
|
||||||
runner: runner{
|
runner: runner{
|
||||||
state: StateInit,
|
state: StateInit,
|
||||||
stats: newRequestStats(),
|
stats: newRequestStats(),
|
||||||
outputs: make([]Output, 0),
|
spawnCount: int64(spawnCount),
|
||||||
spawn: &SpawnInfo{
|
spawnRate: spawnRate,
|
||||||
spawnCount: int64(spawnCount),
|
controller: &Controller{},
|
||||||
spawnRate: spawnRate,
|
outputs: make([]Output, 0),
|
||||||
spawnDone: make(chan struct{}),
|
closeChan: make(chan bool),
|
||||||
},
|
once: &sync.Once{},
|
||||||
reportedChan: make(chan bool),
|
|
||||||
stopChan: make(chan bool),
|
|
||||||
closeChan: make(chan bool),
|
|
||||||
once: &sync.Once{},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *localRunner) start() {
|
func (r *localRunner) start() {
|
||||||
// init state
|
// init localRunner
|
||||||
r.updateState(StateInit)
|
r.reset()
|
||||||
atomic.StoreInt32(&r.currentClientsNum, 0)
|
|
||||||
r.stats.clearAll()
|
|
||||||
|
|
||||||
// start rate limiter
|
// start rate limiter
|
||||||
if r.rateLimitEnabled {
|
if r.rateLimitEnabled {
|
||||||
r.rateLimiter.Start()
|
r.rateLimiter.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
r.stopChan = make(chan bool)
|
r.spawnWorkers(r.getSpawnCount(), r.getSpawnRate(), r.stopChan, nil)
|
||||||
r.reportedChan = make(chan bool)
|
|
||||||
r.rebalance = make(chan bool)
|
|
||||||
|
|
||||||
go r.spawnWorkers(r.spawn.spawnCount, r.spawn.spawnRate, r.stopChan, nil)
|
|
||||||
|
|
||||||
// output setup
|
// output setup
|
||||||
r.outputOnStart()
|
r.outputOnStart()
|
||||||
@@ -548,12 +593,10 @@ type workerRunner struct {
|
|||||||
func newWorkerRunner(masterHost string, masterPort int) (r *workerRunner) {
|
func newWorkerRunner(masterHost string, masterPort int) (r *workerRunner) {
|
||||||
r = &workerRunner{
|
r = &workerRunner{
|
||||||
runner: runner{
|
runner: runner{
|
||||||
stats: newRequestStats(),
|
stats: newRequestStats(),
|
||||||
spawn: &SpawnInfo{
|
controller: &Controller{},
|
||||||
spawnDone: make(chan struct{}),
|
closeChan: make(chan bool),
|
||||||
},
|
once: &sync.Once{},
|
||||||
closeChan: make(chan bool),
|
|
||||||
once: &sync.Once{},
|
|
||||||
},
|
},
|
||||||
masterHost: masterHost,
|
masterHost: masterHost,
|
||||||
masterPort: masterPort,
|
masterPort: masterPort,
|
||||||
@@ -566,7 +609,7 @@ func newWorkerRunner(masterHost string, masterPort int) (r *workerRunner) {
|
|||||||
|
|
||||||
func (r *workerRunner) spawnComplete() {
|
func (r *workerRunner) spawnComplete() {
|
||||||
data := make(map[string]int64)
|
data := make(map[string]int64)
|
||||||
data["count"] = r.spawn.getSpawnCount()
|
data["count"] = r.controller.getSpawnCount()
|
||||||
r.client.sendChannel() <- newGenericMessage("spawning_complete", data, r.nodeID)
|
r.client.sendChannel() <- newGenericMessage("spawning_complete", data, r.nodeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,11 +617,11 @@ func (r *workerRunner) onSpawnMessage(msg *genericMessage) {
|
|||||||
r.client.sendChannel() <- newGenericMessage("spawning", nil, r.nodeID)
|
r.client.sendChannel() <- newGenericMessage("spawning", nil, r.nodeID)
|
||||||
spawnCount, ok := msg.Data["spawn_count"]
|
spawnCount, ok := msg.Data["spawn_count"]
|
||||||
if ok {
|
if ok {
|
||||||
r.spawn.setSpawn(spawnCount, -1)
|
r.setSpawnCount(spawnCount)
|
||||||
}
|
}
|
||||||
spawnRate, ok := msg.Data["spawn_rate"]
|
spawnRate, ok := msg.Data["spawn_rate"]
|
||||||
if ok {
|
if ok {
|
||||||
r.spawn.setSpawn(-1, float64(spawnRate))
|
r.setSpawnRate(float64(spawnRate))
|
||||||
}
|
}
|
||||||
if msg.Tasks != nil {
|
if msg.Tasks != nil {
|
||||||
r.testCaseBytes <- msg.Tasks
|
r.testCaseBytes <- msg.Tasks
|
||||||
@@ -586,6 +629,19 @@ func (r *workerRunner) onSpawnMessage(msg *genericMessage) {
|
|||||||
log.Info().Msg("on spawn message successful")
|
log.Info().Msg("on spawn message successful")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *workerRunner) onRebalanceMessage(msg *genericMessage) {
|
||||||
|
spawnCount, ok := msg.Data["spawn_count"]
|
||||||
|
if ok {
|
||||||
|
r.setSpawnCount(spawnCount)
|
||||||
|
}
|
||||||
|
spawnRate, ok := msg.Data["spawn_rate"]
|
||||||
|
if ok {
|
||||||
|
r.setSpawnRate(float64(spawnRate))
|
||||||
|
}
|
||||||
|
r.rebalance <- true
|
||||||
|
log.Info().Msg("on rebalance message successful")
|
||||||
|
}
|
||||||
|
|
||||||
// Runner acts as a state machine.
|
// Runner acts as a state machine.
|
||||||
func (r *workerRunner) onMessage(msg *genericMessage) {
|
func (r *workerRunner) onMessage(msg *genericMessage) {
|
||||||
switch r.getState() {
|
switch r.getState() {
|
||||||
@@ -602,7 +658,8 @@ func (r *workerRunner) onMessage(msg *genericMessage) {
|
|||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case "spawn":
|
case "spawn":
|
||||||
r.onSpawnMessage(msg)
|
r.onSpawnMessage(msg)
|
||||||
r.rebalance <- true
|
case "rebalance":
|
||||||
|
r.onRebalanceMessage(msg)
|
||||||
case "stop":
|
case "stop":
|
||||||
r.stop()
|
r.stop()
|
||||||
log.Info().Msg("Recv stop message from master, all the goroutines are stopped")
|
log.Info().Msg("Recv stop message from master, all the goroutines are stopped")
|
||||||
@@ -679,7 +736,7 @@ func (r *workerRunner) run() {
|
|||||||
data := map[string]int64{
|
data := map[string]int64{
|
||||||
"state": int64(r.getState()),
|
"state": int64(r.getState()),
|
||||||
"current_cpu_usage": int64(CPUUsage),
|
"current_cpu_usage": int64(CPUUsage),
|
||||||
"spawn_count": int64(atomic.LoadInt32(&r.currentClientsNum)),
|
"spawn_count": r.controller.getCurrentClientsNum(),
|
||||||
}
|
}
|
||||||
r.client.sendChannel() <- newGenericMessage("heartbeat", data, r.nodeID)
|
r.client.sendChannel() <- newGenericMessage("heartbeat", data, r.nodeID)
|
||||||
case <-r.closeChan:
|
case <-r.closeChan:
|
||||||
@@ -692,23 +749,19 @@ func (r *workerRunner) run() {
|
|||||||
|
|
||||||
// start load test
|
// start load test
|
||||||
func (r *workerRunner) start() {
|
func (r *workerRunner) start() {
|
||||||
r.stats.clearAll()
|
r.reset()
|
||||||
|
|
||||||
// start rate limiter
|
// start rate limiter
|
||||||
if r.rateLimitEnabled {
|
if r.rateLimitEnabled {
|
||||||
r.rateLimiter.Start()
|
r.rateLimiter.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
r.stopChan = make(chan bool)
|
|
||||||
r.reportedChan = make(chan bool)
|
|
||||||
r.rebalance = make(chan bool)
|
|
||||||
|
|
||||||
r.once.Do(r.outputOnStart)
|
r.once.Do(r.outputOnStart)
|
||||||
|
|
||||||
r.startSpawning(r.spawn.getSpawnCount(), r.spawn.getSpawnRate(), r.spawnComplete)
|
r.spawnWorkers(r.getSpawnCount(), r.getSpawnRate(), r.stopChan, r.spawnComplete)
|
||||||
|
|
||||||
// start stats report
|
// start stats report
|
||||||
go r.runner.statsStart()
|
go r.statsStart()
|
||||||
|
|
||||||
<-r.reportedChan
|
<-r.reportedChan
|
||||||
|
|
||||||
@@ -718,12 +771,8 @@ func (r *workerRunner) start() {
|
|||||||
|
|
||||||
func (r *workerRunner) stop() {
|
func (r *workerRunner) stop() {
|
||||||
if r.isStarted() {
|
if r.isStarted() {
|
||||||
close(r.stopChan)
|
r.runner.stop()
|
||||||
close(r.rebalance)
|
close(r.rebalance)
|
||||||
// stop rate limiter
|
|
||||||
if r.rateLimitEnabled {
|
|
||||||
r.rateLimiter.Stop()
|
|
||||||
}
|
|
||||||
r.updateState(StateStopped)
|
r.updateState(StateStopped)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -763,24 +812,19 @@ type masterRunner struct {
|
|||||||
expectWorkersMaxWait int
|
expectWorkersMaxWait int
|
||||||
|
|
||||||
parseTestCasesChan chan bool
|
parseTestCasesChan chan bool
|
||||||
startFlag bool
|
|
||||||
testCaseBytes chan []byte
|
testCaseBytes chan []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMasterRunner(masterBindHost string, masterBindPort int) *masterRunner {
|
func newMasterRunner(masterBindHost string, masterBindPort int) *masterRunner {
|
||||||
return &masterRunner{
|
return &masterRunner{
|
||||||
runner: runner{
|
runner: runner{
|
||||||
state: StateInit,
|
state: StateInit,
|
||||||
spawn: &SpawnInfo{
|
|
||||||
spawnDone: make(chan struct{}),
|
|
||||||
},
|
|
||||||
closeChan: make(chan bool),
|
closeChan: make(chan bool),
|
||||||
},
|
},
|
||||||
masterBindHost: masterBindHost,
|
masterBindHost: masterBindHost,
|
||||||
masterBindPort: masterBindPort,
|
masterBindPort: masterBindPort,
|
||||||
server: newServer(masterBindHost, masterBindPort),
|
server: newServer(masterBindHost, masterBindPort),
|
||||||
parseTestCasesChan: make(chan bool),
|
parseTestCasesChan: make(chan bool),
|
||||||
startFlag: false,
|
|
||||||
testCaseBytes: make(chan []byte),
|
testCaseBytes: make(chan []byte),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -946,15 +990,15 @@ func (r *masterRunner) start() error {
|
|||||||
if numWorkers == 0 {
|
if numWorkers == 0 {
|
||||||
return errors.New("current workers: 0")
|
return errors.New("current workers: 0")
|
||||||
}
|
}
|
||||||
workerSpawnRate := r.spawn.spawnRate / float64(numWorkers)
|
workerSpawnRate := r.getSpawnRate() / float64(numWorkers)
|
||||||
workerSpawnCount := r.spawn.getSpawnCount() / int64(numWorkers)
|
workerSpawnCount := r.getSpawnCount() / int64(numWorkers)
|
||||||
|
|
||||||
log.Info().Msg("send spawn data to worker")
|
log.Info().Msg("send spawn data to worker")
|
||||||
r.updateState(StateSpawning)
|
r.updateState(StateSpawning)
|
||||||
// waitting to fetch testcase
|
// waitting to fetch testcase
|
||||||
testcase, ok := r.fetchTestCase()
|
testcase, err := r.fetchTestCase()
|
||||||
if !ok {
|
if err != nil {
|
||||||
return errors.New("starting, do not retry frequently")
|
return err
|
||||||
}
|
}
|
||||||
r.server.sendChannel() <- newSpawnMessageToWorker("spawn", map[string]int64{
|
r.server.sendChannel() <- newSpawnMessageToWorker("spawn", map[string]int64{
|
||||||
"spawn_count": workerSpawnCount,
|
"spawn_count": workerSpawnCount,
|
||||||
@@ -965,27 +1009,44 @@ func (r *masterRunner) start() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *masterRunner) fetchTestCase() ([]byte, bool) {
|
|
||||||
if r.startFlag {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
r.startFlag = true
|
|
||||||
defer func() {
|
|
||||||
r.startFlag = false
|
|
||||||
}()
|
|
||||||
r.parseTestCasesChan <- true
|
|
||||||
return <-r.testCaseBytes, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *masterRunner) rebalance() error {
|
func (r *masterRunner) rebalance() error {
|
||||||
return r.start()
|
numWorkers := r.server.getClientsLength()
|
||||||
|
if numWorkers == 0 {
|
||||||
|
return errors.New("current workers: 0")
|
||||||
|
}
|
||||||
|
workerSpawnRate := r.getSpawnRate() / float64(numWorkers)
|
||||||
|
workerSpawnCount := r.getSpawnCount() / int64(numWorkers)
|
||||||
|
|
||||||
|
r.server.sendChannel() <- newSpawnMessageToWorker("rebalance", map[string]int64{
|
||||||
|
"spawn_count": workerSpawnCount,
|
||||||
|
"spawn_rate": int64(workerSpawnRate),
|
||||||
|
}, nil)
|
||||||
|
println("send rebalance data to worker successful")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *masterRunner) stop() {
|
func (r *masterRunner) fetchTestCase() ([]byte, error) {
|
||||||
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
|
if len(r.testCaseBytes) > 0 {
|
||||||
|
<-r.testCaseBytes
|
||||||
|
}
|
||||||
|
r.parseTestCasesChan <- true
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
return nil, errors.New("parse testcases timeout")
|
||||||
|
case tcb := <-r.testCaseBytes:
|
||||||
|
return tcb, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *masterRunner) stop() error {
|
||||||
if r.isStarted() {
|
if r.isStarted() {
|
||||||
r.updateState(StateStopping)
|
r.updateState(StateStopping)
|
||||||
r.server.sendChannel() <- &genericMessage{Type: "stop", Data: map[string]int64{}}
|
r.server.sendChannel() <- &genericMessage{Type: "stop", Data: map[string]int64{}}
|
||||||
r.updateState(StateStopped)
|
r.updateState(StateStopped)
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return errors.New("already stopped")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,13 +127,12 @@ func TestSpawnWorkers(t *testing.T) {
|
|||||||
defer runner.close()
|
defer runner.close()
|
||||||
|
|
||||||
runner.client = newClient("localhost", 5557, runner.nodeID)
|
runner.client = newClient("localhost", 5557, runner.nodeID)
|
||||||
|
runner.reset()
|
||||||
runner.setTasks(tasks)
|
runner.setTasks(tasks)
|
||||||
runner.stopChan = make(chan bool)
|
|
||||||
runner.rebalance = make(chan bool)
|
|
||||||
go runner.spawnWorkers(10, 10, runner.stopChan, runner.spawnComplete)
|
go runner.spawnWorkers(10, 10, runner.stopChan, runner.spawnComplete)
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
currentClients := atomic.LoadInt32(&runner.currentClientsNum)
|
currentClients := runner.controller.getCurrentClientsNum()
|
||||||
if currentClients != 10 {
|
if currentClients != 10 {
|
||||||
t.Error("Unexpected count", currentClients)
|
t.Error("Unexpected count", currentClients)
|
||||||
}
|
}
|
||||||
@@ -163,17 +162,16 @@ func TestSpawnWorkersWithManyTasks(t *testing.T) {
|
|||||||
runner := newWorkerRunner("localhost", 5557)
|
runner := newWorkerRunner("localhost", 5557)
|
||||||
defer runner.close()
|
defer runner.close()
|
||||||
|
|
||||||
|
runner.reset()
|
||||||
runner.setTasks(tasks)
|
runner.setTasks(tasks)
|
||||||
runner.client = newClient("localhost", 5557, runner.nodeID)
|
runner.client = newClient("localhost", 5557, runner.nodeID)
|
||||||
|
|
||||||
const numToSpawn int64 = 30
|
const numToSpawn int64 = 30
|
||||||
runner.stopChan = make(chan bool)
|
|
||||||
runner.rebalance = make(chan bool)
|
|
||||||
|
|
||||||
go runner.spawnWorkers(numToSpawn, float64(numToSpawn), runner.stopChan, runner.spawnComplete)
|
go runner.spawnWorkers(numToSpawn, float64(numToSpawn), runner.stopChan, runner.spawnComplete)
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
currentClients := atomic.LoadInt32(&runner.currentClientsNum)
|
currentClients := runner.controller.getCurrentClientsNum()
|
||||||
|
|
||||||
assert.Equal(t, numToSpawn, int64(currentClients))
|
assert.Equal(t, numToSpawn, int64(currentClients))
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
@@ -226,15 +224,15 @@ func TestSpawnAndStop(t *testing.T) {
|
|||||||
runner.client = newClient("localhost", 5557, runner.nodeID)
|
runner.client = newClient("localhost", 5557, runner.nodeID)
|
||||||
|
|
||||||
runner.setTasks(tasks)
|
runner.setTasks(tasks)
|
||||||
runner.spawn.setSpawn(10, 10)
|
runner.setSpawnCount(10)
|
||||||
runner.updateState(StateSpawning)
|
runner.setSpawnRate(10)
|
||||||
|
|
||||||
go runner.start()
|
go runner.start()
|
||||||
|
|
||||||
// wait for spawning goroutines
|
// wait for spawning goroutines
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
if atomic.LoadInt32(&runner.currentClientsNum) != 10 {
|
if runner.controller.getCurrentClientsNum() != 10 {
|
||||||
t.Error("Number of goroutines mismatches, expected: 10, current count", atomic.LoadInt32(&runner.currentClientsNum))
|
t.Error("Number of goroutines mismatches, expected: 10, current count", runner.controller.getCurrentClientsNum())
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := <-runner.client.sendChannel()
|
msg := <-runner.client.sendChannel()
|
||||||
@@ -258,10 +256,8 @@ func TestStop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
tasks := []*Task{taskA}
|
tasks := []*Task{taskA}
|
||||||
runner := newWorkerRunner("localhost", 5557)
|
runner := newWorkerRunner("localhost", 5557)
|
||||||
runner.stopChan = make(chan bool)
|
|
||||||
runner.rebalance = make(chan bool)
|
|
||||||
runner.setTasks(tasks)
|
runner.setTasks(tasks)
|
||||||
runner.spawn.setSpawn(10, 10)
|
runner.reset()
|
||||||
runner.updateState(StateSpawning)
|
runner.updateState(StateSpawning)
|
||||||
|
|
||||||
runner.stop()
|
runner.stop()
|
||||||
@@ -281,20 +277,21 @@ func TestOnSpawnMessage(t *testing.T) {
|
|||||||
defer runner.close()
|
defer runner.close()
|
||||||
runner.client = newClient("localhost", 5557, runner.nodeID)
|
runner.client = newClient("localhost", 5557, runner.nodeID)
|
||||||
runner.updateState(StateInit)
|
runner.updateState(StateInit)
|
||||||
|
runner.reset()
|
||||||
runner.setTasks([]*Task{taskA})
|
runner.setTasks([]*Task{taskA})
|
||||||
runner.spawn.spawnCount = 100
|
runner.setSpawnCount(100)
|
||||||
runner.spawn.spawnRate = 100
|
runner.setSpawnRate(100)
|
||||||
|
|
||||||
runner.onSpawnMessage(newGenericMessage("spawn", map[string]int64{
|
runner.onSpawnMessage(newGenericMessage("spawn", map[string]int64{
|
||||||
"spawn_count": 20,
|
"spawn_count": 20,
|
||||||
"spawn_rate": 20,
|
"spawn_rate": 20,
|
||||||
}, runner.nodeID))
|
}, runner.nodeID))
|
||||||
|
|
||||||
if runner.spawn.spawnCount != 20 {
|
if runner.getSpawnCount() != 20 {
|
||||||
t.Error("workers should be overwrote by onSpawnMessage, expected: 20, was:", runner.spawn.spawnCount)
|
t.Error("workers should be overwrote by onSpawnMessage, expected: 20, was:", runner.controller.spawnCount)
|
||||||
}
|
}
|
||||||
if runner.spawn.spawnRate != 20 {
|
if runner.getSpawnRate() != 20 {
|
||||||
t.Error("spawnRate should be overwrote by onSpawnMessage, expected: 20, was:", runner.spawn.spawnRate)
|
t.Error("spawnRate should be overwrote by onSpawnMessage, expected: 20, was:", runner.controller.spawnRate)
|
||||||
}
|
}
|
||||||
|
|
||||||
runner.onMessage(newGenericMessage("stop", nil, runner.nodeID))
|
runner.onMessage(newGenericMessage("stop", nil, runner.nodeID))
|
||||||
@@ -309,9 +306,8 @@ func TestOnQuitMessage(t *testing.T) {
|
|||||||
<-runner.closeChan
|
<-runner.closeChan
|
||||||
|
|
||||||
runner.updateState(StateRunning)
|
runner.updateState(StateRunning)
|
||||||
|
runner.reset()
|
||||||
runner.closeChan = make(chan bool)
|
runner.closeChan = make(chan bool)
|
||||||
runner.stopChan = make(chan bool)
|
|
||||||
runner.rebalance = make(chan bool)
|
|
||||||
runner.client.shutdownChan = make(chan bool)
|
runner.client.shutdownChan = make(chan bool)
|
||||||
runner.onMessage(newGenericMessage("quit", nil, runner.nodeID))
|
runner.onMessage(newGenericMessage("quit", nil, runner.nodeID))
|
||||||
<-runner.closeChan
|
<-runner.closeChan
|
||||||
@@ -321,7 +317,7 @@ func TestOnQuitMessage(t *testing.T) {
|
|||||||
|
|
||||||
runner.updateState(StateStopped)
|
runner.updateState(StateStopped)
|
||||||
runner.closeChan = make(chan bool)
|
runner.closeChan = make(chan bool)
|
||||||
runner.stopChan = make(chan bool)
|
runner.reset()
|
||||||
runner.client.shutdownChan = make(chan bool)
|
runner.client.shutdownChan = make(chan bool)
|
||||||
runner.onMessage(newGenericMessage("quit", nil, runner.nodeID))
|
runner.onMessage(newGenericMessage("quit", nil, runner.nodeID))
|
||||||
<-runner.closeChan
|
<-runner.closeChan
|
||||||
@@ -344,7 +340,6 @@ func TestOnMessage(t *testing.T) {
|
|||||||
tasks := []*Task{taskA, taskB}
|
tasks := []*Task{taskA, taskB}
|
||||||
|
|
||||||
runner := newWorkerRunner("localhost", 5557)
|
runner := newWorkerRunner("localhost", 5557)
|
||||||
defer runner.close()
|
|
||||||
runner.client = newClient("localhost", 5557, runner.nodeID)
|
runner.client = newClient("localhost", 5557, runner.nodeID)
|
||||||
runner.updateState(StateInit)
|
runner.updateState(StateInit)
|
||||||
runner.setTasks(tasks)
|
runner.setTasks(tasks)
|
||||||
@@ -364,8 +359,8 @@ func TestOnMessage(t *testing.T) {
|
|||||||
|
|
||||||
// spawn complete and running
|
// spawn complete and running
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
if atomic.LoadInt32(&runner.currentClientsNum) != 10 {
|
if runner.controller.getCurrentClientsNum() != 10 {
|
||||||
t.Error("Number of goroutines mismatches, expected: 10, current count:", atomic.LoadInt32(&runner.currentClientsNum))
|
t.Error("Number of goroutines mismatches, expected: 10, current count:", runner.controller.getCurrentClientsNum())
|
||||||
}
|
}
|
||||||
msg = <-runner.client.sendChannel()
|
msg = <-runner.client.sendChannel()
|
||||||
if msg.Type != "spawning_complete" {
|
if msg.Type != "spawning_complete" {
|
||||||
@@ -376,22 +371,17 @@ func TestOnMessage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// increase goroutines while running
|
// increase goroutines while running
|
||||||
runner.onMessage(newGenericMessage("spawn", map[string]int64{
|
runner.onMessage(newGenericMessage("rebalance", map[string]int64{
|
||||||
"spawn_count": 15,
|
"spawn_count": 15,
|
||||||
"spawn_rate": 15,
|
"spawn_rate": 15,
|
||||||
}, runner.nodeID))
|
}, runner.nodeID))
|
||||||
|
|
||||||
msg = <-runner.client.sendChannel()
|
|
||||||
if msg.Type != "spawning" {
|
|
||||||
t.Error("Runner should send spawning message when starting spawn, got", msg.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
if runner.getState() != StateRunning {
|
if runner.getState() != StateRunning {
|
||||||
t.Error("State of runner is not running after spawn, got", runner.getState())
|
t.Error("State of runner is not running after spawn, got", runner.getState())
|
||||||
}
|
}
|
||||||
if atomic.LoadInt32(&runner.currentClientsNum) != 15 {
|
if runner.controller.getCurrentClientsNum() != 15 {
|
||||||
t.Error("Number of goroutines mismatches, expected: 20, current count:", atomic.LoadInt32(&runner.currentClientsNum))
|
t.Error("Number of goroutines mismatches, expected: 15, current count:", runner.controller.getCurrentClientsNum())
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop all the workers
|
// stop all the workers
|
||||||
@@ -404,7 +394,7 @@ func TestOnMessage(t *testing.T) {
|
|||||||
t.Error("Runner should send client_stopped message, got", msg.Type)
|
t.Error("Runner should send client_stopped message, got", msg.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(4 * time.Second)
|
||||||
|
|
||||||
go runner.start()
|
go runner.start()
|
||||||
// spawn again
|
// spawn again
|
||||||
@@ -420,8 +410,8 @@ func TestOnMessage(t *testing.T) {
|
|||||||
|
|
||||||
// spawn complete and running
|
// spawn complete and running
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
if atomic.LoadInt32(&runner.currentClientsNum) != 10 {
|
if runner.controller.getCurrentClientsNum() != 10 {
|
||||||
t.Error("Number of goroutines mismatches, expected: 10, current count:", atomic.LoadInt32(&runner.currentClientsNum))
|
t.Error("Number of goroutines mismatches, expected: 10, current count:", runner.controller.getCurrentClientsNum())
|
||||||
}
|
}
|
||||||
if runner.getState() != StateRunning {
|
if runner.getState() != StateRunning {
|
||||||
t.Error("State of runner is not running after spawn, got", runner.getState())
|
t.Error("State of runner is not running after spawn, got", runner.getState())
|
||||||
@@ -440,13 +430,17 @@ func TestOnMessage(t *testing.T) {
|
|||||||
if msg.Type != "client_stopped" {
|
if msg.Type != "client_stopped" {
|
||||||
t.Error("Runner should send client_stopped message, got", msg.Type)
|
t.Error("Runner should send client_stopped message, got", msg.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// quit
|
||||||
|
runner.onMessage(newGenericMessage("quit", nil, runner.nodeID))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientListener(t *testing.T) {
|
func TestClientListener(t *testing.T) {
|
||||||
runner := newMasterRunner("localhost", 5557)
|
runner := newMasterRunner("localhost", 5557)
|
||||||
defer runner.close()
|
defer runner.close()
|
||||||
runner.updateState(StateInit)
|
runner.updateState(StateInit)
|
||||||
runner.spawn.setSpawn(10, 10)
|
runner.setSpawnCount(10)
|
||||||
|
runner.setSpawnRate(10)
|
||||||
go runner.clientListener()
|
go runner.clientListener()
|
||||||
runner.server.clients.Store("testID1", &WorkerNode{ID: "testID1", Heartbeat: 3})
|
runner.server.clients.Store("testID1", &WorkerNode{ID: "testID1", Heartbeat: 3})
|
||||||
runner.server.clients.Store("testID2", &WorkerNode{ID: "testID2", Heartbeat: 3})
|
runner.server.clients.Store("testID2", &WorkerNode{ID: "testID2", Heartbeat: 3})
|
||||||
@@ -496,7 +490,8 @@ func TestHeartbeatWorker(t *testing.T) {
|
|||||||
runner := newMasterRunner("localhost", 5557)
|
runner := newMasterRunner("localhost", 5557)
|
||||||
defer runner.close()
|
defer runner.close()
|
||||||
runner.updateState(StateInit)
|
runner.updateState(StateInit)
|
||||||
runner.spawn.setSpawn(10, 10)
|
runner.setSpawnCount(10)
|
||||||
|
runner.setSpawnRate(10)
|
||||||
runner.server.clients.Store("testID1", &WorkerNode{ID: "testID1", Heartbeat: 1, State: StateInit})
|
runner.server.clients.Store("testID1", &WorkerNode{ID: "testID1", Heartbeat: 1, State: StateInit})
|
||||||
runner.server.clients.Store("testID2", &WorkerNode{ID: "testID2", Heartbeat: 1, State: StateInit})
|
runner.server.clients.Store("testID2", &WorkerNode{ID: "testID2", Heartbeat: 1, State: StateInit})
|
||||||
go runner.clientListener()
|
go runner.clientListener()
|
||||||
|
|||||||
@@ -198,9 +198,16 @@ func (api *apiHandler) Stop(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
api.boomer.Stop()
|
var resp *CommonResponseBody
|
||||||
resp := &CommonResponseBody{
|
err := api.boomer.Stop()
|
||||||
ServerStatus: EnumAPIResponseSuccess,
|
if err != nil {
|
||||||
|
resp = &CommonResponseBody{
|
||||||
|
ServerStatus: EnumAPIResponseStopError(err.Error()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resp = &CommonResponseBody{
|
||||||
|
ServerStatus: EnumAPIResponseSuccess,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(resp)
|
body, _ := json.Marshal(resp)
|
||||||
writeJSON(w, body, http.StatusOK)
|
writeJSON(w, body, http.StatusOK)
|
||||||
|
|||||||
Reference in New Issue
Block a user