发送一个NMI unknown 事件给OS
在ec2触发linux的crash发送一个诊断请求给EC2, 触发os本身NMI Unknown事件,这个时间会触发Kdump记录当时的现场。
aws ec2 send-diagnostic-interrupt --region cn-north-1 --instance-id i-********************
记录下来的现场文件保存在 /var/crash/
[root@mysql 5.14.0-284.11.1.el9_2.x86_64]# ll /var/crash/
total 0
drwxr-xr-x. 2 root root 67 Jun 6 05:22 127.0.0.1-2023-06-06-05:22:11
drwxr-xr-x. 2 root root 67 Jun 6 08:58 127.0.0.1-2023-06-06-08:58:20
drwxr-xr-x. 2 root root 67 Jun 9 09:39 127.0.0.1-2023-06-09-09:39:56
使用Crash命令进行分析, 需要安装kernel-debug 和 kernel-debuginfo kernel-devel
[root@mysql 5.14.0-284.11.1.el9_2.x86_64]# crash /usr/lib/debug/lib/modules/5.14.0-284.11.1.el9_2.x86_64/vmlinux /var/crash/127.0.0.1-2023-06-09-09\:39\:56/vmcore
相关文档:
New – Trigger a Kernel Panic to Diagnose Unresponsive EC2 Instances
发送诊断中断(适用于高级用户)
主要的问题是怎么解读这个结果, 目前我的理解是 找大佬。
Cscope 查看内核源代码# 下载源代码
yum install -y yum-utils
yum
yum download --source kernel
# 解压代码包
rpm2cpio ./kernel-5.14.0-284.11.1.el9_2.src.rpm | cpio -div
tar xf ./linux-5.14.0-284.11.1.el9_2.tar.xz
# 使用命令查看源代码
make cscope ARCH=x86
# 读取并标记tag
make tags ARCH=x86
# 查看
cscope -d
Dracut的使用和命令# 添加驱动程序到ramfs
]$ dracut -fv --add-drivers "nvme ena" /boot/initramfs-$(uname -r).img $(uname -r)
# 查看是否有模块在ramfs中
]$ lsinitrd /boot/initramfs-$(uname -r).img | grep -E "nvme|ena"
安全软件引起的用户空间进程失去响应:Redhat关于这个问题的文档说明:
https://access.redhat.com/solutions/5201171
https://access.redhat.com/solutions/2838901
使用Ftrace的方法,和一部分命令的使用方法:
[root@ip-172-31-51-167 ~]$ echo 'func fanotify_get_response +p' > /sys/kernel/debug/dynamic_debug/control
追踪这个系统调用, 并输出 callgraph.内核的DynamicTracing, 这是一个古老的方式了, 出现在Kprobe之前。会直接将追踪的结果输出到dmesg中。
[root@ip-172-31-51-167 ~]$ perf trace -s -p 2688
[root@ip-172-31-51-167 ~]$ cd /var/crash/127.0.0.1-2023-08-11-06:53:10
[root@ip-172-31-51-167 ~]$ crash /usr/lib/debug/lib/modules/6.1.34-59.116.amzn2023.x86_64/vmlinux vmcore
[root@ip-172-31-51-167 127.0.0.1-2023-08-11-06:53:10]$ ll /var/crash
total 0
drwxr-xr-x. 2 root root 67 Aug 10 05:52 127.0.0.1-2023-08-10-05:52:16
drwxr-xr-x. 2 root root 67 Aug 10 06:13 127.0.0.1-2023-08-10-06:13:44
drwxr-xr-x. 2 root root 67 Aug 11 05:45 127.0.0.1-2023-08-10-13:03:27
drwxr-xr-x. 2 root root 91 Aug 12 15:05 127.0.0.1-2023-08-11-04:57:13
drwxr-xr-x. 2 root root 67 Aug 11 08:41 127.0.0.1-2023-08-11-06:53:10
drwxr-xr-x. 2 root root 67 Aug 11 20:56 badstop
drwxr-xr-x. 2 root root 41 Aug 11 20:46 crash
Grubby 命令简单的用法设置内核参数:
# 查看所有内核的参数
$ grubby --info=ALL
# 设置默认的启动内核
$ grubby --set-default-index=1
# 查看当前的默认启动内核
$ grubby --default-kernel
# 移除所有内核的参数
$ grubby --update-kernel=ALL --remove-args="systemd.log_level=debug systemd.log_target=kmsg log_buf_len=1M loglevel=8 crashkernel=512M"
# 更新所有内核的参数
$ grubby --update-kernel=ALL --args="systemd.log_level=debug systemd.log_target=kmsg log_buf_len=1M loglevel=8 crashkernel=512M"
# 为特定的内核添加参数。
$ grubby --update-kernel=/boot/vmlinuz-5.9.1-1.el8.elrepo.x86_64 --args=“systemd.log_level=debug systemd.log_target=kmsg log_buf_len=1M loglevel=8 crashkernel=512M”
[root@ip-172-31-0-170 ~]# sudo kdumpctl status
kdump: Kdump is operational
[root@ip-172-31-0-170 ~]# sudo kdumpctl showmem
kdump: Reserved 256MB memory for crash kernel
[root@ip-172-31-0-170 ~]# cat /proc/cmdline
BOOT_IMAGE=(hd0,gpt1)/boot/vmlinuz-6.1.34-59.116.amzn2023.x86_64 root=UUID=483d7075-a0f8-4ba8-a951-a668fa079cac ro console=tty0 console=ttyS0,115200n8 nvme_core.io_timeout=42949672
95 rd.emergency=poweroff rd.shell=0 selinux=1 security=selinux quiet systemd.log_level=debug systemd.log_target=kmsg log_buf_len=1M loglevel=8 crashkernel=512M
快速启动一个 prometheus 和 grafana
快速创建一个可用的 prometheus 和 grafana 进行测试, 并将数据保留在当前的目录中, 在重启之后数据不会丢失:
创建一个目录.
mkdir /opt/monitor
mkdir /opt/monitor/grafana
mkdir /opt/monitor/grafana_data
mkdir /opt/monitor/prometheus
mkdir /opt/monitor/prometheus_data
touch /opt/monitor/docker-compose.yaml
创建docker-compose 文件
---
version: "3"
services:
prometheus:
container_name: prometheus
image: reg.liarlee.site/docker.io/prom/prometheus:latest
restart: always
network_mode: host
environment:
- TZ=Asia/Shanghai
volumes:
# - /opt/monitor/prometheus/prometheus.yaml:/etc/prometheus/prometheus.yml
- /opt/monitor/prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
- '--storage.tsdb.retention.time=90d'
grafana:
container_name: grafana
image: reg.liarlee.site/docker.io/grafana/grafana-oss:main-ubuntu
restart: always
network_mode: host
environment:
- TZ=Asia/Shanghai
volumes:
- /opt/monitor/grafana_data:/var/lib/grafana
- /opt/monitor/grafana/datasource:/etc/grafana/provisioning/datasources
# - /opt/monitor/grafana/grafana.ini:/etc/grafana/grafana.ini
- /etc/localtime:/etc/localtime:ro
user: '472'
准备基础配置文件
docker compose up -d
docker cp grafana:/etc/grafana/grafana.ini /opt/monitor/grafana/grafana.ini
docker cp prometheus:/etc/prometheus/prometheus.yml /opt/monitor/prometheus/prometheus.yaml
chown -R 472:472 /opt/monitor/grafana_data
chown -R 472:472 /opt/monitor/grafana
chown -R nobody:nobody /opt/monitor/prometheus_data
chown -R nobody:nobody /opt/monitor/prometheus
docker compose down --remove-orphans
准备prometheus 作为默认的Datasource
touch /opt/monitor/grafana/datasource/datasource.yml
---
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://localhost:9090
isDefault: true
access: proxy
editable: true
修改配置文件中需要的参数, 取消配置文件中的注释, 然后重启即可。
docker compose down --remove-orphans && docker compose up -d
数据库单表的测试
基于这个问题的测试为什么MySQL单表不要超过2000w行?
测试过程:CREATE TABLE test(
id int NOT NULL AUTO_INCREMENT PRIMARY KEY comment '主键',
person_id int not null comment '用户id',
person_name VARCHAR(200) comment '用户名称',
gmt_create datetime comment '创建时间',
gmt_modified datetime comment '修改时间'
) comment '人员信息表';
插入数据:
insert into test values(1,1,'user_1', NOW(), now());
insert into test (person_id, person_name, gmt_create, gmt_modified)
select (@i:=@i+1) as rownum, person_name, now(), now() from test, (select @i:=100) as init;
set @i=1;
//测试 SQL,记录他们的运行时间
select count(*) from test;
select count(*) from test where id=XXX;
查看这个表格的数据量大小:
show table status like 'test'\G
200w行表:
mysql> describe table test;
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------+
| 1 | SIMPLE | test | NULL | ALL | NULL | NULL | NULL | NULL | 2092640 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------+
1 row in set, 1 warning (0.00 sec)
MySQL [test]> select count(*) from test;
1 row in set (0.045 sec)
1 row in set (0.050 sec)
1 row in set (0.050 sec)
400w:
mysql> describe table test;
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------+
| 1 | SIMPLE | test | NULL | ALL | NULL | NULL | NULL | NULL | 4185280 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------+
1 row in set, 1 warning (0.00 sec)
MySQL [test]> select count(*) from test;
1 row in set (0.126 sec)
1 row in set (0.120 sec)
1 row in set (0.119 sec)
800w:
mysql> describe table test;
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------+
| 1 | SIMPLE | test | NULL | ALL | NULL | NULL | NULL | NULL | 8370120 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------+
1 row in set, 1 warning (0.00 sec)
MySQL [test]> select count(*) from test;
1 row in set (0.266 sec)
1 row in set (0.266 sec)
1 row in set (0.253 sec)
1600w:
mysql> describe table test;
+----+-------------+-------+------------+------+---------------+------+---------+------+----------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+----------+----------+-------+
| 1 | SIMPLE | test | NULL | ALL | NULL | NULL | NULL | NULL | 16337090 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+----------+----------+-------+
1 row in set, 1 warning (0.00 sec)
MySQL [test]> select count(*) from test;
1 row in set (0.544 sec)
1 row in set (0.524 sec)
1 row in set (0.523 sec)
3200w:
mysql> describe table test;
+----+-------------+-------+------------+------+---------------+------+---------+------+----------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+----------+----------+-------+
| 1 | SIMPLE | test | NULL | ALL | NULL | NULL | NULL | NULL | 32665301 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+----------+----------+-------+
1 row in set, 1 warning (0.00 sec)
MySQL [test]> select count(*) from test;
1 row in set (1.068 sec)
1 row in set (1.057 sec)
1 row in set (1.044 sec)
这个结果基本上都是线性的, 感觉数据量实在是太小了。
mysql> show table status like 'test'\G
*************************** 1. row ***************************
Name: test
Engine: InnoDB
Version: 10
Row_format: Dynamic
Rows: 4182365
Avg_row_length: 48
Data_length: 202063872
Max_data_length: 0
...
Network 相关知识不知道放那儿
Origin Version:https://datatracker.ietf.org/doc/html/rfc1180
Chinese Version:http://arthurchiao.art/blog/rfc1180-a-tcp-ip-tutorial-zh/
Tuning initcwnd for optimum Performance:
https://www.cdnplanet.com/blog/tune-tcp-initcwnd-for-optimum-performance/
https://www.kawabangga.com/posts/5217
Other Linux Network Stack Explaination:https://www.clockblog.life/article/2023/7/4/44.html
Linux内核网络https://www.clockblog.life/article/2023/7/4/44.html
https://blogs.runsunway.com/
buffer/cache 无法释放
问题看到了一个案例, 这个案例的问题是: 为什么我的buffer/cache在echo 3 之后, 还是不能回收, 内存的占用很大。
命令如下:
root@ip-172-31-47-174 ~# free -h
total used free shared buff/cache available
Mem: 7.5Gi 1.3Gi 3.5Gi 2.1Gi 2.6Gi 3.8Gi
Swap: 0B 0B 0B
root@ip-172-31-47-174 ~# echo 3 > /proc/sys/vm/drop_caches
root@ip-172-31-47-174 ~# free -h
total used free shared buff/cache available
Mem: 7.5Gi 1.3Gi 3.7Gi 2.1Gi 2.4Gi 3.9Gi
Swap: 0B 0B 0B
root@ip-172-31-47-174 ~# free
total used free shared buff/cache available
Mem: 7833520 1376608 3917368 2160524 2539544 4059944
Swap: 0 0 0
分析和答案分析: 开始的时候我并没有发现具体有什么问题, 认为是应用程序确实无法回收cache的空间,因为正在使用。
例如Firefox启动的时候就会使用Cache的空间来进行数据的存储。
答案: 第二天看到了大佬的更新, 这个问题的原因是: 内核会将 shared 的空间, 一并统计在 buffer/cache 中, 所以free命令的输出是正常的, 实际cache已经释放了一部分,没有大幅度变化的原因是因为那部分是 shmem的空间, 所以。。。释放不掉。
初见这个结论是有些震惊的, 我一直都认为 shared 字段里面统计的内存是独立的, 仔细看看上面的命令, 确实 shared空间基本上与 buffer/cache的空间是差不多的。
测试一部分信息:
OS: Arch Linux x86_64
Kernel: 6.3.1-arch2-1
Software Version: free from procps-ng 3.3.17
测试的方法, 我只是尝试证明free命令的统计方式变化, 所以直接简化了, 直接扩大ShareMemory。
# 直接创建一个临时的目录
# 其实直接使用 /dev/shm 也行, 但是可用空间会被限制到 物理内存的一半。
sudo mkdir /mnt/tmpfs/
# 挂载到 /mnt/tmpfs/
sudo mount -t tmpfs -o size=5000m shared /mnt/tmpfs/
# 创建一个文件占用那个部分的内存。
sudo fallocate -l 4G /mnt/tmpfs/file
按照上面的步骤测试, 可以发现 share memory 确实也同时被统计在了 buffer/cache 里面, 与客户的现象完全一致。这个命令确实就是这样工作的。
按照这个思路应该看看meminfo 以及 内核的文档,Mark一下准备开始走一遍这个思路。
立刻查看free命令的manpage, 发现果然没有更新, 说明摘要:
sharedMemory used (mostly) by tmpfs (Shmem in /proc/meminfo)
buffersMemory used by kernel buffers (Buffers in /proc/meminfo)
cacheMemory used by the page cache and slabs (Cached and SReclaimable in /proc/meminfo)
buff/cacheSum of buffers and cache
看 /proc/meminfo , 发现确实是取值取到了 shmem, 这个值是对的, 现在的问题就是为什么内核提供了这样的一个值。
sudo cat /proc/meminfo | grep -Ei "mem|cache|buffer|active"
MemTotal: 7833520 kB
MemFree: 4417692 kB
MemAvailable: 4615060 kB
Buffers: 0 kB
Cached: 2550368 kB # 这个的统计就。。。。 free是对的
SwapCached: 0 kB
Active: 830560 kB
Inactive: 2397160 kB
Active(anon): 444220 kB
Inactive(anon): 2385016 kB
Active(file): 386340 kB
Inactive(file): 12144 kB
Shmem: 2151884 kB # 这个是创建的文件大小,转换成文件的大小, 差不多是 2G 左右。
现在的问题变成, 具体是什么时候 meminfo里面的值变更了统计方式呢? 为什么这样统计呢?
不会找具体是什么时候commit的变更, 直接看代码吧。 希望我看的是对的。
// https://elixir.bootlin.com/linux/latest/source/fs/proc/meminfo.c
static int meminfo_proc_show(struct seq_file *m, void *v)
{
struct sysinfo i;
...
cached = global_node_page_state(NR_FILE_PAGES) -
total_swapcache_pages() - i.bufferram;
if (cached < 0)
cached = 0
...
show_val_kb(m, "MemTotal: ", i.totalram);
show_val_kb(m, "MemFree: ", i.freeram);
show_val_kb(m, "MemAvailable: ", available);
show_val_kb(m, "Buffers: ", i.bufferram);
show_val_kb(m, "Cached: ", cached);
show_val_kb(m, "SwapCached: ", total_swapcache_pages());
show_val_kb(m, "Active: ", pages[LRU_ACTIVE_ANON] +
pages[LRU_ACTIVE_FILE]);
show_val_kb(m, "Inactive: ", pages[LRU_INACTIVE_ANON] +
pages[LRU_INACTIVE_FILE]);
show_val_kb(m, "Active(anon): ", pages[LRU_ACTIVE_ANON]);
show_val_kb(m, "Inactive(anon): ", pages[LRU_INACTIVE_ANON]);
show_val_kb(m, "Active(file): ", pages[LRU_ACTIVE_FILE]);
show_val_kb(m, "Inactive(file): ", pages[LRU_INACTIVE_FILE]);
show_val_kb(m, "Unevictable: ", pages[LRU_UNEVICTABLE]);
show_val_kb(m, "Mlocked: ", global_zone_page_state(NR_MLOCK));
可以看到从一个sysinfo 的 struct 取出, 然后进行计算, 存储在cached 变量里面,然后输出:
// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/sysinfo.h#L14
__kernel_ulong_t bufferram; /* Memory used by buffers */
好了 我看不动了,Cached 的最后结果: os所有可用的文件页面 - SwapCached的数值 - Buffers的数值,大概应该是这个意思, 按照这么算的话, 确实会加入 shmem 的部分,邮件归档以及解释
That’s a reasonable position to take.
Another point of view is that everything in tmpfs is part of the page cache and can be written out to swap, so keeping it as part of Cached is not misleading.
I can see it both ways, and personally, I’d lean towards clarifyingthe documentation about how shmem is accounted rather than changing how the memory usage is reported.
这是上面的邮件链接中的一部分, 解释了为什么将 shmem 计算到 Cached 中的原因,看起来现在应该由一个新的指标数值来处理这个了。
更多的部分看参考链接吧,我顺便看了讲解,大佬讲的清楚。
参考链接https://zhuanlan.zhihu.com/p/586107891
https://www.cnblogs.com/tsecer/p/16290025.html
RDS QPS 下降引发的网络流控分析记录
Topic 1 现象看到一个朋友的问题, 由 RDS QPS 下降引发的网络问题分析:
引用原文的问题描述:
这个问题一开始是在进行RDS实验的时候发现的。最初的情景是,多台机器同时对数据库进行select和insert操作时,会发现insert操作会造成select操作qps大幅下降,且insert操作结束之后select操作的qps仍不能回升。起初以为是RDS的问题,但是在复现问题、监控RDS之后发现RDS的压力其实很小。于是开始怀疑是网络的问题,在简化了场景和操作之后,发现能在过去做tcp实验的机器上复现,于是用这个更简单的场景进行问题复现和分析。
正确答案以及复盘: https://yishenggong.com/2023/05/06/why-does-my-network-speed-drop-cn/
感谢大佬@plantegg 提供的这个案例和知识星球, tcp协议和 os 网络系统的分析我之前真是一句都说不出来, 这次确实完整的走了一遍网络的部分。
下面是我的一些思路和资料整理:
看完了问题的描述, 我基本上可以给出一个经验的判断,这个问题是aws t2实例的流控, 因为表现和之前的测试非常一致, 为了确认这个问题我又进行了相关的测试,基准信息如下:
实例类型:t2.micro
实例的基准带宽: 64Mbps
实例的突增带宽: 1Gbps
实例的EBS规格: 3000 IOPS, 100GB根卷存储, 125MiB/s
实例的OS : AmazonLinux2023.
Congestion: Cubic
具体的测试方法:
启动4个实例 t2.micro, hostname 分别是 nginx \ client1 \ client2 , 方便测试和区分实例; 启动一个额外的实例Client3 来尝试验证是不是流控。
nginx上面启动一个Nginx server, 发布一个 2GB 大小的文件; Client 1、 Client2 两个实例上面使用curl命令下载。
尝试保持这个流量一段时间之后, 带宽会被限制到64 Mbps。
由于是流控的问题, 所以现象会稳定出现。
Summary:
我理解这个应该是一个对tcp流的限制, 所以验证这个问题的方法是启动一个iperf的服务并重新生成流量, 如果是流控的话, 那么新的流量应该不受限制, 简单的证明方式就是在Nginx Server上面启动一个Iperf3 的 Server , 然后在发生降低速度的时刻, 使用 Client3 iperf 测试 Nginx Server, 这个时候观察到的现象是两个原有网络链接的速度还是很低, 但是iperf3 的测试网络吞吐量是比较高的。
t系列的实例都是突增类型, 可以允许一定的时间使用超过实例基准性能的资源, 在网络流量超过基准性能一段时间之后, 实例的带宽会被限制。 之前真的一直都没有思考过实例的带宽限制都是怎么做的, 让我在描述更多的细节我也确实无法提供, 借着这个机会,深入研究一下。
重新开始分析这个问题:
分析网络抓包看看,被限速的是Server的实例, 所以直接看Server的抓包.
贴一个我自己抓包截图,两个不同的流:
Server to Client 1:
Server to Client 2:
对与上面的两个截图来看, 斜率表越大,下载速度越快, 在斜率较大的前半段, 并未发生任何的TCP异常; 后半段斜率变小的期间,一直有规律的红色标记, 那个标记对应到抓包的结果中,是快速重传的数据包, 并且发生快速重传的时间比较规律, 这类规律的快速重传影响了传输的速度。
查看RTT的变化, 截图如下:
Server to Client 1:
Server to Client 2:
上面的这个部分可以看出整体增大了RTT ,基于之前的tc流控测试,rtt增大的链路上, 发送和接收的窗口需要相应的增大, 才能保证带宽的利用率较高。 按照实验更增加 wmem 和 rmem, 三个实例的设置都增加, 增加之后的速度并没有改变, 依旧还是被限制的水平。RTT从抓包的结果上面来看就是有明显的变化。
查看Server 实例的CWND, 这个CWND的记录直接使用命令行看:
左侧是Server上面的命令行执行结果 命令是 ss -4tni , 命令输出的结果中, 有当前的TCP链接的 RTT, CWND的值, 可以观察到, 到两个Server的链接中RTT的值 ~60ms 左右, 我使用了watch 0.1s来执行ss命令和观察, 重点关注上面的几个参数, 可以发现这几个数值的范围如下:
RTT ~ 50ms ~ 70ms 之间变化的频率基本上与cwnd的变化频率一致。
CWND 40 - 64 之间变化, 到达64之后回落到40左右, 然后快速提高到64再回落。
引用一个描述比较清晰的中文文章,其中也介绍了常见的几种拥塞控制算法的模式, 帮我大概理解了下网络的部分:
TCP 连接建立后先经过 Slow Start 阶段,每收到一个 ACK,CWND 翻倍,数据发送率以指数形式增长,等出现丢包,或达到 ssthresh,或到达接收方 RWND 限制后进入 Congestion Avoidance 阶段。1
从另一个文章中找到了如下的一个公式:
因此想要充分利用带宽,必须让窗口大小接近 BDP 的大小,才能确保最大吞吐量。
我们通过如下的例子来讨论一下,究竟 rwnd 和 cwnd 的值与理论最大使用带宽的关系是什么?
min(cwnd, rwnd) = 16 KB
RTT = 100 ms
16 KB = 16 X 1024 X 8 = 131072 bits
131072 / 0.1 = 1310720 bits/s
1310720 bits/s = 1310720 / 1000000 = 1.31 Mbps
因此,无论发送端和接收端的实际带宽为多大,当窗口大小为 16 KB 时,传输速率最大只能为 1.31 Mbps 。2
可以看到上面的公式中的第一个, 在计算的时候会在cwnd, rwnd 两个指标中选取最小的生效, 那么基于上面的变动, 我们已经把wmem , rmem 都改到了较大的值, 那么这个时候较小 的就是 cwnd 了, 这个值增加不上去就无法充分的利用带宽。
那么现在的问题变成了为什么 cwnd 无法继续增长?
Cubic 在 BIC 的基础上,一个是通过三次函数去平滑 CWND 增长曲线,让它在接近上一个 CWND 最大值时保持的时间更长一些,再有就是让 CWND 的增长和 RTT 长短无关,即不是每次 ACK 后就去增大 CWND,而是让 CWND 增长的三次函数跟时间相关,不管 RTT 多大,一定时间后 CWND 一定增长到某个值,从而让网络更公平,RTT 小的连接不能挤占 RTT 大的连接的资源。1
如果cwnd的大小在拥塞避免之后是基于时间来进行增长的,那么就可以结合上面我观察到的现象, 基本上过一段时间就会出现一定量的快速重传(抓包结果), 周期性的RTT 与 cwnd的变化(命令输出结果)比较吻合了。
基于上面的命令输出结果可以认为确实是通过RTT的增加 + 丢包控制 CWND, 压着CWND的值上不去的原因就是快速重传, 让cwnd随时间增大之后快速重传, 然后触发拥塞避免, 回退到较低的值, 然后开始循环。
感谢Shuo Chen 和 Hao Chen 大佬的测试分析思路以及工具,反复的理解这个Thread里面的说法, 现在基本上可以抓住这个问题的逻辑了。
走着一圈之后, 写完了上面自己的总结, 现在复制大佬的答案:
@bnu_chenshuo 从发生 retransmit 的间隔看,太规律了。我现在怀疑是 aws 有意限速,通过非常巧妙的少量丢包或乱序(图一中粗线下挂的小黑点),并控制 RTT,让 Linux TCP sender 的 cwnd 钳制在几十 KB,RTT 在 10ms,进而限制吞吐量在几千 KB/s。
我在 t3.micro 上用 FreeBSD 13 复现。观察到 AWS 先用对付 Linux 的办法:故意丢包乱序 + RTT=10ms,发现不灵之后,恼羞成怒,包也不丢了,直接卡脖子。总之就是不让你白嫖网速。对 Linux 是智斗,每秒钟丢你一两个包,RTT=10ms,让你自己cwnd小、速度上不去,你也不好说啥。对 FreeBSD 就上武力了。
???对于测试的结果, 我这边确实不同,我读取到的RTT 60ms 明显要比大佬测试的时候的RTT 10ms 更高. 我把这个理解为 中国区AWS 与 Global的差异, 这个问题也许还能继续分析?
调整 rmem , wmem 的大小到 MTU * CWND = 9001 * 60 = 540600 ,这个数值x2 设置到发送和接收的窗口, 重新抓包, 理论上应该看不到快速重传了。并且拥塞窗口应该是稳定在 60 左右。
测试的过程中观察, 设置 rmem wmem 最终的值为 1000000, ss命令中的 CWND稳定在 57 , 抓包的结果中没有快速重传, RTT也比较稳定了, 不再有跳跃和突然增加到 100+ 的RTT, 速度还是被限制了, 这时候较小的是 RWND, 所以也是为什么 CWND 可以比较稳定不再变化的原因。
Topic 2 如何使用wireshark查看丢包率?如何查看实例在限速状态的 丢包率 和 重传率?
在客户端的抓包结果里面, 使用字段 tcp.stream eq 0 and tcp.analysis.fast_retransmission 调整到对应的 tcp stream, 然后在Conversion
s窗口中统计的 Percent Filter 里面的百分比就是重传率, 丢包应该是 : tcp.analysis.lost_segment
或者就直接使用IO Graphs, 然后可以看到下面的图像,可以看到这个丢包比较小, 所以我在Y轴设置 100 的 Factor, 这样看起来清楚些。
如图:
基于这个抓包结果里面的基本上是乱序和快速重传,Server是发送数据的源头, 客户端没有收到认为这个是丢包。
那么感觉丢包率看Client的抓包结果应该就可以。 查看重传率 从Server这测的抓包结果来看。
Topic 3 RTT的变长是不是流控可以设计的?这个rtt确实无法区分到底是底层的流控带来的, 或者是设计故意拖长的,更新额外测试的结果:
将三个实例完全换成 c5.large, 后续没有RTT非常高的的问题了, 如图:
尝试处理掉CPU 的 IOwait, 也就是将这个文件缩小到 500MB ,之后nginx的数据都从内存取出, 发现RTT增加到了120ms, 维持了较长的时间之后还会回到60ms, 不知道这个原因是什么, 但是看起来RTT不稳定可能确实和os本身没什么关系。
试试其他规格的实例: 尝试在c5的实例上面直接使用tc卡住网桥设备的带宽, 来模拟链路中的网络设备? 观察rtt看看是否有任何的变化。
测试的方法使用docker-compose启动两个pod, 文件如下:
---
version: "3.8"
services:
pod1:
image: 111.dkr.ecr.cn-north-1.amazonaws.com.cn/haydenarchlinux:latest
restart: always
pod2:
image: 111.dkr.ecr.cn-north-1.amazonaws.com.cn/haydenarchlinux:latest
restart: always
在docker容器里面放入 Shuo Chen 大佬的tcpper ...
将ArchLinux作为节点加入EKS UnmanagedNode
添加一个自管理的节点添加这个自管理的节点是直接添加进入集群的, 并未使用EKS节点组的概念, 所以这个节点是可以被重启, 或者健康检查失败的, 并不具有任何的扩展或者弹性管理的能力。
创建集群,启动一个新的 EC2, 登录到已经启动的 EKS 优化 OS 内,准备复制一些脚本过来。
添加EC2的标签: kubernetes.io/cluster/clusterName owned
配置EC2的Instance Profile
控制台获取 Kubernetes APIServer的Endpoint URL
获取 apiserver b64 CA : cat ~/.kube/config 这个文件里面可以找到 ,或者是通过EKS的控制台上面, 找到 Base64 的 CA。
编辑 userdata, 或者 ssh 登录到ec2上面创建一个bash脚本用来调用 bootstrap.sh
mkdir ~/eks; touch ~/eks/start.sh
---
#!/bin/bash
set -ex
B64_CLUSTER_CA=
API_SERVER_URL=
K8S_CLUSTER_DNS_IP=10.100.0.10
/etc/eks/bootstrap.sh ${ClusterName} --b64-cluster-ca ${B64_CLUSTER_CA} --apiserver-endpoint ${API_SERVER_URL}
集群里面没有节点组 , 也不会创建aws-auth configmap 所以节点无法正常的加入集群, 需要手动创建。
apiVersion: v1
data:
mapRoles: |
- groups:
- system:bootstrappers
- system:nodes
rolearn: [CLUSTER_ROLE]
username: system:node:{{EC2PrivateDNSName}}
mapUsers: |
[]
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
需要复制的文件sudo pacman -S containerd # 安装Containerd
scp /etc/eks/bootstrap.sh root@54.222.253.235:/etc/eks/bootstrap.sh # 复制bootstrap
scp /usr/bin/imds root@54.222.253.235:/usr/bin/imds # shell 脚本, 用来帮忙调用ec2 metadata 获取实例和VPC子网的信息
scp -pr /etc/eks/ root@54.222.253.235:/etc/eks/ # 直接复制了eks的相关脚本和配置模板
scp -pr /var/lib/kubelet/kubeconfig root@54.222.253.235:/var/lib/kubelet/kubeconfig # 复制kubeletconfig配置文件模板
scp -pr /etc/kubernetes/ root@54.222.253.235:/etc/kubernetes/ # 复制 kubernetes 的配置文件
scp -pr /etc/kubernetes/kubelet/ root@54.222.253.235:/etc/kubernetes/kubelet/ # 上面的命令没有递归复制, 所以需要指定
# 设置对应的内核参数, 如果不做kubelet 会报错提示 这些参数不符合要求。
kernel.panic = 10
kernel.panic_on_oops = 1
vm.overcommit_memory = 1
Bootstrap 脚本内容分析记录一下脚本自动配置的内容, 大概就是 获取变量, aws的服务地址, ec2 元数据地址, 替换模板中的变量生成Kubelet配置文件 和 Containerd 的配置文件(这个替换是一次性的, 也就是说, bootstrap只能变更模板中的变量一次, 第二次执行只会生成刷新一次集群的信息, 以及重启服务)。
读取bootstrap后面给出的参数,设置变量, 例如: ClusterName etc.
查看Kubelet的版本, 决定Runtime, containerd | dockerd, 判断条件是 kubelet 版本 大于 1.24++ kubelet --version
++ grep -Eo '[0-9]\.[0-9]+\.[0-9]+'
+ KUBELET_VERSION=1.24.9
---
+ IS_124_OR_GREATER=true
+ DEFAULT_CONTAINER_RUNTIME=containerd
设置ECR以及Pause容器地址# 获取region以及aws service domain
+ AWS_DEFAULT_REGION=cn-north-1
+ AWS_SERVICES_DOMAIN=amazonaws.com.cn
# 调用脚本 /etc/eks/get-ecr-uri.sh cn-north-1 amazonaws.com.cn ''
+ ECR_URI=918309763551.dkr.ecr.cn-north-1.amazonaws.com.cn
+ PAUSE_CONTAINER_IMAGE=918309763551.dkr.ecr.cn-north-1.amazonaws.com.cn/eks/pause
+ PAUSE_CONTAINER=918309763551.dkr.ecr.cn-north-1.amazonaws.com.cn/eks/pause:3.5
+ CA_CERTIFICATE_DIRECTORY=/etc/kubernetes/pki
+ CA_CERTIFICATE_FILE_PATH=/etc/kubernetes/pki/ca.crt
创建证书目录:+ mkdir -p /etc/kubernetes/pki
+ sed -i s,MASTER_ENDPOINT,https://CE0253A94B6B14215AE3282580CFA5E3.yl4.cn-north-1.eks.amazonaws.com.cn,g /var/lib/kubelet/kubeconfig
+ sed -i s,AWS_REGION,cn-north-1,g /var/lib/kubelet/kubeconfig
+ sed -i s,CLUSTER_NAME,NewClusterForManualJoin,g /var/lib/kubelet/kubeconfig
获取 VPC CIDR# imds shell script help to get metadata.
imds: /usr/bin/imds
++ imds latest/meta-data/local-ipv4
++ imds latest/meta-data/network/interfaces/macs/02:66:06:2e:48:08/vpc-ipv4-cidr-blocks
创建kubelet 配置, 计算预留的资源 和 MaxPod 等等参数的数值。/etc/kubernetes/kubelet/kubelet-config.json
+ mkdir -p /etc/systemd/system/kubelet.service.d
+ sudo mkdir -p /etc/containerd
+ sudo mkdir -p /etc/cni/net.d
+ sudo mkdir -p /etc/systemd/system/containerd.service.d
创建containerd 配置文件+ printf '[Service]\nSlice=runtime.slice\n'
+ sudo tee /etc/systemd/system/containerd.service.d/00-runtime-slice.conf
+ sudo sed -i s,SANDBOX_IMAGE,918309763551.dkr.ecr.cn-north-1.amazonaws.com.cn/eks/pause:3.5,g /etc/eks/containerd/containerd-config.toml
kubelet配置和启动。+ sudo cp -v /etc/eks/containerd/kubelet-containerd.service /etc/systemd/system/kubelet.service
+ sudo chown root:root /etc/systemd/system/kubelet.service
+ sudo containerd config dump
+ systemctl enable kubelet
+ systemctl start kubelet
制作 NodeTemplate AMI
如果使用已经加入过集群的实例直接制作AMI, 节点会无法加入, kubelet报错是APIserver拒绝这个节点的加入。
2023-04-19T15:10:13Z kubelet-eks.daemon[11577]: E0419 15:10:13.325154 11577 event.go:267] Server rejected event '&v1.Event{TypeMeta:v1.TypeMeta{Kind:"", APIVersion:""}, ObjectMeta:v1.ObjectMeta{Name:"ip-10-0-1-249.cn-northwest-1.compute.internal.17575e9c08aefd8d", GenerateName:"", Namespace:"default", SelfLink:"", UID:"", ResourceVersion:"", Generation:0, CreationTimestamp:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), DeletionTimestamp:<nil>, DeletionGracePeriodSeconds:(*int64)(nil), Labels:map[string]string(nil), Annotations:map[string]string(nil), OwnerReferences:[]v1.OwnerReference(nil), Finalizers:[]string(nil), ZZZ_DeprecatedClusterName:"", ManagedFields:[]v1.ManagedFieldsEntry(nil)}, InvolvedObject:v1.ObjectReference{Kind:"Node", Namespace:"", Name:"ip-10-0-1-249.cn-northwes ...
Linux OS 网络流量控制测试
内核参数的说明对于 TCP 来说,会遇到如下的几个参数。
如果我们需要查看一下当前OS内与tcp相关的 KernelParam, 命令如下:
]$ sysctl -a | egrep "rmem|wmem|tcp_mem|adv_win|moderate"
其中主要需要关注的是:
net.ipv4.tcp_rmem = 4096 131072 6291456
net.ipv4.tcp_wmem = 4096 16384 4194304
这两个参数其实表示的是当前内核预留的 Socket Buffer, 单位是Bytes, 也是具体指 内存 的大小。具体的说明我找到 Kernel文档的说明如下:
tcp_rmem - vector of 3 INTEGERs: min, default, max
min: Minimal size of receive buffer used by TCP sockets.
It is guaranteed to each TCP socket, even under moderate memory
pressure.
Default: 4K
default: initial size of receive buffer used by TCP sockets.
This value overrides net.core.rmem_default used by other protocols.
Default: 87380 bytes. This value results in window of 65535 with
default setting of tcp_adv_win_scale and tcp_app_win:0 and a bit
less for default tcp_app_win. See below about these variables.
max: maximal size of receive buffer allowed for automatically
selected receiver buffers for TCP socket. This value does not override
net.core.rmem_max. Calling setsockopt() with SO_RCVBUF disables
automatic tuning of that socket's receive buffer size, in which
case this value is ignored.
Default: between 87380B and 6MB, depending on RAM size.
tcp_wmem - vector of 3 INTEGERs: min, default, max
min: Amount of memory reserved for send buffers for TCP sockets.
Each TCP socket has rights to use it due to fact of its birth.
Default: 4K
default: initial size of send buffer used by TCP sockets. This
value overrides net.core.wmem_default used by other protocols.
It is usually lower than net.core.wmem_default.
Default: 16K
max: Maximal amount of memory allowed for automatically tuned
send buffers for TCP sockets. This value does not override
net.core.wmem_max. Calling setsockopt() with SO_SNDBUF disables
automatic tuning of that socket's send buffer size, in which case
this value is ignored.
Default: between 64K and 4MB, depending on RAM size.
这文档中的说明,默认的三个值分别是: 最小, 默认, 最大。测试了一下, 如果只是需要实际调整的话, 调整那个最大值即可, 在高延迟的链路中, 调整默认值或者最大值就可以生效。 在测试的过程中, 将三个值都固定到预期,控制变量。
对于TCP协议的接收与发送两方, 各自有自己的 RecvBuffer 和 SendBuffer, 发送方会考虑链路上面可以承载的数据量(带宽), 以及 对方可以承载的数据量(rmem)。
实际上, 只是更新 max 的值, 并不会更新 Recvbuffer.如果想增大 receive buffer 的大小, 可以增加 tcp_rmem 的 default 的值大小.
测试环境两个 EC2 c5.2xlarge, 其中一个部署 Nginx , 并使用如下配置文件部分设置 , 发布一个 Fedora ISO, 大小大约 2G。另一个上面只是客户端, 使用的访问客户端是Curl。
http {
server {
autoindex on;
autoindex_localtime on;
}
}
}
这个配置文件中的其他部分就省略吧。
测试准备测试的过程其实涉及了四个部分的 延迟 问题:
发送方的应用程序性能。
发送方的发送缓冲区大小, SendBuffer
接收方的接收缓冲区大小, RecvBuffer
接收方的应用程序性能。
调整延迟和控制带宽 , 这两个部分控制的都是网络传输部分的性能,测试的过程中默认认为 发送 以及接收方的性能都完全没有问题。
基准两个机器的内核参数使用默认值, 先通过 tc 流量控制注入一些延迟, 查看并分析RTT对于传输速度的影响。
两个server之间默认的内核参数:
服务端参数
net.ipv4.tcp_rmem = 4096 131072 6291456
net.ipv4.tcp_wmem = 4096 16384 4194304
客户端参数
net.ipv4.tcp_wmem = 4096 131072 6291456
net.ipv4.tcp_rmem = 4096 16384 4194304
curl 的 测试命令 如下:(更新版本, 之前的测试版本少了一些参数
~]$ curl -o /dev/null -s -w "time_namelookup:%{time_namelookup}\ntime_connect: %{time_connect}\ntime_appconnect: %{time_appconnect}\ntime_redirect: %{time_redirect}\ntime_pretransfer: %{time_pretransfer}\ntime_starttransfer: %{time_starttransfer}\ntime_total: %{time_total}\n" http://nginx.liarlee.site/Fedora-Workstation-Live-x86_64-38_Beta-1.3.iso
如果使用默认的参数, 那么2G的 ISO 文件可以快速的传完, 这是两个实例的基准表现, 这也是创建了一个TCP Connection 的较好的性能表现, EC2 之间的带宽较大 10Gbps。
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0*
Trying 172.31.48.133:80...
100 1967M 100 1967M 0 0 1116M 0 0:00:01 0:00:01 --:--:-- 1116M
* Connection #0 to host nginx.liarlee.site left intact
time_connect: 0.005461
time_starttransfer: 0.005797
time_total: 1.762040
总体看起来使用了 2s 不到的时间, 就传输完成了2G的文件。
仅控制带宽在添加了带宽 控制, 带宽控制在 1000Mbps, 带宽监控结果:
Curl命令的结果:
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1967M 100 1967M 0 0 118M 0 0:00:16 0:00:16 --:--:-- 118M
time_connect: 0.002070
time_starttransfer: 0.002304
time_total: 16.633562
在仅仅控制带宽的前提下, 由于延迟忽略不计, 所以传输的数据量可以使用全部的带宽,这个场景下面传输的速度取决于网络带宽的大小, 网络带宽越大, 传输的数据量和速度都会相应的增加。
控制带宽+延迟给服务端添加一个 50ms 的 延迟, 使用 tc 工具, 参考文档, 命令如下:
tc qdisc del dev eth0 root
tc qdisc add dev eth0 root handle 1:0 htb default 1
tc class add dev eth0 parent 1:0 classid 1:1 htb rate 1000mbit
tc qdisc add dev eth0 parent 1:1 handle 2:0 netem delay 50ms
测试的时候使用curl命令, 将结果直接输出到 /dev/null, 这个场景下, 客户端收写数据的速度非常快,这样的测试排除了大文件落硬盘速度慢的问题, 但是也让客户端收到这批数据之后立刻可以发送ack给服务端(Client - ACK——> Server), 告知服务端我这边的数据已经处理完了, 回收 RecvBuffer 空间.
在添加带宽控制和 50ms 延迟之后, 带宽使用:
ping 命令 确认 rtt :
root@arch ~# ping nginx.liarlee.site
PING nginx.liarlee.site (172.31.48.133) 56(84) bytes ...
删除所有非Running状态的Pod
背景 如果所有的节点上面都有Taint, 然后这个没有Taint的节点磁盘满了, 会导致当前的节点上面留下许多状态不正常的Pod, 这些Pod大概率是停留在了Evicted状态, 或者是Completed, 甚至是 Unknown 。
这种状态的Pod Deployment默认的情况下不会自动回收, 所以需要人工操作一下。
Knowledge Source
处理方法记录一个命令来处理这个类型的Pod。
kubectl delete pod --field-selector="status.phase==Failed"
测试方法
集群内两个节点, 其中一个节点Taint kubectl taint nodes ip-172-31-60-181.cn-north-1.compute.internal app=grafana:NoSchedule
启动30个Grafana
登录到节点上面, 创建一个巨大的文件, 触发DiskPressure。 fallocate -l 72G ./large.file
等待节点的DiskPressure被识别, 然后触发驱逐。
删除文件, 取消DiskPressure状态, 等待 30 个新的Pod Ready。rm -rf ./large.file
使用命令清除所有不是Running状态的Pod。~$ kubectl delete pod --field-selector="status.phase==Failed"
Over.
bpftrace 使用tracepoint 追踪 tcp 状态变化
入门教程大佬的博客, 入门教程: http://arthurchiao.art/blog/bpf-advanced-notes-1-zh/
记录基础的bpftrace使用方法单行程序的使用方法
[root@localhost-live ~]# bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%s %s\n", comm, str(args->filename));}'
Tracepoint如何获取可用参数的解释[root@localhost-live sys_enter_execve]# pwd
/sys/kernel/tracing/events/syscalls/sys_enter_execve
[root@localhost-live sys_enter_execve]# grep -ri .
format:name: sys_enter_execve
format:ID: 742
format:format:
format: field:unsigned short common_type; offset:0; size:2; signed:0;
format: field:unsigned char common_flags; offset:2; size:1; signed:0;
format: field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
format: field:int common_pid; offset:4; size:4; signed:1;
format: field:int __syscall_nr; offset:8; size:4; signed:1;
format: field:const char * filename; offset:16; size:8; signed:0;
format: field:const char *const * argv; offset:24; size:8; signed:0;
format: field:const char *const * envp; offset:32; size:8; signed:0;
format:print fmt: "filename: 0x%08lx, argv: 0x%08lx, envp: 0x%08lx", ((unsigned long)(REC->filename)), ((unsigned long)(REC->argv)), ((unsigned long)(REC->envp))
trigger:# Available triggers:
trigger:# traceon traceoff snapshot stacktrace enable_event disable_event enable_hist disable_hist hist
filter:none
id:742
enable:0
记录尝试追踪tcp状态变化的方法关于bpftrace追踪的总结, 追踪tcp状态的方法, 通过使用特定的tracepoint的来获取tcp状态的变化:
~$ bpftrace -e 'tracepoint:sock:inet_sock_set_state { printf("%s %d %d\n", comm, pid, args->newstate); }'
~$ bpftrace -e 'tracepoint:sock:inet_sock_set_state { printf("%s - %d -> %d - %d - %s\n",strftime("%H:%M:%S.%L", nsecs), args->oldstate, args->newstate, pid, comm); }'
关于返回值的说明:
https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-9/-/blob/main/include/net/tcp_states.h
tcp_set_state 是一个内核函数,用于设置 TCP 套接字的状态。它有 12 个可能的返回值,分别对应 TCP 协议中定义的 12 种状态1。这些状态是:
1 TCP_ESTABLISHED:连接已建立2 TCP_SYN_SENT:主动打开连接,已发送 SYN 包3 TCP_SYN_RECV:被动打开连接,已收到 SYN 包4 TCP_FIN_WAIT1:主动关闭连接,已发送 FIN 包5 TCP_FIN_WAIT2:主动关闭连接,已收到对方的 ACK 包6 TCP_TIME_WAIT:主动关闭连接,等待一段时间以确保对方收到最后一个 ACK 包7 TCP_CLOSE:连接已关闭8 TCP_CLOSE_WAIT:被动关闭连接,已收到 FIN 包9 TCP_LAST_ACK:被动关闭连接,已发送最后一个 ACK 包10 TCP_LISTEN:监听状态,等待被动打开连接11 TCP_CLOSING:双方同时关闭连接,交换 FIN 和 ACK 包的过程中12 TCP_NEW_SYN_RECV:临时状态,用于处理 SYN 队列溢出的情况
追踪点理解起来还是比较简单的, 查看追踪点可用的参数,在这个部分可以看。
~$ cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_sendmsg/format
name: sys_enter_sendmsg
ID: 1252
format:
field:unsigned short common_type; offset:0; size:2; signed:0;
field:unsigned char common_flags; offset:2; size:1; signed:0;
field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
field:int common_pid; offset:4; size:4; signed:1;
field:unsigned char common_preempt_lazy_count; offset:8; size:1; signed:0;
field:int __syscall_nr; offset:12; size:4; signed:1;
field:int fd; offset:16; size:8; signed:0;
field:struct user_msghdr * msg; offset:24; size:8; signed:0;
field:unsigned int flags; offset:32; size:8; signed:0;
print fmt: "fd: 0x%08lx, msg: 0x%08lx, flags: 0x%08lx", ((unsigned long)(REC->fd)), ((unsigned long)(REC->msg)), ((unsigned long)(REC->flags))
如果是使用 kprobe 的话, 不能使用 args, 需要使用确定的arg0 - argN, 这个部分还在摸索, 目前尝试用这个参数还是失败。 取到对应的得值, 但是print不出来。