Monitoring
如何辨識系統容器上不斷增長的阻塞程序的來源(LXC/OpenVZ)
當阻塞程序的數量無限增長時,如何準確辨識這些程序是什麼。
由於我位於系統容器 (LXC/OpenVZ) 中,因此無法修改主機的核心模組以使用perf-tools或bcc-tools/bpfcc-tools 等工具
問題如何在 Linux 中跟踪新創建的程序?沒有涵蓋系統容器的場景,大部分答案依賴於
perf-tools
和bcc-tools
.
1.檢查
CONFIG_PROC_EVENTS
首先檢查是否
CONFIG_PROC_EVENTS
啟用。根據您的發行版,您可以執行:grep CONFIG_PROC_EVENTS= /boot/config-`uname -r`
如果啟用,您將獲得以下結果:
CONFIG_PROC_EVENTS=y
2.如果啟用,創建監視器
創建一個
proc_events.c
包含以下內容的文件:static volatile bool need_exit = false; static int nl_connect() { int rc; int nl_sock; struct sockaddr_nl sa_nl; nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR); if (nl_sock == -1) { perror("socket"); return -1; } sa_nl.nl_family = AF_NETLINK; sa_nl.nl_groups = CN_IDX_PROC; sa_nl.nl_pid = getpid(); rc = bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl)); if (rc == -1) { perror("bind"); close(nl_sock); return -1; } return nl_sock; } static int set_proc_ev_listen(int nl_sock, bool enable) { int rc; struct __attribute__ ((aligned(NLMSG_ALIGNTO))) { struct nlmsghdr nl_hdr; struct __attribute__ ((__packed__)) { struct cn_msg cn_msg; enum proc_cn_mcast_op cn_mcast; }; } nlcn_msg; memset(&nlcn_msg, 0, sizeof(nlcn_msg)); nlcn_msg.nl_hdr.nlmsg_len = sizeof(nlcn_msg); nlcn_msg.nl_hdr.nlmsg_pid = getpid(); nlcn_msg.nl_hdr.nlmsg_type = NLMSG_DONE; nlcn_msg.cn_msg.id.idx = CN_IDX_PROC; nlcn_msg.cn_msg.id.val = CN_VAL_PROC; nlcn_msg.cn_msg.len = sizeof(enum proc_cn_mcast_op); nlcn_msg.cn_mcast = enable ? PROC_CN_MCAST_LISTEN : PROC_CN_MCAST_IGNORE; rc = send(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0); if (rc == -1) { perror("netlink send"); return -1; } return 0; } static int handle_proc_ev(int nl_sock) { int rc; struct __attribute__ ((aligned(NLMSG_ALIGNTO))) { struct nlmsghdr nl_hdr; struct __attribute__ ((__packed__)) { struct cn_msg cn_msg; struct proc_event proc_ev; }; } nlcn_msg; while (!need_exit) { rc = recv(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0); if (rc == 0) { /* shutdown? */ return 0; } else if (rc == -1) { if (errno == EINTR) continue; perror("netlink recv"); return -1; } switch (nlcn_msg.proc_ev.what) { case PROC_EVENT_NONE: printf("set mcast listen ok\n"); break; case PROC_EVENT_FORK: printf("fork: parent tid=%d pid=%d -> child tid=%d pid=%d\n", nlcn_msg.proc_ev.event_data.fork.parent_pid, nlcn_msg.proc_ev.event_data.fork.parent_tgid, nlcn_msg.proc_ev.event_data.fork.child_pid, nlcn_msg.proc_ev.event_data.fork.child_tgid); break; case PROC_EVENT_EXEC: printf("exec: tid=%d pid=%d\n", nlcn_msg.proc_ev.event_data.exec.process_pid, nlcn_msg.proc_ev.event_data.exec.process_tgid); break; case PROC_EVENT_UID: printf("uid change: tid=%d pid=%d from %d to %d\n", nlcn_msg.proc_ev.event_data.id.process_pid, nlcn_msg.proc_ev.event_data.id.process_tgid, nlcn_msg.proc_ev.event_data.id.r.ruid, nlcn_msg.proc_ev.event_data.id.e.euid); break; case PROC_EVENT_GID: printf("gid change: tid=%d pid=%d from %d to %d\n", nlcn_msg.proc_ev.event_data.id.process_pid, nlcn_msg.proc_ev.event_data.id.process_tgid, nlcn_msg.proc_ev.event_data.id.r.rgid, nlcn_msg.proc_ev.event_data.id.e.egid); break; case PROC_EVENT_EXIT: printf("exit: tid=%d pid=%d exit_code=%d\n", nlcn_msg.proc_ev.event_data.exit.process_pid, nlcn_msg.proc_ev.event_data.exit.process_tgid, nlcn_msg.proc_ev.event_data.exit.exit_code); break; default: printf("unhandled proc event\n"); break; } } return 0; } static void on_sigint(__attribute__ ((unused)) int unused) { need_exit = true; } int main() { int nl_sock; int rc = EXIT_SUCCESS; signal(SIGINT, &on_sigint); siginterrupt(SIGINT, true); nl_sock = nl_connect(); if (nl_sock == -1) exit(EXIT_FAILURE); rc = set_proc_ev_listen(nl_sock, true); if (rc == -1) { rc = EXIT_FAILURE; goto out; } rc = handle_proc_ev(nl_sock); if (rc == -1) { rc = EXIT_FAILURE; goto out; } set_proc_ev_listen(nl_sock, false); out: close(nl_sock); exit(rc); }
確保您可以編譯 C 程序。在 Debian 或基於 Debian 的發行版上,例如 Ubuntu,執行:
sudo apt-get install build-essential
並編譯它:
gcc proc_events.c -o proc_events
3.執行幾秒鐘
以這種方式執行將直接在終端上輸出:
./proc_events
但是您也可以重定向它並執行幾秒鐘,以便將其輸出保存在文件中:
./proc_events > results_file
4. 分析結果
輸出將採用以下格式:
fork: parent tid=48 pid=48 -> child tid=56 pid=56 fork: parent tid=48 pid=48 -> child tid=57 pid=57 exec: tid=57 pid=57 exec: tid=56 pid=56 exit: tid=57 pid=57 exit_code=0 exit: tid=56 pid=56 exit_code=0
歸功於@cirosantilli