Week7 命题作业(性能压测工具编写)

发布于: 2020 年 07 月 19 日
Week7命题作业(性能压测工具编写)

1、性能压测的时候,随着并发压力的增加,系统响应时间和吞吐量如何变化,为什么?

随着并发压力的增加

  • 系统响应时间先是基本不变,然后再缓慢增加,最后急剧增加至无限大。

  • 吞吐量先是随着并发压力(数)的增加而线性增加,而后增速缓慢,最后逐渐下降再急剧下降在某点变为0。

  • 这是因为在并发压力从0开始时,系统资源是足够的,对每个请求的处理不会阻塞,所以一开始系统响应时间基本不变,而且,在响应时间基本不变的前提下,吞吐量与并发数成正比,自然是线性增长的。

  • 随着并发压力逐渐增大至超过资源刚好够用的一个点,继续增加并发压力,新的请求进入计算机没有足够的资源进行处理,就会去排队,就产生了阻塞,响应时间会随着并发数的增加而缓慢增加,,分母缓慢增大,吞吐量增速变慢。

  • 并发压力继续增大,超过了系统的最大负载点,开始出现响应错误或者超时(队列可能已满或者排队时间过长),响应时间急剧增加,响应错误率也急剧增加,由公式,吞吐量急剧降低,到达系统崩溃点时系统已无法处理新的请求,响应时间无限大(即吞吐量极限为0,有限的并发数除以无限大的响应时间可得),整个系统失去响应,系统崩溃。

2、用你熟悉的编程语言写一个Web性能压测工具,输入参数:URL,请求总次数,并发数。输出参数:平均响应时间,95%响应时间。用这个测试工具以10并发、100次请求压测www.baidu.com。

运行结果如下:

-n后接请求总次数,-c后接并发数,然后输入URL

代码基于https://github.com/kaimixu/kbang 进行修改,详细代码如下:

main.go

package main
import (
"flag"
"fmt"
"os"
"runtime"
"PerformancePressureTest/robot"
)
var (
n, c, t int
url string
)
var usage = `Usage: main [options...] <url>
options:
-n Number of requests to run (default: 10)
-c Number of requests to run concurrency (default: 1)
-t Request connection timeout in second (default: 5s)
`
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
flag.Usage = func() {
fmt.Fprint(os.Stderr, usage)
}
flag.IntVar(&n, "n", 10, "")
flag.IntVar(&c, "c", 1, "")
flag.IntVar(&t, "t", 5, "")
flag.Parse()
if flag.NArg() < 1 {
abort("")
}
var httpConf = robot.HttpConf{
Timeout: t,
}
if flag.NArg() > 0 {
url = flag.Args()[0]
} else {
url = "https://www.taobao.com/"
}
httpConf.Request = robot.RequestConf{
Method: "GET",
Url: url,
}
robot := robot.NewRoboter(n, c, &httpConf)
err := robot.CreateRequest()
if err != nil {
abort(err.Error())
}
robot.Run()
}
func abort(errmsg string) {
if errmsg != "" {
fmt.Fprintf(os.Stderr, "%s\n\n", errmsg)
}
flag.Usage()
os.Exit(1)
}

robot/robot.go

package robot
import (
"errors"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"strings"
"sync"
"time"
)
type RequestConf struct {
Method string `method`
Url string `url`
}
type HttpConf struct {
Timeout int `timeout`
Request RequestConf `request`
}
type Roboter struct {
n int
c int
count int
httpConf *HttpConf
hc *http.Client
requests []*http.Request
output *output
}
func NewRoboter(n, c int, httpConf *HttpConf) *Roboter {
return &Roboter{
n: n,
c: c,
httpConf: httpConf,
requests: make([]*http.Request, 0),
output: newOutput(n, c),
}
}
func (this *Roboter) CreateRequest() error {
type request struct {
preq *http.Request
conf *RequestConf
}
reqC := this.httpConf.Request
method := strings.ToUpper(reqC.Method)
if method != "GET" {
return errors.New("invalid http method")
}
r, err := http.NewRequest(method, reqC.Url, nil)
if err != nil {
return err
}
for i := 0; i < this.n; i++ {
req := &request{preq: r, conf: &reqC}
this.requests = append(this.requests, cloneRequest(req.preq))
}
return nil
}
func (this *Roboter) Run() {
s := make(chan os.Signal, 1)
signal.Notify(s, os.Interrupt)
tr := &http.Transport{
Dial: (&net.Dialer{
Timeout: time.Duration(this.httpConf.Timeout) * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
DisableKeepAlives: true,
}
this.hc = &http.Client{Transport: tr}
fmt.Println("start...")
st := time.Now()
go func() {
<-s
fmt.Println("receive sigint")
this.output.finalize(time.Now().Sub(st).Seconds())
os.Exit(1)
}()
this.startWorkers()
this.output.finalize(time.Now().Sub(st).Seconds())
}
func (this *Roboter) startWorkers() {
var wg sync.WaitGroup
wg.Add(this.c)
for i := 0; i < this.c; i++ {
go func(rid int) {
this.startWorker(rid, this.n/this.c)
wg.Done()
}(i)
}
wg.Wait()
}
func (this *Roboter) startWorker(rid, num int) {
for i := 0; i < num; i++ {
req := this.requests[rid*num+i]
this.sendRequest(req)
}
}
func (this *Roboter) sendRequest(req *http.Request) {
s := time.Now()
var code int
resp, err := this.hc.Do(req)
if err == nil {
code = resp.StatusCode
}
this.output.addResult(&result{
statusCode: code,
duration: time.Now().Sub(s),
})
}
func cloneRequest(r *http.Request) *http.Request {
r2 := new(http.Request)
*r2 = *r
r2.Header = make(http.Header, len(r.Header))
for k, s := range r.Header {
r2.Header[k] = append([]string(nil), s...)
}
return r2
}

robot/print.go

package robot
import (
"fmt"
"os"
"sort"
"time"
)
type result struct {
statusCode int
duration time.Duration
}
type output struct {
average float64
percent95 float64
rps float64
n int
c int
reqNumTotal int64
reqNumFail int64
reqNumSucc int64
costTimeTotal float64
results chan *result
}
var durationSort []float64
func newOutput(n int, c int) *output {
return &output{
n: n,
c: c,
results: make(chan *result, n),
}
}
func (this *output) addResult(res *result) {
this.results <- res
}
func (this *output) finalize(costTime float64) {
this.costTimeTotal = costTime
for {
select {
case res := <-this.results:
this.reqNumTotal++
durationSort = append(durationSort, res.duration.Seconds())
if res.statusCode != 200 {
this.reqNumFail++
} else {
this.reqNumSucc++
}
default:
sort.Float64s(durationSort)
index95 := this.reqNumTotal * 95 / 100
this.percent95 = durationSort[index95]
this.rps = float64(this.reqNumTotal) / this.costTimeTotal
this.average = this.costTimeTotal / float64(this.reqNumTotal)
this.print()
return
}
}
}
func (this *output) print() {
if this.reqNumTotal > 0 {
fmt.Printf("总结:\n")
fmt.Printf(" 并发数:\t%d\n", this.c)
fmt.Printf(" 总花费时间:\t%0.4f 秒\n", this.costTimeTotal)
fmt.Printf(" 完成请求数:\t%d\n", this.reqNumTotal)
fmt.Printf(" 失败请求数:\t%d\n", this.reqNumFail)
fmt.Printf(" 成功请求数:\t%d\n", this.reqNumSucc)
fmt.Printf(" 每秒请求数:\t%0.4f\n", this.rps)
fmt.Printf(" 平均响应时间:\t%0.4f 秒\n", this.average)
fmt.Printf(" 95%%响应时间:\t%0.4f 秒\n", this.percent95)
}
os.Exit(0)
}

发布于: 2020 年 07 月 19 日 阅读数: 37
用户头像

星河寒水

关注

还未添加个人签名 2018.09.17 加入

还未添加个人简介

评论

发布
暂无评论
Week7命题作业(性能压测工具编写)