mirror of
https://github.com/httprunner/httprunner.git
synced 2026-07-09 06:23:52 +08:00
refactor: simplify boomer runner
This commit is contained in:
@@ -22,4 +22,4 @@ Copyright 2021 debugtalk
|
|||||||
* [hrp har2case](hrp_har2case.md) - Convert HAR to json/yaml testcase files
|
* [hrp har2case](hrp_har2case.md) - Convert HAR to json/yaml testcase files
|
||||||
* [hrp run](hrp_run.md) - run API test
|
* [hrp run](hrp_run.md) - run API test
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 23-Dec-2021
|
###### Auto generated by spf13/cobra on 24-Dec-2021
|
||||||
|
|||||||
@@ -38,4 +38,4 @@ hrp boom [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 23-Dec-2021
|
###### Auto generated by spf13/cobra on 24-Dec-2021
|
||||||
|
|||||||
@@ -23,4 +23,4 @@ hrp har2case harPath... [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 23-Dec-2021
|
###### Auto generated by spf13/cobra on 24-Dec-2021
|
||||||
|
|||||||
@@ -31,4 +31,4 @@ hrp run path... [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 23-Dec-2021
|
###### Auto generated by spf13/cobra on 24-Dec-2021
|
||||||
|
|||||||
@@ -9,26 +9,19 @@ import (
|
|||||||
|
|
||||||
// A Boomer is used to run tasks.
|
// A Boomer is used to run tasks.
|
||||||
type Boomer struct {
|
type Boomer struct {
|
||||||
rateLimiter RateLimiter
|
|
||||||
|
|
||||||
localRunner *localRunner
|
localRunner *localRunner
|
||||||
spawnCount int
|
|
||||||
spawnRate float64
|
|
||||||
|
|
||||||
cpuProfile string
|
cpuProfile string
|
||||||
cpuProfileDuration time.Duration
|
cpuProfileDuration time.Duration
|
||||||
|
|
||||||
memoryProfile string
|
memoryProfile string
|
||||||
memoryProfileDuration time.Duration
|
memoryProfileDuration time.Duration
|
||||||
|
|
||||||
outputs []Output
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStandaloneBoomer returns a new Boomer, which can run without master.
|
// NewStandaloneBoomer returns a new Boomer, which can run without master.
|
||||||
func NewStandaloneBoomer(spawnCount int, spawnRate float64) *Boomer {
|
func NewStandaloneBoomer(spawnCount int, spawnRate float64) *Boomer {
|
||||||
return &Boomer{
|
return &Boomer{
|
||||||
spawnCount: spawnCount,
|
localRunner: newLocalRunner(spawnCount, spawnRate),
|
||||||
spawnRate: spawnRate,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,12 +45,16 @@ func (b *Boomer) SetRateLimiter(maxRPS int64, requestIncreaseRate string) {
|
|||||||
log.Error().Err(err).Msg("failed to create rate limiter")
|
log.Error().Err(err).Msg("failed to create rate limiter")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b.rateLimiter = rateLimiter
|
|
||||||
|
if rateLimiter != nil {
|
||||||
|
b.localRunner.rateLimitEnabled = true
|
||||||
|
b.localRunner.rateLimiter = rateLimiter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddOutput accepts outputs which implements the boomer.Output interface.
|
// AddOutput accepts outputs which implements the boomer.Output interface.
|
||||||
func (b *Boomer) AddOutput(o Output) {
|
func (b *Boomer) AddOutput(o Output) {
|
||||||
b.outputs = append(b.outputs, o)
|
b.localRunner.addOutput(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnableCPUProfile will start cpu profiling after run.
|
// EnableCPUProfile will start cpu profiling after run.
|
||||||
@@ -87,19 +84,12 @@ func (b *Boomer) Run(tasks ...*Task) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
b.localRunner = newLocalRunner(tasks, b.rateLimiter, b.spawnCount, b.spawnRate)
|
b.localRunner.setTasks(tasks)
|
||||||
for _, o := range b.outputs {
|
|
||||||
b.localRunner.addOutput(o)
|
|
||||||
}
|
|
||||||
b.localRunner.start()
|
b.localRunner.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordTransaction reports a transaction stat.
|
// RecordTransaction reports a transaction stat.
|
||||||
func (b *Boomer) RecordTransaction(name string, success bool, elapsedTime int64, contentSize int64) {
|
func (b *Boomer) RecordTransaction(name string, success bool, elapsedTime int64, contentSize int64) {
|
||||||
if b.localRunner == nil {
|
|
||||||
log.Warn().Msg("boomer not initialized")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.localRunner.stats.transactionChan <- &transaction{
|
b.localRunner.stats.transactionChan <- &transaction{
|
||||||
name: name,
|
name: name,
|
||||||
success: success,
|
success: success,
|
||||||
@@ -110,10 +100,6 @@ func (b *Boomer) RecordTransaction(name string, success bool, elapsedTime int64,
|
|||||||
|
|
||||||
// RecordSuccess reports a success.
|
// RecordSuccess reports a success.
|
||||||
func (b *Boomer) RecordSuccess(requestType, name string, responseTime int64, responseLength int64) {
|
func (b *Boomer) RecordSuccess(requestType, name string, responseTime int64, responseLength int64) {
|
||||||
if b.localRunner == nil {
|
|
||||||
log.Warn().Msg("boomer not initialized")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.localRunner.stats.requestSuccessChan <- &requestSuccess{
|
b.localRunner.stats.requestSuccessChan <- &requestSuccess{
|
||||||
requestType: requestType,
|
requestType: requestType,
|
||||||
name: name,
|
name: name,
|
||||||
@@ -124,10 +110,6 @@ func (b *Boomer) RecordSuccess(requestType, name string, responseTime int64, res
|
|||||||
|
|
||||||
// RecordFailure reports a failure.
|
// RecordFailure reports a failure.
|
||||||
func (b *Boomer) RecordFailure(requestType, name string, responseTime int64, exception string) {
|
func (b *Boomer) RecordFailure(requestType, name string, responseTime int64, exception string) {
|
||||||
if b.localRunner == nil {
|
|
||||||
log.Warn().Msg("boomer not initialized")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.localRunner.stats.requestFailureChan <- &requestFailure{
|
b.localRunner.stats.requestFailureChan <- &requestFailure{
|
||||||
requestType: requestType,
|
requestType: requestType,
|
||||||
name: name,
|
name: name,
|
||||||
@@ -138,9 +120,5 @@ func (b *Boomer) RecordFailure(requestType, name string, responseTime int64, exc
|
|||||||
|
|
||||||
// Quit will send a quit message to the master.
|
// Quit will send a quit message to the master.
|
||||||
func (b *Boomer) Quit() {
|
func (b *Boomer) Quit() {
|
||||||
if b.localRunner == nil {
|
|
||||||
log.Warn().Msg("boomer not initialized")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.localRunner.stop()
|
b.localRunner.stop()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.spawnCount != 100 {
|
if b.localRunner.spawnCount != 100 {
|
||||||
t.Error("spawnCount should be 100")
|
t.Error("spawnCount should be 100")
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.spawnRate != 10 {
|
if b.localRunner.spawnRate != 10 {
|
||||||
t.Error("spawnRate should be 10")
|
t.Error("spawnRate should be 10")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ func TestSetRateLimiter(t *testing.T) {
|
|||||||
b := NewStandaloneBoomer(100, 10)
|
b := NewStandaloneBoomer(100, 10)
|
||||||
b.SetRateLimiter(10, "10/1s")
|
b.SetRateLimiter(10, "10/1s")
|
||||||
|
|
||||||
if b.rateLimiter == nil {
|
if b.localRunner.rateLimiter == nil {
|
||||||
t.Error("b.rateLimiter should not be nil")
|
t.Error("b.rateLimiter should not be nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ func TestAddOutput(t *testing.T) {
|
|||||||
b.AddOutput(NewConsoleOutput())
|
b.AddOutput(NewConsoleOutput())
|
||||||
b.AddOutput(NewConsoleOutput())
|
b.AddOutput(NewConsoleOutput())
|
||||||
|
|
||||||
if len(b.outputs) != 2 {
|
if len(b.localRunner.outputs) != 2 {
|
||||||
t.Error("length of outputs should be 2")
|
t.Error("length of outputs should be 2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ func TestCreateRatelimiter(t *testing.T) {
|
|||||||
b := NewStandaloneBoomer(10, 10)
|
b := NewStandaloneBoomer(10, 10)
|
||||||
b.SetRateLimiter(100, "-1")
|
b.SetRateLimiter(100, "-1")
|
||||||
|
|
||||||
if stableRateLimiter, ok := b.rateLimiter.(*StableRateLimiter); !ok {
|
if stableRateLimiter, ok := b.localRunner.rateLimiter.(*StableRateLimiter); !ok {
|
||||||
t.Error("Expected stableRateLimiter")
|
t.Error("Expected stableRateLimiter")
|
||||||
} else {
|
} else {
|
||||||
if stableRateLimiter.threshold != 100 {
|
if stableRateLimiter.threshold != 100 {
|
||||||
@@ -115,7 +115,7 @@ func TestCreateRatelimiter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.SetRateLimiter(0, "1")
|
b.SetRateLimiter(0, "1")
|
||||||
if rampUpRateLimiter, ok := b.rateLimiter.(*RampUpRateLimiter); !ok {
|
if rampUpRateLimiter, ok := b.localRunner.rateLimiter.(*RampUpRateLimiter); !ok {
|
||||||
t.Error("Expected rampUpRateLimiter")
|
t.Error("Expected rampUpRateLimiter")
|
||||||
} else {
|
} else {
|
||||||
if rampUpRateLimiter.maxThreshold != math.MaxInt64 {
|
if rampUpRateLimiter.maxThreshold != math.MaxInt64 {
|
||||||
@@ -127,7 +127,7 @@ func TestCreateRatelimiter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.SetRateLimiter(10, "2/2s")
|
b.SetRateLimiter(10, "2/2s")
|
||||||
if rampUpRateLimiter, ok := b.rateLimiter.(*RampUpRateLimiter); !ok {
|
if rampUpRateLimiter, ok := b.localRunner.rateLimiter.(*RampUpRateLimiter); !ok {
|
||||||
t.Error("Expected rampUpRateLimiter")
|
t.Error("Expected rampUpRateLimiter")
|
||||||
} else {
|
} else {
|
||||||
if rampUpRateLimiter.maxThreshold != 10 {
|
if rampUpRateLimiter.maxThreshold != 10 {
|
||||||
|
|||||||
@@ -201,20 +201,17 @@ type localRunner struct {
|
|||||||
stopChan chan bool
|
stopChan chan bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLocalRunner(tasks []*Task, rateLimiter RateLimiter, spawnCount int, spawnRate float64) (r *localRunner) {
|
func newLocalRunner(spawnCount int, spawnRate float64) *localRunner {
|
||||||
r = &localRunner{}
|
return &localRunner{
|
||||||
r.setTasks(tasks)
|
runner: runner{
|
||||||
r.spawnRate = spawnRate
|
state: stateInit,
|
||||||
r.spawnCount = spawnCount
|
spawnRate: spawnRate,
|
||||||
r.stopChan = make(chan bool)
|
spawnCount: spawnCount,
|
||||||
|
stats: newRequestStats(),
|
||||||
if rateLimiter != nil {
|
outputs: make([]Output, 0),
|
||||||
r.rateLimitEnabled = true
|
},
|
||||||
r.rateLimiter = rateLimiter
|
stopChan: make(chan bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
r.stats = newRequestStats()
|
|
||||||
return r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *localRunner) start() {
|
func (r *localRunner) start() {
|
||||||
@@ -240,6 +237,7 @@ func (r *localRunner) start() {
|
|||||||
var ticker = time.NewTicker(reportStatsInterval)
|
var ticker = time.NewTicker(reportStatsInterval)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
// record stats
|
||||||
case t := <-r.stats.transactionChan:
|
case t := <-r.stats.transactionChan:
|
||||||
r.stats.logTransaction(t.name, t.success, t.elapsedTime, t.contentSize)
|
r.stats.logTransaction(t.name, t.success, t.elapsedTime, t.contentSize)
|
||||||
case m := <-r.stats.requestSuccessChan:
|
case m := <-r.stats.requestSuccessChan:
|
||||||
@@ -247,28 +245,34 @@ func (r *localRunner) start() {
|
|||||||
case n := <-r.stats.requestFailureChan:
|
case n := <-r.stats.requestFailureChan:
|
||||||
r.stats.logRequest(n.requestType, n.name, n.responseTime, 0)
|
r.stats.logRequest(n.requestType, n.name, n.responseTime, 0)
|
||||||
r.stats.logError(n.requestType, n.name, n.errMsg)
|
r.stats.logError(n.requestType, n.name, n.errMsg)
|
||||||
|
// report stats
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
data := r.stats.collectReportData()
|
data := r.stats.collectReportData()
|
||||||
data["user_count"] = atomic.LoadInt32(&r.currentClientsNum)
|
data["user_count"] = atomic.LoadInt32(&r.currentClientsNum)
|
||||||
data["state"] = atomic.LoadInt32(&r.state)
|
data["state"] = atomic.LoadInt32(&r.state)
|
||||||
r.outputOnEevent(data)
|
r.outputOnEevent(data)
|
||||||
|
// stop
|
||||||
case <-r.stopChan:
|
case <-r.stopChan:
|
||||||
|
atomic.StoreInt32(&r.state, stateQuitting)
|
||||||
|
|
||||||
// stop previous goroutines without blocking
|
// stop previous goroutines without blocking
|
||||||
// those goroutines will exit when r.safeRun returns
|
// those goroutines will exit when r.safeRun returns
|
||||||
close(quitChan)
|
close(quitChan)
|
||||||
|
|
||||||
// stop rate limiter
|
// stop rate limiter
|
||||||
if r.rateLimitEnabled {
|
if r.rateLimitEnabled {
|
||||||
r.rateLimiter.Stop()
|
r.rateLimiter.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// output teardown
|
// output teardown
|
||||||
r.outputOnStop()
|
r.outputOnStop()
|
||||||
|
|
||||||
|
atomic.StoreInt32(&r.state, stateStopped)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *localRunner) stop() {
|
func (r *localRunner) stop() {
|
||||||
atomic.StoreInt32(&r.state, stateQuitting)
|
|
||||||
close(r.stopChan)
|
close(r.stopChan)
|
||||||
atomic.StoreInt32(&r.state, stateStopped)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ func TestLocalRunner(t *testing.T) {
|
|||||||
Name: "TaskA",
|
Name: "TaskA",
|
||||||
}
|
}
|
||||||
tasks := []*Task{taskA}
|
tasks := []*Task{taskA}
|
||||||
runner := newLocalRunner(tasks, nil, 2, 2)
|
runner := newLocalRunner(2, 2)
|
||||||
|
runner.setTasks(tasks)
|
||||||
go runner.start()
|
go runner.start()
|
||||||
time.Sleep(4 * time.Second)
|
time.Sleep(4 * time.Second)
|
||||||
runner.stop()
|
runner.stop()
|
||||||
|
|||||||
Reference in New Issue
Block a user