引
内容简介
Linux 大部分数据流动,包括进程间通讯,socket…… 均通过文件描述符(fd) 读写实现。在 troubleshooting 时,如果可以偷看到 fd 的流量,那么很多问题可以加速证明/证伪。本文介绍一个老工具 peekfd ,可以在一定环境中完成这个任务。
我遇到的问题
我在 《小编码,我输给 AI 了 —— 简记一次父子进程互锁的坑,自己挖的》 中说了一个场景。下面是进程父子关系图,我想用 kill -QUIT $
(为何不 jstack ?) 查看 jvm stack 。理想情况下, kill -QUIT $test_case_jvm
会让 test cases jvm
打印 stack dump 到 stdout,然后 maven jvm
也会复制一份打印出来。但现实是 maven jvm
把 test cases jvm
的 stack dump 输出吃了没吐出来。
[maven jvm]
|
| -- [test cases jvm] (waiting for [curl] exit)
|
| -- [curl] (stdout pipe buffer full, waiting for pipe writeable)
$ sudo ll /proc/$test_case_jvm/fd
lr-x------ 1 1201 1202 64 7月 4 09:33 0 -> pipe:[1095338147]
l-wx------ 1 1201 1202 64 7月 4 09:33 1 -> pipe:[1095338148]
l-wx------ 1 1201 1202 64 7月 4 09:33 2 -> pipe:[1095338149]
lrwx------ 1 1201 1202 64 7月 4 09:33 3 -> socket:[1095340467]
lrwx------ 1 1201 1202 64 7月 4 09:33 4 -> socket:[1095340468]
lrwx------ 1 1201 1202 64 7月 4 09:33 5 -> socket:[1095340471]
还有计吗?当然有。 test cases jvm
是通过写 pipe 去分享 stdout 给 maven jvm
的。 pipe 的写说到底还是个 fd(file descriptor) 如果我们有个类似 tcpdump 的工具,可以看到这个 fd 的流量,就可以获得我们要的数据了。
尝试 cat fd
一开始,我想用 cat test cases jvm
的 stdout fd(fd 2) 的方法:
sudo cat /proc/$$test_case_jvm/fd/1
去偷看。但很快发现,stack dump 的结果是不完整的,每隔一行丢失一行。想想原因也简单,Linux pipe 支持多 reader ,但每个 buffer 块只给一个 reader 消费。
还有计吗?当然有。
peekfd
我的英语词汇其实有限。很多是在现在公司学习的(心怀感恩!)。我以前不了解 peek 这个英文单词的意思。第一次注意到这个单词是 java steam 的 peek。 然后就是 Envoy Proxy 的 Listener Filter 中用到的 libc 的 [recv(int sockfd, void buf[.len], size_t len, int flags) 中的 MSG_PEEK flag](https://man7.org/linux/man-pages/man2/recv.2.html#:~:text=wit...,MSG_PEEK,-This%20flag%20causes) 。
看看英文单词 peek 的定义:
to look, especially for a short time or while trying to avoid being seen:
Close your eyes. Don't peek. I have a surprise for you.
I peeked out the window to see who was there.
The children peeked over the wall to see where the ball had gone.
The film peeks behind the scenes of a multinational corporation.
take 1
我们用 jdk 自带的 java 应用 jconsole 看看效果。
jconsole | grep "not match at all"
在另外的终端中:
peekfd $(pgrep jconsole) 1 # 1 is fd of stdout
在另外的终端中:
kill -QUIT `pgrep console`
结果是,peekfd 什么也没输出!失望吧!爱折腾的我,当然不会就这样放弃。我自以为是懂点 kernel、 懂点 gdb 的人。
take 2
看看 peekfd 源码吧,就一个 c file:
https://github.com/acg/psmisc/blob/0d03fe8ca1d494d1cc48ca26aa0d780ad0cd4898/src/peekfd.c#L95
void attach(pid_t pid) {
if (num_attached_pids >= MAX_ATTACHED_PIDS)
return;
attached_pids[num_attached_pids] = pid;
if (ptrace(PTRACE_ATTACH, pid, 0, 0) == -1) {
fprintf(stderr, _("Error attaching to pid %i\n"), pid);
return;
}
num_attached_pids++;
}
用了 ptrace ,ptrace 是指定线程的,而 jvm 是多线程的,所以我得指定整个进程。
$ peekfd --help
Usage: peekfd [-8] [-n] [-c] [-d] [-V] [-h] <pid> [<fd> ..]
-8, --eight-bit-clean output 8 bit clean streams.
-n, --no-headers don't display read/write from fd headers.
-c, --follow peek at any new child processes too.
-t, --tgid peek at all threads where tgid equals <pid>.
-d, --duplicates-removed remove duplicate read/writes from the output.
-V, --version prints version info.
-h, --help prints this help.
--tgid
好像可以救命,试试呗:
peekfd --follow --tgid `pgrep jconsole` 1 # 1 is fd of stdout
不知道为何,最近人品好像有点问题,还是没偷看到!
take 3
我自以为是懂点 jvm 的人,知道 thead dump 是由一个叫 VM Thread
的线程写 stdout 的。那么:
export vm_thread_id=$(ps -T -p `pgrep jconsole` | grep 'VM Thread' | cut -d ' ' -f 2)
peekfd $vm_thread_id 1 # 1 is fd of stdout
正所谓:功夫不负折腾人,只是家娃没人陪。终于这次可以抓取到 stdout 了。
更有意思的是, socket 也是 fd ,也可以用 peekfd 看 socket 的读写数据!
结语
peekfd 用了 ptrace ,可能比较性能影响,所以不建议用于生产环境中。peekfd 是个老掉牙的工具了,藏在 psmisc 这个工具包中。知名度很低很低。再次证明一点,解决疑难大问题,用小工具。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。