AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / unix / 问题 / 779002
Accepted
wobtax
wobtax
Asked: 2024-06-26 02:54:26 +0800 CST2024-06-26 02:54:26 +0800 CST 2024-06-26 02:54:26 +0800 CST

使用‘top’持续监控一个进程和它的所有子进程?

  • 772

我想要运行一个产生子进程的过程,例如,

for i in {1..4}; do
  sh -c 'echo $$; for j in {1..3}; do
    sh -c "echo ...\$\$; sleep 1"
  done'
done

并且我想每 2 秒用 监控一次 CPU 和内存使用情况top。

  • 我可以用 监控它的资源使用情况top -p <PID>,但这并没有考虑到孩子的情况。
  • 我可以仅用 来监控所有top正在运行的进程,但是信息太多了。
  • 我可以预先计算 PID 列表,然后将它们全部传递给top,但该过程可能会产生新的子进程,这是无法解释的。

我如何才能top每 2 秒获取一次我正在运行的进程及其产生的任何进程的快照?

有人在这里问过类似的问题,但它是关于在流程结束后将这些信息汇总为一个数字。我的问题是关于在流程仍在进行时持续监控流程。

top
  • 5 5 个回答
  • 117 Views

5 个回答

  • Voted
  1. Marcus Müller
    2024-06-26T18:05:30+08:002024-06-26T18:05:30+08:00

    由于您在评论中提到,在启动母进程之前准备一些事情通常是可行的,并且因为您的示例启动了一个容器,它可能已经这样做了:

    只需使用 Linux cgroups 来监控(如果需要,还可以控制和限制)相关进程组所使用的资源。

    这很简单:如果您的进程位于 cgroup 中,那么它的子进程也会在同一个 cgroup 中生成。因此,将您的母进程放入它们自己的 cgroup 中(这“基本上是免费的”,默认情况下,新 cgroup 会从父 cgroup 继承所有内容)。然后,定期从 cgroupsv2 控制器轮询该 cgroup 中的进程,并显示它们。整个游戏如下所示:

    #!/usr/bin/python3
    # Copyright 2024 Marcus Müller
    # SPDX-License-Identifier: EUPL-1.2
    #
    # I was about to write this in *sh, but honestly, doesn't pay.
    # Let's do this in python. I'm not sure you'd understand shell script
    # better than python, anyways, and we're about to handle a few strings,
    # so, let's simply not do that in a shell language
    
    from subprocess import run
    from time import sleep
    import argparse
    
    
    def pids_of_cgroup(cgroup: str) -> list:
        with open(f"/sys/fs/cgroup/{cgroup}/cgroup.procs",
                  "r",
                  encoding="ascii") as procs:
            return [int(line) for line in procs.readlines()]
    
    
    def cgroup_from_pid(pid: int) -> str:
        with open(f"/proc/{pid}/cgroup", "r", encoding="ascii") as cgr_file:
            line = cgr_file.readline().strip()  # just one line
        return line.split(":")[-1]  # last element
    
    
    def run_program(pids: list, program: str = None) -> None:
        if program is None:
            prog = "top"
        else:
            prog = program
        cmdline = [prog, "-p", ",".join(str(pid) for pid in pids), "-n", "1"]
        if prog == "htop":
            cmdline += ["--tree", "--readonly"]  # tree view
    
        run(cmdline)
    
    
    def do_job(mother: int, htop: bool = False, interval: float = 2.0):
        # First argument is the parent PID, we use that to get the cgroup:
        cgroup = cgroup_from_pid(mother)
        program = "htop" if htop else "top"
        first_iter = True
        try:
            while True:
                if not first_iter:
                    sleep(interval)
                first_iter = False
                pids = pids_of_cgroup(cgroup)
                run_program(pids, program)
        except KeyboardInterrupt:
            pass  # User pressed ctrl+c, quit normally
        except FileNotFoundError:
            print(f"CGroup {cgroup} ceased to exist; quitting.")
    
    
    if __name__ == "__main__":  # if we're called as freestanding program
        argparser = argparse.ArgumentParser(
            description="show process stats of all child processes")
        argparser.add_argument("PID",
                               help="Mother process PID", type=int)
        argparser.add_argument(
            "-H", "--htop", help="Use htop instead of top", action='store_true')
    
        def positive_float(in_str: str) -> float:
            val = float(in_str)
            if val <= 0:
                raise ValueError(f"'{in_str}' has non-positive value {val}")
            return val
    
        argparser.add_argument("-t", "--interval",
                               help="Time between updates",
                               type=positive_float,
                               default=2.0)
        args = argparser.parse_args()
        do_job(args.PID, htop=args.htop, interval=args.interval)
    
    
    

    将其保存到文件中,使该文件可执行(chmod 755 filename),并使用父进程的 PID 运行它(/path/to/filename 1234)。

    这几乎肯定可以与任何已经是容器的东西一起工作(所以,flatpak、podman 和 docker 容器、apptainer 容器,……)。

    在自己的 cgroup 中启动某件事非常简单:

    systemd-run --user --scope program_you_wanted_to_run
    

    (当然,如果该程序实际上是一种服务,请务必删除--scope并作为服务运行;如果它要作为系统运行,而不是作为用户程序运行,请删除--user。理想情况下,如果它是一项服务,你实际上只需编写 6 行配置即可使其成为具有自己范围的功能齐全的 systemd 服务)

    • 3
  2. David G.
    2024-06-26T08:00:14+08:002024-06-26T08:00:14+08:00

    有很多种选择。

    1. 对于任何可见字段,您都可以对该字段进行过滤。一个明显的候选者是TTY=pts/*number*。请注意,您需要打开 TTY 显示。通过按o或使用O添加另一个过滤器来启动此过滤。您可以使用多个命令(包括 和screen command和xterm -e command)将命令隔离为 tty 上的唯一命令ssh -t localhost command(这也具有不丢失输出的优点)。
    2. 您可以使用 将整个顶部置于树视图模式V,然后使用 关闭不需要的父节点v。这应该允许您将其限制到足以看到您的命令,尽管限制它可能需要一些时间。这可能需要与其他事情相结合,因为通常只有一两棵完整的树。
    3. 您可以在其自己的用户中运行该命令,并使用 限制显示u。

    这些技术或其组合可能会给您带来想要的结果。

    编辑:为了便于设置,您可能需要tty;sleep 10在脚本顶部写一些类似的内容……或者甚至在另一个exec目标脚本中写一些类似的内容。

    • 2
  3. aviro
    2024-06-26T23:40:14+08:002024-06-26T23:40:14+08:00

    如果您不介意使用在不同的用户命名空间中运行您的程序,那么您可以根据用户命名空间unshare进行过滤。top

    $ unshare -r
    $ readlink /proc/$$/ns/user
    user:[4026532793]
    

    您还可以运行unshare -r <COMMAND>,然后使用它的 pid 来查找它的用户命名空间。

    现在,为了查看用户命名空间中的所有进程top:

    1. 按下f以选择一个字段

    2. 使用Down/Up箭头键,导航到nsUSER(USER namespace Inode)。

    3. 按空格键打开它

    4. 按“q”返回主top视图。

    5. 按“O”键触发过滤器,并根据用户命名空间过滤进程

      add filter #1 (case sensitive) as: [!]FLD?VAL nsUSER=4026532793
      
    6. 完毕。

    • 2
  4. wobtax
    2024-06-26T23:41:02+08:002024-06-26T23:41:02+08:00

    我自己想到的答案并不像其他人提出的那么好:

    #!/bin/bash
    # Tested with `top` from `procps-ng 4.0.3` and `pstree 23.6`.
    
    # Print the ID of the given process together with IDs of all its descendants
    get_pid_with_descendants() {
      pid="$1"
      pstree -ap "$pid" | sed -r 's/,([0-9]+).*|./\1/g'
    }
    
    # Monitor the given PID and its descendants every k seconds
    monitor_with_top() {
      pid="$1"
      k="$2"
    
      # While the process is running...
      while kill -s 0 "$pid" 2>/dev/null; do
        pids=( $(get_pid_with_descendants $pid) )
    
        # Print a top screen for all processes in this list
        top -bn1 "${pids[@]/#/-p }"
    
        # Wait k seconds
        sleep "$k"
      done
    }
    
    

    然后运行monitor_with_top <my_PID> 2每 2 秒打印一次预期的顶部屏幕。

    (编辑:哦,对了,正如 aviro 指出的那样,top只能通过这种方式传递最多 20 个 PID。按照 Marcus Müller 的回答,您可以改用htopwith --tree --readonly。)

    • 2
  5. Best Answer
    aviro
    2024-06-28T01:47:54+08:002024-06-28T01:47:54+08:00

    由于我也想要类似的功能,所以我编写了一个可以实现该功能的脚本。

    该脚本的优点是它top连续运行,因此它实际上显示的是最后一个间隔(默认为 3 秒,但可以通过参数更改-d secs)的 CPU 消耗。

    之前的答案是top每个周期运行一次,在这种情况下top只显示当时的 CPU 消耗。3 秒平均值更准确。

    top它还解决了无法接收超过 20 个 pid 的问题。

    该脚本需要top在“森林视图模式” 1中进行设置:您需要启动top,按V切换森林模式,然后按W将当前设置写入$HOME/.toprc配置文件2。

    脚本3连续运行top(默认情况下),所有进程都在主机上运行,​​并进行一些解析sed以仅显示所需的 pid 及其子进程。

    这是代码。我没有做过任何认真的测试,也没有考虑过极端情况,我只是让它工作了。也许我将来会改进它。

    #!/bin/bash
    
    usage() {
      echo "Usage: toptree.sh <PID> [top options]"
      echo "You need to make sure your 'top' is configured to a tree mode"
      exit
    }
    
    cleanup() {
      ps -p $toppid >& /dev/null && kill $toppid
      rm -rf "$FIFO_DIR"
      trap - EXIT INT TERM
    }
    
    trap cleanup EXIT INT TERM
    
    parse_top() {
      local pid=$1
      ps -p $toppid >& /dev/null || exit # top is not running anymore
      sed -un -e $'/^top/,/PID/{p; /PID/{q}}' # print the header of top  
      local parent=$( 
        sed -nu \
          -e "/^ *$pid/{p; q}" \
          -e '/^top/{q 1}' # if we reached the next top cycle,
                           # it means we didn't find the parent process
      ) || exit 0  # sed didn't find the process
                   # The process is probably finished
    
      # Next, we want to find only the children of the process
      # Since this is in tree form, we want only processes
      # with greater indentation than the parent.
      # we catch the spaces before the branch (`-) of the parent
      local indent=$(sed -nr 's/.*[^ ](  +)`-.*/\1/p' <<< "$parent")
      
      [ -z "$indent" ] && exit # Didn't find the branch marker
                               # Happens if top is not in tree mode
      
      sed "s/$indent\`-/ \`-/" <<< "$parent" # Remove indentation
      
      # Now we're printing all the children
      # until we reach a process in a another process tree
      # (ie, the branch level is equal or smaller than parent) 
      # than the parent we're monitoring
    
      sed -unr "
        s/ $indent\`-/ \`-/p
        t next # if indentation >  parent's, next cycle
        q      # if indentation <= parent's, outside branch. quit.
        :next" 
      echo 
    }
    
    is_pid() {
      local pid=$1
      if [ -n "$pid" ] && [ "$pid" -eq "$pid" ] 2>/dev/null
      then
        if ! ps -p $pid >& /dev/null
        then
          echo $pid is not running
          exit 1 
        fi
      else
        echo $pid should be a number
        usage
      fi
    }
    
    
    PID=$1
    is_pid "$PID"
    
    shift
    FIFO_DIR=$(mktemp -d /tmp/toptree.XXXXXX)
    FIFO="$FIFO_DIR/fifo"
    mkfifo "$FIFO" 
    
    top -b -w 150 "${@}" > "$FIFO" & toppid=$!
    
    while parse_top $PID; do :; done < "$FIFO"
    

    例子:

    $ toptree.sh
    Usage: toptree.sh <PID> [top options]
    You need to make sure your 'top' is configured to a tree mode
    
    $ toptree.sh 17349 -d 2 -n 2
    top - 20:29:27 up 90 days,  6:10, 396 users,  load average: 0.16, 0.14, 0.10
    Tasks: 1926 total,   1 running, 1925 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.4 us,  2.6 sy,  0.0 ni, 96.6 id,  0.0 wa,  0.0 hi,  0.4 si,  0.0 st
    KiB Mem:  39455878+total, 17768724 used, 37679008+free,    47308 buffers
    KiB Swap: 39427174+total,        0 used, 39427174+free. 11275700 cached Mem
    
      PID USER      PR  NI    VIRT    RES    SHR S   %CPU  %MEM     TIME+ COMMAND
    17349 aviro     20   0   12096   8136   2976 S  0.000 0.002   0:00.32 `- bash
    15817 aviro     20   0    6988   2620   2388 S  0.000 0.001   0:00.00    `- bash
    15820 aviro     20   0    2500   1268   1188 S  0.000 0.000   0:00.00        `- sleep
    15821 aviro     20   0    2500   1268   1188 S  0.000 0.000   0:00.00        `- sleep
    15818 aviro     20   0    6988   2620   2388 S  0.000 0.001   0:00.00    `- bash
    15822 aviro     20   0    2500   1268   1188 S  0.000 0.000   0:00.00        `- sleep
    15823 aviro     20   0    2500   1268   1188 S  0.000 0.000   0:00.00        `- sleep
    15819 aviro     20   0    2500   1268   1188 S  0.000 0.000   0:00.00    `- sleep
    
    top - 20:29:29 up 90 days,  6:10, 396 users,  load average: 0.16, 0.14, 0.10
    Tasks: 1926 total,   1 running, 1925 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.2 us,  0.5 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem:  39455878+total, 17768724 used, 37679008+free,    47308 buffers
    KiB Swap: 39427174+total,        0 used, 39427174+free. 11275700 cached Mem
    
      PID USER      PR  NI    VIRT    RES    SHR S   %CPU  %MEM     TIME+ COMMAND
    17349 aviro     20   0   12096   8136   2976 S  0.000 0.002   0:00.32 `- bash
    15817 aviro     20   0    6988   2620   2388 S  0.000 0.001   0:00.00    `- bash
    15820 aviro     20   0    2500   1268   1188 S  0.000 0.000   0:00.00        `- sleep
    15821 aviro     20   0    2500   1268   1188 S  0.000 0.000   0:00.00        `- sleep
    15818 aviro     20   0    6988   2620   2388 S  0.000 0.001   0:00.00    `- bash
    15822 aviro     20   0    2500   1268   1188 S  0.000 0.000   0:00.00        `- sleep
    15823 aviro     20   0    2500   1268   1188 S  0.000 0.000   0:00.00        `- sleep
    15819 aviro     20   0    2500   1268   1188 S  0.000 0.000   0:00.00    `- sleep
    

    请注意,您可以向要传递给 的脚本添加任何标志top。例如,如果您想查看完整的命令行,请添加-c:

    $ toptree.sh 19140 -c -n1
    top - 20:40:52 up 90 days,  6:21, 398 users,  load average: 0.24, 0.13, 0.10
    Tasks: 1930 total,   1 running, 1929 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.8 us,  2.8 sy,  0.0 ni, 96.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem:  39455878+total, 17782400 used, 37677638+free,    45796 buffers
    KiB Swap: 39427174+total,        0 used, 39427174+free. 11276956 cached Mem
    
      PID USER      PR  NI    VIRT    RES    SHR S   %CPU  %MEM     TIME+ COMMAND
    19140 aviro     20   0    6988   2624   2388 S  0.000 0.001   0:00.00 `- bash -c sleep 5 & sleep 5
    19141 aviro     20   0    2500   1268   1188 S  0.000 0.000   0:00.00    `- sleep 5
    19142 aviro     20   0    2500   1268   1188 S  0.000 0.000   0:00.00    `- sleep 5
    

    1我希望top有更多的命令行选项,这样就可以从命令行启动森林视图模式,而不必更改默认视图,但这就是它。

    2在退出 top 之前发出“W”命令,稍后你将能够以完全相同的状态重新启动

    3使用 Python 来做这件事可能更加优雅,但是没关系。

    • 1

相关问题

  • 为什么默认非irix模式下top的CPU故障(选项1)显示冲突结果?

  • top命令中swap部分的含义

  • 不同top的CPU模式如何相互对应?

  • 为什么“top”命令输出中列出的总任务不等于运行+睡眠?

  • 清除`top`中添加的过滤器

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    模块 i915 可能缺少固件 /lib/firmware/i915/*

    • 3 个回答
  • Marko Smith

    无法获取 jessie backports 存储库

    • 4 个回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    user12345 无法获取 jessie backports 存储库 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl 为什么大多数 systemd 示例都包含 WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve