Monitoring

如何辨識系統容器上不斷增長的阻塞程序的來源(LXC/OpenVZ)

  • May 11, 2019

當阻塞程序的數量無限增長時,如何準確辨識這些程序是什麼。

在此處輸入圖像描述

由於我位於系統容器 (LXC/OpenVZ) 中,因此無法修改主機的核心模組以使用perf-toolsbcc-tools/bpfcc-tools 等工具


問題如何在 Linux 中跟踪新創建的程序?沒有涵蓋系統容器的場景,大部分答案依賴於perf-toolsbcc-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

引用自:https://serverfault.com/questions/966709