mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-19 18:19:32 +08:00
614 lines
17 KiB
Go
614 lines
17 KiB
Go
package boomer
|
|
|
|
import (
|
|
"math"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/rs/zerolog/log"
|
|
"golang.org/x/net/context"
|
|
|
|
"github.com/httprunner/httprunner/v4/hrp/internal/json"
|
|
)
|
|
|
|
// Mode is the running mode of boomer, both standalone and distributed are supported.
|
|
type Mode int
|
|
|
|
const (
|
|
// DistributedMasterMode requires being connected by each worker.
|
|
DistributedMasterMode Mode = iota
|
|
// DistributedWorkerMode requires connecting to a master.
|
|
DistributedWorkerMode
|
|
// StandaloneMode will run without a master.
|
|
StandaloneMode
|
|
)
|
|
|
|
// A Boomer is used to run tasks.
|
|
type Boomer struct {
|
|
masterHost string
|
|
masterPort int
|
|
mode Mode
|
|
|
|
localRunner *localRunner
|
|
workerRunner *workerRunner
|
|
masterRunner *masterRunner
|
|
|
|
testcasePath []string
|
|
|
|
cpuProfile string
|
|
cpuProfileDuration time.Duration
|
|
|
|
memoryProfile string
|
|
memoryProfileDuration time.Duration
|
|
|
|
disableKeepalive bool
|
|
disableCompression bool
|
|
}
|
|
|
|
type Profile struct {
|
|
SpawnCount int64 `json:"spawn-count,omitempty" yaml:"spawn-count,omitempty" mapstructure:"spawn-count,omitempty"`
|
|
SpawnRate float64 `json:"spawn-rate,omitempty" yaml:"spawn-rate,omitempty" mapstructure:"spawn-rate,omitempty"`
|
|
RunTime int64 `json:"run-time,omitempty" yaml:"run-time,omitempty" mapstructure:"run-time,omitempty"`
|
|
MaxRPS int64 `json:"max-rps,omitempty" yaml:"max-rps,omitempty" mapstructure:"max-rps,omitempty"`
|
|
LoopCount int64 `json:"loop-count,omitempty" yaml:"loop-count,omitempty" mapstructure:"loop-count,omitempty"`
|
|
RequestIncreaseRate string `json:"request-increase-rate,omitempty" yaml:"request-increase-rate,omitempty" mapstructure:"request-increase-rate,omitempty"`
|
|
MemoryProfile string `json:"memory-profile,omitempty" yaml:"memory-profile,omitempty" mapstructure:"memory-profile,omitempty"`
|
|
MemoryProfileDuration time.Duration `json:"memory-profile-duration,omitempty" yaml:"memory-profile-duration,omitempty" mapstructure:"memory-profile-duration,omitempty"`
|
|
CPUProfile string `json:"cpu-profile,omitempty" yaml:"cpu-profile,omitempty" mapstructure:"cpu-profile,omitempty"`
|
|
CPUProfileDuration time.Duration `json:"cpu-profile-duration,omitempty" yaml:"cpu-profile-duration,omitempty" mapstructure:"cpu-profile-duration,omitempty"`
|
|
PrometheusPushgatewayURL string `json:"prometheus-gateway,omitempty" yaml:"prometheus-gateway,omitempty" mapstructure:"prometheus-gateway,omitempty"`
|
|
DisableConsoleOutput bool `json:"disable-console-output,omitempty" yaml:"disable-console-output,omitempty" mapstructure:"disable-console-output,omitempty"`
|
|
DisableCompression bool `json:"disable-compression,omitempty" yaml:"disable-compression,omitempty" mapstructure:"disable-compression,omitempty"`
|
|
DisableKeepalive bool `json:"disable-keepalive,omitempty" yaml:"disable-keepalive,omitempty" mapstructure:"disable-keepalive,omitempty"`
|
|
}
|
|
|
|
func NewProfile() *Profile {
|
|
return &Profile{
|
|
SpawnCount: 1,
|
|
SpawnRate: 1,
|
|
MaxRPS: -1,
|
|
LoopCount: -1,
|
|
RequestIncreaseRate: "-1",
|
|
CPUProfileDuration: 30 * time.Second,
|
|
MemoryProfileDuration: 30 * time.Second,
|
|
}
|
|
}
|
|
|
|
func (b *Boomer) GetProfile() *Profile {
|
|
switch b.mode {
|
|
case DistributedMasterMode:
|
|
return b.masterRunner.profile
|
|
case DistributedWorkerMode:
|
|
return b.workerRunner.profile
|
|
default:
|
|
return b.localRunner.profile
|
|
}
|
|
}
|
|
|
|
func (b *Boomer) SetProfile(profile *Profile) {
|
|
switch b.mode {
|
|
case DistributedMasterMode:
|
|
b.masterRunner.profile = profile
|
|
case DistributedWorkerMode:
|
|
b.workerRunner.profile = profile
|
|
default:
|
|
b.localRunner.profile = profile
|
|
}
|
|
}
|
|
|
|
// SetMode only accepts boomer.DistributedMasterMode、boomer.DistributedWorkerMode and boomer.StandaloneMode.
|
|
func (b *Boomer) SetMode(mode Mode) {
|
|
switch mode {
|
|
case DistributedMasterMode:
|
|
b.mode = DistributedMasterMode
|
|
case DistributedWorkerMode:
|
|
b.mode = DistributedWorkerMode
|
|
case StandaloneMode:
|
|
b.mode = StandaloneMode
|
|
default:
|
|
log.Error().Err(errors.New("Invalid mode, ignored!"))
|
|
}
|
|
}
|
|
|
|
// GetMode returns boomer operating mode
|
|
func (b *Boomer) GetMode() string {
|
|
switch b.mode {
|
|
case DistributedMasterMode:
|
|
return "master"
|
|
case DistributedWorkerMode:
|
|
return "worker"
|
|
case StandaloneMode:
|
|
return "standalone"
|
|
default:
|
|
log.Error().Err(errors.New("Invalid mode, ignored!"))
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// NewStandaloneBoomer returns a new Boomer, which can run without master.
|
|
func NewStandaloneBoomer(spawnCount int64, spawnRate float64) *Boomer {
|
|
return &Boomer{
|
|
mode: StandaloneMode,
|
|
localRunner: newLocalRunner(spawnCount, spawnRate),
|
|
}
|
|
}
|
|
|
|
// NewMasterBoomer returns a new Boomer.
|
|
func NewMasterBoomer(masterBindHost string, masterBindPort int) *Boomer {
|
|
return &Boomer{
|
|
masterRunner: newMasterRunner(masterBindHost, masterBindPort),
|
|
mode: DistributedMasterMode,
|
|
}
|
|
}
|
|
|
|
// NewWorkerBoomer returns a new Boomer.
|
|
func NewWorkerBoomer(masterHost string, masterPort int) *Boomer {
|
|
return &Boomer{
|
|
workerRunner: newWorkerRunner(masterHost, masterPort),
|
|
masterHost: masterHost,
|
|
masterPort: masterPort,
|
|
mode: DistributedWorkerMode,
|
|
}
|
|
}
|
|
|
|
// SetAutoStart auto start to load testing
|
|
func (b *Boomer) SetAutoStart() {
|
|
b.masterRunner.autoStart = true
|
|
}
|
|
|
|
// RunMaster start to run master runner
|
|
func (b *Boomer) RunMaster() {
|
|
b.masterRunner.run()
|
|
}
|
|
|
|
// RunWorker start to run worker runner
|
|
func (b *Boomer) RunWorker() {
|
|
b.workerRunner.run()
|
|
}
|
|
|
|
// TestCaseBytesChan gets test case bytes chan
|
|
func (b *Boomer) TestCaseBytesChan() chan []byte {
|
|
return b.masterRunner.testCaseBytesChan
|
|
}
|
|
|
|
func (b *Boomer) GetTestCaseBytes() []byte {
|
|
switch b.mode {
|
|
case DistributedMasterMode:
|
|
return b.masterRunner.testCasesBytes
|
|
case DistributedWorkerMode:
|
|
return b.workerRunner.testCasesBytes
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func ProfileToBytes(profile *Profile) []byte {
|
|
profileBytes, err := json.Marshal(profile)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to marshal testcases")
|
|
return nil
|
|
}
|
|
return profileBytes
|
|
}
|
|
|
|
func BytesToProfile(profileBytes []byte) *Profile {
|
|
var profile *Profile
|
|
err := json.Unmarshal(profileBytes, &profile)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to unmarshal testcases")
|
|
}
|
|
return profile
|
|
}
|
|
|
|
// GetTasksChan getsTasks chan
|
|
func (b *Boomer) GetTasksChan() chan *task {
|
|
switch b.mode {
|
|
case DistributedWorkerMode:
|
|
return b.workerRunner.tasksChan
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (b *Boomer) GetRebalanceChan() chan bool {
|
|
switch b.mode {
|
|
case DistributedWorkerMode:
|
|
return b.workerRunner.controller.getRebalanceChan()
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (b *Boomer) SetTestCasesPath(paths []string) {
|
|
b.testcasePath = paths
|
|
}
|
|
|
|
func (b *Boomer) GetTestCasesPath() []string {
|
|
return b.testcasePath
|
|
}
|
|
|
|
func (b *Boomer) ParseTestCasesChan() chan bool {
|
|
return b.masterRunner.parseTestCasesChan
|
|
}
|
|
|
|
// GetMasterHost returns master IP
|
|
func (b *Boomer) GetMasterHost() string {
|
|
return b.masterHost
|
|
}
|
|
|
|
// GetState gets worker state
|
|
func (b *Boomer) GetState() int32 {
|
|
switch b.mode {
|
|
case DistributedWorkerMode:
|
|
return b.workerRunner.getState()
|
|
case DistributedMasterMode:
|
|
return b.masterRunner.getState()
|
|
default:
|
|
return b.localRunner.getState()
|
|
}
|
|
}
|
|
|
|
// SetSpawnCount sets spawn count
|
|
func (b *Boomer) SetSpawnCount(spawnCount int64) {
|
|
switch b.mode {
|
|
case DistributedMasterMode:
|
|
b.masterRunner.setSpawnCount(spawnCount)
|
|
case DistributedWorkerMode:
|
|
b.workerRunner.setSpawnCount(spawnCount)
|
|
default:
|
|
b.localRunner.setSpawnCount(spawnCount)
|
|
}
|
|
}
|
|
|
|
// SetSpawnRate sets spawn rate
|
|
func (b *Boomer) SetSpawnRate(spawnRate float64) {
|
|
switch b.mode {
|
|
case DistributedMasterMode:
|
|
b.masterRunner.setSpawnRate(spawnRate)
|
|
case DistributedWorkerMode:
|
|
b.workerRunner.setSpawnRate(spawnRate)
|
|
default:
|
|
b.localRunner.setSpawnRate(spawnRate)
|
|
}
|
|
}
|
|
|
|
// SetRunTime sets run time
|
|
func (b *Boomer) SetRunTime(runTime int64) {
|
|
switch b.mode {
|
|
case DistributedMasterMode:
|
|
b.masterRunner.setRunTime(runTime)
|
|
case DistributedWorkerMode:
|
|
b.workerRunner.setRunTime(runTime)
|
|
default:
|
|
b.localRunner.setRunTime(runTime)
|
|
}
|
|
}
|
|
|
|
// SetExpectWorkers sets expect workers while load testing
|
|
func (b *Boomer) SetExpectWorkers(expectWorkers int, expectWorkersMaxWait int) {
|
|
b.masterRunner.setExpectWorkers(expectWorkers, expectWorkersMaxWait)
|
|
}
|
|
|
|
// SetRateLimiter creates rate limiter with the given limit and burst.
|
|
func (b *Boomer) SetRateLimiter(maxRPS int64, requestIncreaseRate string) {
|
|
var rateLimiter RateLimiter
|
|
var err error
|
|
if requestIncreaseRate != "-1" {
|
|
if maxRPS <= 0 {
|
|
maxRPS = math.MaxInt64
|
|
}
|
|
log.Warn().Int64("maxRPS", maxRPS).Str("increaseRate", requestIncreaseRate).Msg("set ramp up rate limiter")
|
|
rateLimiter, err = NewRampUpRateLimiter(maxRPS, requestIncreaseRate, time.Second)
|
|
} else {
|
|
if maxRPS > 0 {
|
|
log.Warn().Int64("maxRPS", maxRPS).Msg("set stable rate limiter")
|
|
rateLimiter = NewStableRateLimiter(maxRPS, time.Second)
|
|
}
|
|
}
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to create rate limiter")
|
|
return
|
|
}
|
|
|
|
if rateLimiter != nil {
|
|
switch b.mode {
|
|
case DistributedWorkerMode:
|
|
b.workerRunner.rateLimitEnabled = true
|
|
b.workerRunner.rateLimiter = rateLimiter
|
|
case StandaloneMode:
|
|
b.localRunner.rateLimitEnabled = true
|
|
b.localRunner.rateLimiter = rateLimiter
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetDisableKeepAlive disable keep-alive for tcp
|
|
func (b *Boomer) SetDisableKeepAlive(disableKeepalive bool) {
|
|
b.disableKeepalive = disableKeepalive
|
|
}
|
|
|
|
// SetIgnoreQuit not quit while master quit
|
|
func (b *Boomer) SetIgnoreQuit() {
|
|
b.workerRunner.ignoreQuit = true
|
|
}
|
|
|
|
// SetDisableCompression disable compression to prevent the Transport from requesting compression with an "Accept-Encoding: gzip"
|
|
func (b *Boomer) SetDisableCompression(disableCompression bool) {
|
|
b.disableCompression = disableCompression
|
|
}
|
|
|
|
func (b *Boomer) GetDisableKeepAlive() bool {
|
|
return b.disableKeepalive
|
|
}
|
|
|
|
func (b *Boomer) GetDisableCompression() bool {
|
|
return b.disableCompression
|
|
}
|
|
|
|
// SetLoopCount set loop count for test.
|
|
func (b *Boomer) SetLoopCount(loopCount int64) {
|
|
// total loop count for testcase, it will be evenly distributed to each worker
|
|
switch b.mode {
|
|
case DistributedWorkerMode:
|
|
b.workerRunner.loop = &Loop{loopCount: loopCount * b.workerRunner.getSpawnCount()}
|
|
case DistributedMasterMode:
|
|
b.masterRunner.loop = &Loop{loopCount: loopCount * b.masterRunner.getSpawnCount()}
|
|
case StandaloneMode:
|
|
b.localRunner.loop = &Loop{loopCount: loopCount * b.localRunner.getSpawnCount()}
|
|
}
|
|
}
|
|
|
|
// AddOutput accepts outputs which implements the boomer.Output interface.
|
|
func (b *Boomer) AddOutput(o Output) {
|
|
switch b.mode {
|
|
case DistributedWorkerMode:
|
|
b.workerRunner.addOutput(o)
|
|
case DistributedMasterMode:
|
|
b.masterRunner.addOutput(o)
|
|
case StandaloneMode:
|
|
b.localRunner.addOutput(o)
|
|
}
|
|
}
|
|
|
|
// EnableCPUProfile will start cpu profiling after run.
|
|
func (b *Boomer) EnableCPUProfile(cpuProfile string, duration time.Duration) {
|
|
b.cpuProfile = cpuProfile
|
|
b.cpuProfileDuration = duration
|
|
}
|
|
|
|
// EnableMemoryProfile will start memory profiling after run.
|
|
func (b *Boomer) EnableMemoryProfile(memoryProfile string, duration time.Duration) {
|
|
b.memoryProfile = memoryProfile
|
|
b.memoryProfileDuration = duration
|
|
}
|
|
|
|
// EnableGracefulQuit catch SIGINT and SIGTERM signals to quit gracefully
|
|
func (b *Boomer) EnableGracefulQuit(ctx context.Context) context.Context {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
c := make(chan os.Signal, 1)
|
|
signal.Notify(c, syscall.SIGTERM, syscall.SIGINT)
|
|
go func() {
|
|
<-c
|
|
b.Quit()
|
|
cancel()
|
|
}()
|
|
return ctx
|
|
}
|
|
|
|
// Run accepts a slice of Task and connects to the locust master.
|
|
func (b *Boomer) Run(tasks ...*Task) {
|
|
if b.cpuProfile != "" {
|
|
err := startCPUProfile(b.cpuProfile, b.cpuProfileDuration)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to start cpu profiling")
|
|
}
|
|
}
|
|
if b.memoryProfile != "" {
|
|
err := startMemoryProfile(b.memoryProfile, b.memoryProfileDuration)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to start memory profiling")
|
|
}
|
|
}
|
|
|
|
switch b.mode {
|
|
case DistributedWorkerMode:
|
|
log.Info().Msg("running in worker mode")
|
|
b.workerRunner.setTasks(tasks)
|
|
b.workerRunner.start()
|
|
case StandaloneMode:
|
|
log.Info().Msg("running in standalone mode")
|
|
b.localRunner.setTasks(tasks)
|
|
b.localRunner.start()
|
|
default:
|
|
log.Error().Err(errors.New("Invalid mode, expected boomer.DistributedMode or boomer.StandaloneMode"))
|
|
}
|
|
}
|
|
|
|
func (b *Boomer) SetTasks(tasks ...*Task) {
|
|
switch b.mode {
|
|
case DistributedWorkerMode:
|
|
log.Info().Msg("set tasks to worker")
|
|
b.workerRunner.setTasks(tasks)
|
|
case StandaloneMode:
|
|
log.Info().Msg("set tasks to standalone")
|
|
b.localRunner.setTasks(tasks)
|
|
default:
|
|
log.Error().Err(errors.New("Invalid mode, expected boomer.DistributedMode or boomer.StandaloneMode"))
|
|
}
|
|
}
|
|
|
|
// RecordTransaction reports a transaction stat.
|
|
func (b *Boomer) RecordTransaction(name string, success bool, elapsedTime int64, contentSize int64) {
|
|
var runnerStats *requestStats
|
|
switch b.mode {
|
|
case DistributedWorkerMode:
|
|
runnerStats = b.workerRunner.stats
|
|
case DistributedMasterMode:
|
|
runnerStats = b.masterRunner.stats
|
|
case StandaloneMode:
|
|
runnerStats = b.localRunner.stats
|
|
}
|
|
runnerStats.transactionChan <- &transaction{
|
|
name: name,
|
|
success: success,
|
|
elapsedTime: elapsedTime,
|
|
contentSize: contentSize,
|
|
}
|
|
}
|
|
|
|
// RecordSuccess reports a success.
|
|
func (b *Boomer) RecordSuccess(requestType, name string, responseTime int64, responseLength int64) {
|
|
var runnerStats *requestStats
|
|
switch b.mode {
|
|
case DistributedWorkerMode:
|
|
runnerStats = b.workerRunner.stats
|
|
case DistributedMasterMode:
|
|
runnerStats = b.masterRunner.stats
|
|
case StandaloneMode:
|
|
runnerStats = b.localRunner.stats
|
|
}
|
|
runnerStats.requestSuccessChan <- &requestSuccess{
|
|
requestType: requestType,
|
|
name: name,
|
|
responseTime: responseTime,
|
|
responseLength: responseLength,
|
|
}
|
|
}
|
|
|
|
// RecordFailure reports a failure.
|
|
func (b *Boomer) RecordFailure(requestType, name string, responseTime int64, exception string) {
|
|
var runnerStats *requestStats
|
|
switch b.mode {
|
|
case DistributedWorkerMode:
|
|
runnerStats = b.workerRunner.stats
|
|
case DistributedMasterMode:
|
|
runnerStats = b.masterRunner.stats
|
|
case StandaloneMode:
|
|
runnerStats = b.localRunner.stats
|
|
}
|
|
runnerStats.requestFailureChan <- &requestFailure{
|
|
requestType: requestType,
|
|
name: name,
|
|
responseTime: responseTime,
|
|
errMsg: exception,
|
|
}
|
|
}
|
|
|
|
// Start starts to run
|
|
func (b *Boomer) Start(Args *Profile) error {
|
|
if b.masterRunner.isStarting() {
|
|
return errors.New("already started")
|
|
}
|
|
if b.masterRunner.isStopping() {
|
|
return errors.New("Please wait for all workers to finish")
|
|
}
|
|
if int(Args.SpawnCount) < b.masterRunner.server.getAvailableClientsLength() {
|
|
return errors.New("spawn count should be greater than available worker count")
|
|
}
|
|
b.SetSpawnCount(Args.SpawnCount)
|
|
b.SetSpawnRate(Args.SpawnRate)
|
|
b.SetRunTime(Args.RunTime)
|
|
b.SetProfile(Args)
|
|
err := b.masterRunner.start()
|
|
return err
|
|
}
|
|
|
|
// ReBalance starts to rebalance load test
|
|
func (b *Boomer) ReBalance(Args *Profile) error {
|
|
if !b.masterRunner.isStarting() {
|
|
return errors.New("no start")
|
|
}
|
|
if int(Args.SpawnCount) < b.masterRunner.server.getAvailableClientsLength() {
|
|
return errors.New("spawn count should be greater than available worker count")
|
|
}
|
|
b.SetSpawnCount(Args.SpawnCount)
|
|
b.SetSpawnRate(Args.SpawnRate)
|
|
b.SetRunTime(Args.RunTime)
|
|
b.SetProfile(Args)
|
|
err := b.masterRunner.rebalance()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to rebalance")
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Stop stops to load test
|
|
func (b *Boomer) Stop() error {
|
|
return b.masterRunner.stop()
|
|
}
|
|
|
|
// GetWorkersInfo gets workers information
|
|
func (b *Boomer) GetWorkersInfo() []WorkerNode {
|
|
return b.masterRunner.server.getAllWorkers()
|
|
}
|
|
|
|
// GetMasterInfo gets master information
|
|
func (b *Boomer) GetMasterInfo() map[string]interface{} {
|
|
masterInfo := make(map[string]interface{})
|
|
masterInfo["state"] = b.masterRunner.getState()
|
|
masterInfo["workers"] = b.masterRunner.server.getAvailableClientsLength()
|
|
masterInfo["target_users"] = b.masterRunner.getSpawnCount()
|
|
masterInfo["current_users"] = b.masterRunner.server.getCurrentUsers()
|
|
return masterInfo
|
|
}
|
|
|
|
func (b *Boomer) GetCloseChan() chan bool {
|
|
switch b.mode {
|
|
case DistributedWorkerMode:
|
|
return b.workerRunner.closeChan
|
|
case DistributedMasterMode:
|
|
return b.masterRunner.closeChan
|
|
default:
|
|
return b.localRunner.closeChan
|
|
}
|
|
}
|
|
|
|
// Quit will send a quit message to the master.
|
|
func (b *Boomer) Quit() {
|
|
switch b.mode {
|
|
case DistributedWorkerMode:
|
|
b.workerRunner.stop()
|
|
b.workerRunner.close()
|
|
case DistributedMasterMode:
|
|
b.masterRunner.close()
|
|
case StandaloneMode:
|
|
b.localRunner.stop()
|
|
}
|
|
}
|
|
|
|
func (b *Boomer) GetSpawnDoneChan() chan struct{} {
|
|
switch b.mode {
|
|
case DistributedWorkerMode:
|
|
return b.workerRunner.controller.getSpawnDone()
|
|
case DistributedMasterMode:
|
|
return b.masterRunner.controller.getSpawnDone()
|
|
default:
|
|
return b.localRunner.controller.getSpawnDone()
|
|
}
|
|
}
|
|
|
|
func (b *Boomer) GetSpawnCount() int {
|
|
switch b.mode {
|
|
case DistributedWorkerMode:
|
|
return int(b.workerRunner.getSpawnCount())
|
|
case DistributedMasterMode:
|
|
return int(b.masterRunner.getSpawnCount())
|
|
default:
|
|
return int(b.localRunner.getSpawnCount())
|
|
}
|
|
}
|
|
|
|
func (b *Boomer) ResetStartTime() {
|
|
switch b.mode {
|
|
case DistributedWorkerMode:
|
|
b.workerRunner.stats.total.resetStartTime()
|
|
case DistributedMasterMode:
|
|
b.masterRunner.stats.total.resetStartTime()
|
|
default:
|
|
b.localRunner.stats.total.resetStartTime()
|
|
}
|
|
}
|