blob: c99332803288c1f906d8a3e8c8513a99cd521b60 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package engine
import (
"context"
"sync"
"sync/atomic"
"time"
)
import (
"github.com/dubbogo/gost/log/logger"
)
type BenchmarkFunc func(ctx context.Context) (duration time.Duration, err error)
type Engine struct {
concurrency int
warmupDuration time.Duration
testDuration time.Duration
requestTimeout time.Duration
metricsCollector *MetricsCollector
stats *Statistics
isWarmup atomic.Bool
wg sync.WaitGroup
stopChan chan struct{}
ctx context.Context
cancel context.CancelFunc
stopOnce sync.Once
}
func NewEngine(concurrency int, warmupDuration, testDuration, requestTimeout time.Duration) *Engine {
ctx, cancel := context.WithCancel(context.Background())
e := &Engine{
concurrency: concurrency,
warmupDuration: warmupDuration,
testDuration: testDuration,
requestTimeout: requestTimeout,
metricsCollector: NewMetricsCollector(),
stats: NewStatistics(),
stopChan: make(chan struct{}),
ctx: ctx,
cancel: cancel,
}
e.isWarmup.Store(true)
return e
}
func (e *Engine) Run(benchmarkFunc BenchmarkFunc) *Statistics {
logger.Info("[INFO] Starting warmup...")
e.startWorkers(benchmarkFunc)
time.Sleep(e.warmupDuration)
logger.Info("[INFO] Warmup completed, starting benchmark...")
e.metricsCollector.Reset()
e.isWarmup.Store(false)
timer := time.NewTimer(e.testDuration)
defer timer.Stop()
<-timer.C
e.Stop()
return e.stats.Compute(e.metricsCollector)
}
func (e *Engine) startWorkers(benchmarkFunc BenchmarkFunc) {
for i := 0; i < e.concurrency; i++ {
e.wg.Add(1)
go e.worker(benchmarkFunc)
}
}
func (e *Engine) worker(benchmarkFunc BenchmarkFunc) {
defer e.wg.Done()
for {
select {
case <-e.stopChan:
return
default:
ctx, cancel := context.WithTimeout(e.ctx, e.requestTimeout)
start := time.Now()
_, err := benchmarkFunc(ctx)
duration := time.Since(start)
cancel()
if !e.isWarmup.Load() {
e.metricsCollector.Record(duration, err)
}
}
}
}
func (e *Engine) Stop() {
e.stopOnce.Do(func() {
close(e.stopChan)
e.cancel()
e.wg.Wait()
logger.Info("[INFO] Benchmark completed")
})
}
func (e *Engine) GetMetricsCollector() *MetricsCollector {
return e.metricsCollector
}