仓库源文站点原文

观察网站性能

ab

ab (apache benchmark)

一般的使用命令

输出的解释

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 的源码,各种数值的计算方法可以在这里找到 https://github.com/apache/httpd/blob/2.4.62/support/ab.c#L1022 -->

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

ab 一般会跟随 apache httpd 一起安装,但也有单独的安装命令。 ab 单独的安装命令

apt install apache2-utils
或
yum install httpd-tools

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

运行结果的解释

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

<!-- 后续把 ab 的那些输出项也加上去,允许加上其他curl参数,再加个调试模式 a=20;echo $(for i in $(seq 1 $a); do echo "curl -o /dev/null -s -w %{http_code}::%{time_namelookup}::%{time_connect}::%{time_starttransfer}::%{time_total}::%{speed_download}; www.baidu.com ";echo ","; done) | xargs -d "," -n 1 -P 4 bash -c '$0' | sed 's/,/\n/g' -->

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 收到响应的第一个字节,到接受完最后一个字节的时间,就是下载时间

其它

其它工具

查找问题

网站性能的一些准则

<!-- ## 浏览器开发者工具里的 network `经过` 就是一个请求消耗的时间 在谷歌判断网站是否有收录 site: domain.com 谷歌的网站测速,可以看到优化建议 https://developers.google.com/speed/pagespeed/insights 是否适合移动设备浏览 https://search.google.com/test/mobile-friendly 检查 html https://validator.w3.org/nu/ 检查 css http://jigsaw.w3.org/css-validator/ 检查 https https://myssl.com/ssl.html https://myssl.com/ats.html Web 辅助功能评估工具 https://wave.webaim.org/ 另一个网页评价工具 https://www.webpagetest.org/ 查看 网站速度,查看 cdn 速度 http://tool.chinaz.com/speedworld/ http://cdn.chinaz.com/ http://tool.chinaz.com/sitespeed http://report.chinaz.com/tool.chinaz.com 您的网站是否已被 Google 搜索引擎收录? 可在 Google 搜索引擎中对您的网站的首页网址执行“site:”搜索。 如果您能看到结果,则表明您的网站在索引中。如“site:wikipedia.org”。 site:www.my-domain.com/subdirectory/ 只显示/subdirectory/目录下的页面 site:www.my-domain.com 词语 只显示页面中包含这个词语的关键词 site:www.my-domain.com inurl:词语 只显示URL中包含这个短语的 site:www.my-domain.com intitel:词语 只显示标题中包含这个短语的 site:www.my-domain.com filetype:pdf 只显示被收录的pdf文件 2h4g2m 就够用了 在互联网上对于用户响应时间,有一个普遍的标准。2/5/10秒原则。 也就是说,在2秒之内给客户响应被用户认为是“非常有吸引力”的用户体验。在5秒之内响应客户被认为“比较不错”的用户体验,在10秒内给用户响应被认为“糟糕”的用户体验。如果超过10秒还没有得到响应,那么大多用户会认为这次请求是失败的。 -->