用golang实现的一个简单的API压力测试程序,原理如下:使用协程和通道发送并发的网络请求,并统计相关数据。程序的命令行参数有并发请求数量、请求方法、请求URL。
package main
import (
"flag"
"fmt"
"io"
"net/http"
"sort"
"sync"
"time"
)
var timeDistribution []time.Duration // 存储每个请求的用时
func main() {
fmt.Println("start API stress test:")
var concurrentCount int // 并发请求数量
var method string // 请求方法,Get或Post
flag.IntVar(&concurrentCount, "c", 100, "concurrent request concurrentCount")
flag.StringVar(&method, "m", "g", "request method: g is get, p is post")
flag.Parse()
args := flag.Args()
if len(args) < 1 {
fmt.Println("Usage: apic <URL>")
return
}
url := args[0]
success := make(chan struct{})
var wg sync.WaitGroup
var mu sync.Mutex
start := time.Now()
for i := 0; i < concurrentCount; i++ {
wg.Add(1)
go func() {
defer wg.Done()
sendRequest(url, success, method, &mu)
}()
}
// 在协程里面调用wg.Wait()不让主协程阻塞,当所有goroutine完成后关闭success通道
go func() {
wg.Wait()
close(success)
}()
successCount := 0
for range success {
successCount++
}
end := time.Now()
var timePerRequest = time.Duration(float64(end.Sub(start)) / float64(concurrentCount))
sort.Slice(timeDistribution, func(i, j int) bool {
return timeDistribution[i] < timeDistribution[j]
})
length := len(timeDistribution)
index50 := int(float64(length) * 0.5)
index75 := int(float64(length) * 0.75)
index90 := int(float64(length) * 0.90)
index95 := int(float64(length) * 0.95)
index99 := int(float64(length) * 0.99)
fmt.Printf("success:%d total:%d\n", successCount, concurrentCount) // 统计成功数
fmt.Printf("success rate: %.2f%%\n", float64(successCount)/float64(concurrentCount)*100) // 统计成功率
fmt.Println("total time:", end.Sub(start)) // 统计总时间
fmt.Println("time per request:", timePerRequest) // 统计每个请求的平均时间
fmt.Printf("requests per second: %.2f\n", float64(time.Second)/float64(timePerRequest)) // 统计每秒可以发送的请求数
if length > 0 {
fmt.Println("success request time distribution statistics:")
fmt.Println("min request time:", timeDistribution[0])
fmt.Println("max request time:", timeDistribution[length-1])
fmt.Println("avg request time:", time.Duration(float64(sliceSum(timeDistribution))/float64(len(timeDistribution))))
fmt.Println("50% requests in", timeDistribution[index50])
fmt.Println("75% requests in", timeDistribution[index75])
fmt.Println("90% requests in", timeDistribution[index90])
fmt.Println("95% requests in", timeDistribution[index95])
fmt.Println("99% requests in", timeDistribution[index99])
}
}
func sendRequest(url string, success chan struct{}, method string, mu *sync.Mutex) {
var resp *http.Response
var err error
start := time.Now()
if method == "p" {
resp, err = http.Post(url, "", nil)
} else {
resp, err = http.Get(url)
}
if err != nil {
return
}
//关闭resp.Body,释放资源
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
fmt.Println(err)
}
}(resp.Body)
end := time.Now()
mu.Lock()
timeDistribution = append(timeDistribution, end.Sub(start))
mu.Unlock()
// 若请求成功向success通道写入数据
if resp.StatusCode == http.StatusOK {
success <- struct{}{}
}
}
func sliceSum(s []time.Duration) time.Duration {
var sum time.Duration = 0
for _, duration := range s {
sum += duration
}
return sum
}
使用下面的命令可以将上面的程序编译成可执行程序
go build -o apic main.go
如果要将程序变成系统命令,可以使用下面的命令
sudo cp apic /usr/local/bin
程序的执行示例如下