观察网站性能

ab

一般的使用命令

./ab -c 10 -n 1000 http://www.baidu.com/
./ab --enable ssl -c 10 -n 1000 https://www.baidu.com/
./abs -c 10 -n 1000 https://www.baidu.com/

输出的解释

Server Software:        BWS/1.1 # 响应的服务器类型
Server Hostname:        www.baidu.com # 请求的 URL 主机名
Server Port:            443 # 请求端口
SSL/TLS Protocol:       TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128 # https 的版本和密码套件

Document Path:          / # 请求路径
Document Length:        227 bytes # HTTP响应数据的正文长度

Concurrency Level:      10 # 并发用户数,就是 -c 参数的值
Time taken for tests:   21.376 seconds  # 所有这些请求被处理完成所花费的总时间 单位秒
Complete requests:      1000 # 总请求数量,就是 -n 参数的值
Failed requests:        0 # 表示失败的请求数量
Total transferred:      1081951 bytes # 所有请求的响应数据长度总和。包括每个 HTTP 响应数据的头信息和正文数据的长度
HTML transferred:       227000 bytes # 所有请求的响应数据中正文数据的总和,也就是减去了 Total transferred 中 HTTP 响应数据中的头信息的长度
Requests per second:    46.78 [#/sec] (mean) # 吞吐量,计算公式: Complete requests/Time taken for tests  总请求数/处理完成这些请求数所花费的时间
Time per request:       213.762 [ms] (mean) # 用户平均请求等待时间,计算公式: Time token for tests/(Complete requests/Concurrency Level)。处理完成所有请求数所花费的时间/(总请求数/并发用户数)
Time per request:       21.376 [ms] (mean, across all concurrent requests) # 服务器平均请求等待时间,计算公式: Time taken for tests/Complete requests,正好是吞吐率的倒数。也可以这么统计: Time per request/Concurrency Level
Transfer rate:          49.43 [Kbytes/sec] received # 表示这些请求在单位时间内从服务器获取的数据长度,计算公式: Total trnasferred/ Time taken for tests,这个统计很好的说明服务器的处理能力达到极限时,其出口宽带的需求量。

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       31  132 203.5     85    1204 # Connect 建立 tcp 连接的耗时
Processing:     0   81 150.7     55    1141 # Processing 总耗时减去 Connect
Waiting:        0   53 122.5     35    1103 # Waiting 客户端发送完请求信息的最后一个字节 到 接收响应信息的的第一个字节 的耗时
Total:         85  213 250.9    145    1304 # Total 总的耗时,从建立连接到关闭连接的耗时

# min 最小值, mean 平均值, [+/-sd] 标准差, median 中位数, max 最大值

Percentage of the requests served within a certain time (ms)
  50%    145 # 50% 的请求在 145ms 内返回
  66%    154
  75%    160
  80%    165
  90%    192
  95%   1133
  98%   1186 # 98% 的请求在 1186ms 内返回
  99%   1235
 100%   1304 (longest request)

ab 更详细的用法

ab 请求时设置 cookie

./ab -c 10 -n 1000 \
-C "name=ball;age=99;sex=male" \
http://localhsot/

./ab -c 10 -n 1000 \
-H "Cookie: name=ball;age=36" \
http://localhsot/

ab 发送 post 或 put

./ab -c 10 -n 1000 \
-p 'post.txt' -T 'application/x-www-form-urlencoded' \
http://localhsot/

./ab -c 10 -n 1000 \
-u 'put.txt' -T 'application/x-www-form-urlencoded' \
http://localhsot/

如果发送的内容是 application/x-www-form-urlencoded ,那么 post.txt 或 put.txt 里的内容也要经过编码

ab 发送文件和普通的 post 或 put 是一样的

./ab -c 10 -n 1000 \
-p 'post.txt' -T 'multipart/form-data; boundary=----WebKitFormBoundaryRO0YA4pq9oCgwTkt' \
http://localhsot/

post.txt 文件的内容

------WebKitFormBoundaryRO0YA4pq9oCgwTkt
Content-Disposition: form-data; name="fileUpload"; filename="test.png"
Content-Type: image/png
iVBORw0KGg.............................................
------WebKitFormBoundaryRO0YA4pq9oCgwTkt--

实质上就是手工实现 rfc 1867 1521 1522 这几个标准

ab 只能进行一些简单的压力测试,一些更详细的测试还是要用 jmeter

ab 的文档 https://httpd.apache.org/docs/current/programs/ab.html

curl

一般的使用命令

curl -o /dev/null -s -w %{time_namelookup}::%{time_connect}::%{time_starttransfer}::%{time_total}::%{speed_download}"\n" "https://www.baidu.com"

命令解释

-o: 把 curl 返回的 html js 写到 /dev/null
-s: 去掉所有状态
-w: 按照后面的格式输出
time_namelookup: DNS 解析域名 www.baidu.com 的时间
time_commect: client 和 server 端建立 TCP 连接的时间, 包括前一项的时间
time_starttransfer: 从 client 发出请求到 web 的 server 响应第一个字节的时间, 包括前二项的时间
time_total: client 发出请求到 web 的 server 发送会所有的相应数据的时间, 包括前三项的时间
speed_download: 下载速度 单位 byte/s

这是一段根据上面 curl 命令写成的,可以连续执行的,能显示多次执行平均值的 bash 脚本

# ./testwebsite.sh -c 2 -n 10 "https://www.baidu.com"

# 这个命令能输出当前系统 cpu 的核心数
nproc=$(nproc)
c=`echo $nproc | awk '{printf ("%d", $1*2)}'`
n=`echo $c | awk '{printf ("%d", $1*100)}'`
n=100

TEST_URL=www.baidu.com

verbose="false"
DRY_RUN="false"

# echo $nproc
# echo $c
# echo $n

print_version()
{
    echo 'print_version';
}

usage()
{
    echo 'usage';
}

# Use "${1-}" in order to avoid errors because of 'set -u'.
while [ -n "${1-}" ]; do
    case "${1}" in

        -c)
            shift
            c=${1}
            ;;

        -n)
            shift
            n=${1}
            ;;

        -v|--verbose)
            verbose="true"
            ;;

        --curl-options=*)
            opt=$(printf "%s\n" "${1}" | sed 's/^--curl-options=//')
            CURL_OPTIONS="${CURL_OPTIONS} ${opt}"
            ;;

        --curl-options)
            shift
            CURL_OPTIONS="${CURL_OPTIONS} ${1}"
            ;;

        --dry-run)
            DRY_RUN="true"
            ;;

        -h|--help)
            print_version
            usage
            exit 0
            ;;

        -V|--version)
            print_version
            exit 0
            ;;

        --)
            # This is the start of the list of URLs.
            shift
            for url in "$@"; do
                # Encode whitespaces into %20, since wget supports those URLs.
                newurl=$(printf "%s\n" "${url}" | sed 's/ /%20/g')
                URLS="${URLS} ${newurl}"
            done
            break
            ;;

        -*)
            error "Unknown option: '$1'."
            ;;

        *)
            # This must be a URL.
            # Encode whitespaces into %20, since wget supports those URLs.
            newurl=$(printf "%s\n" "${1}" | sed 's/ /%20/g')
            TEST_URL="${URLS} ${newurl}"
            ;;
    esac
    shift
done

# n=5
once="curl -o /dev/null -s -w %{http_code}::%{time_namelookup}::%{time_connect}::%{time_starttransfer}::%{time_total}::%{speed_download}, $TEST_URL ";

ConcurrencyCommand="for i in \$(seq 1 \$n); do echo \$once; echo \";\";done"

t=$(echo $ConcurrencyCommand | sed 's/\$n/'$n'/' | sed 's/\$once/%s/');
t2=$(echo $t" ;; "$once | awk '
    BEGIN {
        FS=";;";
    }
    {
        printf($1, $2)
        if (NR == 1) exit;
    }
    ')
t3="echo \$($t2) | xargs -d \";\" -n 1 -P $c bash -c '\$0'"

printf "%-11s %s \n" "TEST_URL" $TEST_URL
printf "%-11s %d \n" "TEST_COUTE" $n
printf "%-11s %d \n" "Concurrency" $c

if [ $DRY_RUN = "true" ]; then

    echo
    # 运行一次的命令
    echo 'once'
    echo $once | sed 's/,//g'

    echo
    # 完整的运行命令
    echo 'Concurrency'
    echo $t3 " | sed 's/,/\n/g'"
    # echo "bash -c "$(echo $b | sed 's/;/\n/g' | head -n 1 | sed 's/,//g');
    # echo "xargs -d \";\" -n 1 -P $c bash -c";
    # echo $b;

    exit 0
fi

c=$(eval $t3)

# echo $c | sed 's/,/\n/g'
# echo $c
# printf "\n"

# echo $c | sed 's/,/\n/g'
# printf "\n"

d=$(echo $c | sed 's/,$//')
# echo "$d"
# printf "\n"

# echo $c | sed 's/;$//' | awk

if [ $verbose = "true" ]; then
    echo $d | awk '
BEGIN {
    RS=",";
    FS="::";
    printf("\n");
    printf("%-6s %-9s  %-15s  %-12s  %-18s  %-10s  %-14s \n", " ", "http_code", "time_namelookup", "time_connect", "time_starttransfer", "time_total", "speed_download");
}
{
    printf("%-6d %-9d  %-15.6f  %-12.6f  %-18.6f  %-10.6f  %-14d \n", NR, $1, $2, $3, $4, $5, $6);
}
END {
    printf("\n");
}
'
fi

echo $d | awk '
function median(arr, count1) {
    return (count1 % 2 == 0) ? ( arr[count1 / 2] + arr[count1 / 2 + 1] ) / 2  : arr[int(count1 / 2) + 1];
}
function sdev(arr, count1) {
    for (i=1; i <= count1; i++) {
        sum1 += arr[i];
    }
    mean = sum1/count1;
    for (i=1; i <= count1; i++) {
        variance += (arr[i] - mean)^2;
    }
    variance = variance/count1;
    return sqrt(variance);
}
BEGIN {
    RS=",";
    FS="::";
}
{
    time_namelookup+=$2;
    time_namelookup_arr[NR] = $2;
    time_connect+=$3;
    time_connect_arr[NR] = $3;
    time_starttransfer+=$4;
    time_starttransfer_arr[NR] = $4;
    time_total+=$5;
    time_total_arr[NR] = $5;
    speed_download+=$6;
    speed_download_arr[NR] = $6;
}
END {
    if (NR == 0) exit;

    asort(time_namelookup_arr);
    asort(time_connect_arr);
    asort(time_starttransfer_arr);
    asort(time_total_arr);
    asort(speed_download_arr);

    printf("%20s %-12s  %-12s  %-12s  %-12s  %-12s  %-12s \n", " ", "total", "mean", "max", "min", "median", "sdev");
    printf("%-20s %-12.6f  %-12.6f  %-12.6f  %-12.6f  %-12.6f  %-12.6f \n", "time_namelookup", time_namelookup, time_namelookup/NR, time_namelookup_arr[NR], time_namelookup_arr[1], median(time_namelookup_arr, NR), sdev(time_namelookup_arr, NR));
    printf("%-20s %-12.6f  %-12.6f  %-12.6f  %-12.6f  %-12.6f  %-12.6f \n", "time_commect", time_commect, time_commect/NR, time_commect_arr[NR], time_commect_arr[1], median(time_commect_arr, NR), sdev(time_commect_arr, NR));
    printf("%-20s %-12.6f  %-12.6f  %-12.6f  %-12.6f  %-12.6f  %-12.6f \n", "time_starttransfer", time_starttransfer, time_starttransfer/NR, time_starttransfer_arr[NR], time_starttransfer_arr[1], median(time_starttransfer_arr, NR), sdev(time_starttransfer_arr, NR));
    printf("%-20s %-12.6f  %-12.6f  %-12.6f  %-12.6f  %-12.6f  %-12.6f \n", "time_total", time_total, time_total/NR, time_total_arr[NR], time_total_arr[1], median(time_total_arr, NR), sdev(time_total_arr, NR));
    printf("%-20s %-12d  %-12.3f  %-12d  %-12d  %-12d  %-12.3f \n", "speed_download", speed_download, speed_download/NR, speed_download_arr[NR], speed_download_arr[1], median(speed_download_arr, NR), sdev(speed_download_arr, NR));
}
'

这是上面那段脚本的运行结果 ./testwebsite.sh -c 2 -n 10 "https://www.baidu.com"

TEST_URL    https://www.baidu.com
TEST_COUTE  10
Concurrency 2
                     total         mean          max           min           median        sdev
time_namelookup      0.393789      0.003938      0.027595      0.003291      0.003560      0.002553     
time_commect         0.000000      0.000000      0.000000      0.000000      0.000000      0.003946
time_starttransfer   3.791344      0.037913      0.060255      0.028540      0.037378      0.006453
time_total           3.802466      0.038025      0.060337      0.028608      0.037543      0.042166
speed_download       6362500       63625.000     83228         39461         63420         7718.199

运行结果的解释

这样的脚本也没有考虑到重定向的情况

lighthouse

一个是浏览器自带的 lighthouse (基于 chromium 的浏览器才有)。 一个是谷歌在线的 lighthouse https://developers.google.com/speed/pagespeed/insights/ 。 还有一个 chromium 的插件,但实际上和开发者工具里的 lighthouse 是一样的。

评分一共有五部分

一般情况下,只需要关注这三部分即可,分数越高越好

一般情况下,如果低于这个分数就要优化

浏览器开发者工具

相关文档

请求列表里有一个 时间 的列,但这个时间一般会包含队列等待,dns解释,stl握手。

从后端的角度来看,大部分情况下只需要关注,请求详情里的这三个参数

  1. Request sent 请求第一个字节发出前到最后一个字节发出后的时间,也就是上传时间
  2. Waiting 请求发出后,到收到响应的第一个字节所花费的时间 (Time To First Byte)
  3. Content Download 收到响应的第一个字节,到接受完最后一个字节的时间,就是下载时间

其它

其它工具

查找问题

curl -i -v -L www.baidu.com
# -i 输出 http 头
# -v 输出通信的整个过程,用于调试
# -L 让 HTTP 请求跟随服务器的重定向。curl 默认不跟随重定向。
# dns 查询
nslookup www.baidu.com
# dns 反查
nslookup -qt=ptr 14.215.177.38
ping www.baidu.com
telnet 127.0.0.1 80
# 如果端口没有开放,会提示连接失败
curl --no-buffer --connect-timeout 30 -i -v telnet://127.0.0.1:80
# 如果输出 connected 表示端口有开启
# linux 用 traceroute
traceroute www.baidu.com
# windows 用 tracert
tracert www.baidu.com

网站性能的一些准则