【问题标题】:get memory usage per process with sar, sysstat使用 sar、sysstat 获取每个进程的内存使用情况
【发布时间】:2017-04-20 23:30:43
【问题描述】:

我可以使用 Linux 获取每个进程的内存使用情况吗? 我们使用 sysstat/sar 监控我们的服务器。但除此之外 看到记忆在某个时候突然消失,我们无法确定 哪个过程越来越大。 有没有办法使用 sar(或其他工具)来获取内存使用情况 每个进程?稍后再看?

【问题讨论】:

    标签: linux monitoring sar


    【解决方案1】:

    sysstat 包括 pidstat,其手册页显示:

    pidstat 命令用于监控当前由 Linux 内核管理的单个任务。它为使用选项 -p 选择的每个任务或 Linux 内核管理的每个任务写入标准输出活动 [...]

    Linux 内核任务包括用户空间进程和线程(还有内核线程,这里最不感兴趣)。

    但不幸的是,sysstat 不支持从pidstat 收集历史数据,而且作者似乎没有兴趣提供此类支持(GitHub 问题):

    pidstat

    话虽如此,pidstat 的表格输出可以写入文件并稍后进行解析。通常感兴趣的是进程组,而不是系统上的每个进程。我将重点介绍一个带有子进程的进程。

    可以举个例子吗?火狐。 pgrep firefox 返回其 PID,$(pgrep -d, -P $(pgrep firefox)) 返回其子项的逗号分隔列表。鉴于此,pidstat 命令看起来像:

    LC_NUMERIC=C.UTF-8 watch pidstat -dru -hl \
        -p '$(pgrep firefox),$(pgrep -d, -P $(pgrep firefox))' \
        10 60 '>>' firefox-$(date +%s).pidstat
    

    一些观察:

    • LC_NUMERIC 设置为使 pidstat 使用点作为小数分隔符。
    • watch 用于每 600 秒重复一次 pidstat,以防万一 处理子树更改。
    • -d 用于报告 I/O 统计信息-r 用于报告页面错误和内存利用率-u 用于report CPU utilization
    • -h 将所有报告组放在一行中,-l 显示进程命令名称及其所有参数(嗯,有点,因为它仍然将其修剪为 127 个字符)。
    • date 用于避免意外覆盖现有文件

    它会产生类似的东西:

    Linux kernel version (host)     31/03/20    _x86_64_    (8 CPU)
    
    #      Time   UID       PID    %usr %system  %guest    %CPU   CPU  minflt/s  majflt/s     VSZ     RSS   %MEM   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
     1585671289  1000      5173    0.50    0.30    0.00    0.80     5      0.70      0.00 3789880  509536   3.21      0.00     29.60      0.00       0  /usr/lib/firefox/firefox 
     1585671289  1000      5344    0.70    0.30    0.00    1.00     1      0.50      0.00 3914852  868596   5.48      0.00      0.00      0.00       0  /usr/lib/firefox/firefox -contentproc -childID 1 ...
     1585671289  1000      5764    0.10    0.10    0.00    0.20     1      7.50      0.00 9374676  363984   2.29      0.00      0.00      0.00       0  /usr/lib/firefox/firefox -contentproc -childID 2 ...
     1585671289  1000      5852    6.60    0.90    0.00    7.50     7    860.70      0.00 4276640 1040568   6.56      0.00      0.00      0.00       0  /usr/lib/firefox/firefox -contentproc -childID 3 ...
     1585671289  1000     24556    0.00    0.00    0.00    0.00     7      0.00      0.00  419252   18520   0.12      0.00      0.00      0.00       0  /usr/lib/firefox/firefox -contentproc -parentBuildID ...
    
    #      Time   UID       PID    %usr %system  %guest    %CPU   CPU  minflt/s  majflt/s     VSZ     RSS   %MEM   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
     1585671299  1000      5173    3.40    1.60    0.00    5.00     6      7.60      0.00 3789880  509768   3.21      0.00     20.00      0.00       0  /usr/lib/firefox/firefox 
     1585671299  1000      5344    5.70    1.30    0.00    7.00     6    410.10      0.00 3914852  869396   5.48      0.00      0.00      0.00       0  /usr/lib/firefox/firefox -contentproc -childID 1 ...
     1585671299  1000      5764    0.00    0.00    0.00    0.00     3      0.00      0.00 9374676  363984   2.29      0.00      0.00      0.00       0  /usr/lib/firefox/firefox -contentproc -childID 2 ...
     1585671299  1000      5852    1.00    0.30    0.00    1.30     1     90.20      0.00 4276640 1040452   6.56      0.00      0.00      0.00       0  /usr/lib/firefox/firefox -contentproc -childID 3 ...
     1585671299  1000     24556    0.00    0.00    0.00    0.00     7      0.00      0.00  419252   18520   0.12      0.00      0.00      0.00       0  /usr/lib/firefox/firefox -contentproc -parentBuildID ...
    
    ...
    

    请注意,每行数据都以空格开头,因此解析很容易:

    import pandas as pd
    
    def read_columns(filename):
        with open(filename) as f:
            for l in f:
                if l[0] != '#':
                    continue
                else:
                    return l.strip('#').split()
            else:
                raise LookupError
    
    def get_lines(filename, colnum):
        with open(filename) as f:
            for l in f:
                if l[0] == ' ':
                    yield l.split(maxsplit=colnum - 1)        
    
    filename = '/path/to/firefox.pidstat'
    columns = read_columns(filename)
    exclude = 'CPU', 'UID', 
    df = pd.DataFrame.from_records(
        get_lines(filename, len(columns)), columns=columns, exclude=exclude
    )
    numcols = df.columns.drop('Command')
    df[numcols] = df[numcols].apply(pd.to_numeric, errors='coerce')
    df['RSS'] = df.RSS / 1024  # Make MiB
    df['Time'] = pd.to_datetime(df['Time'], unit='s', utc=True)
    df = df.set_index('Time')
    df.info()
    

    dataframe的结构如下:

    Data columns (total 15 columns):
     #   Column     Non-Null Count  Dtype  
    ---  ------     --------------  -----  
     0   PID        6155 non-null   int64  
     1   %usr       6155 non-null   float64
     2   %system    6155 non-null   float64
     3   %guest     6155 non-null   float64
     4   %CPU       6155 non-null   float64
     5   minflt/s   6155 non-null   float64
     6   majflt/s   6155 non-null   float64
     7   VSZ        6155 non-null   int64  
     8   RSS        6155 non-null   float64
     9   %MEM       6155 non-null   float64
     10  kB_rd/s    6155 non-null   float64
     11  kB_wr/s    6155 non-null   float64
     12  kB_ccwr/s  6155 non-null   float64
     13  iodelay    6155 non-null   int64  
     14  Command    6155 non-null   object 
    dtypes: float64(11), int64(3), object(1)
    

    它可以通过多种方式可视化,具体取决于监控的重点,但%CPURSS 是最常见的指标。所以这里是一个例子。

    import matplotlib.pyplot as plt
    
    fig, axes = plt.subplots(len(df.PID.unique()), 2, figsize=(12, 8))
    x_range = [df.index.min(), df.index.max()]
    for i, pid in enumerate(df.PID.unique()):
        subdf = df[df.PID == pid]
        title = ', '.join([f'PID {pid}', str(subdf.index.max() - subdf.index.min())])
        for j, col in enumerate(('%CPU', 'RSS')):
            ax = subdf.plot(
                y=col, title=title if j == 0 else None, ax=axes[i][j], sharex=True
           )
            ax.legend(loc='upper right')
            ax.set_xlim(x_range)
    
    plt.tight_layout()
    plt.show()
    

    它会产生如下图:

    【讨论】:

    • 在 Ubuntu 18.04 上我看到这个错误,我该如何继续?文件“./pd_plot.py”,第 40 行 title = ', '.join([f 'PID {pid}', str(subdf.index.max() - subdf.index.min())]) ^ SyntaxError : 语法无效 $ python3 --version Python 3.6.9
    • 确保您正确复制粘贴了 sn-ps。 f-string 前缀和上面的字符串本身之间肯定没有空格,即f 'PID...' 是无效语法,这就是你得到SyntaxError 的原因。您还可以在this answer 中查看一些现成的工具,用于可视化 Linux 进程资源利用率随时间的变化。
    【解决方案2】:

    这纯粹是偏好,但我会保持它简洁明了,直到您知道自己在寻找什么。我会创建一个 cronjob 来首先输出您的可用内存、磁盘和 cpu 使用情况,然后显示前十名的罪魁祸首。

    #!/bin/sh
    free -m | awk 'NR==2{printf "Memory Usage: %s/%sMB (%.2f%%)\n", $3,$2,$3*100/$2 }'
    df -h | awk '$NF=="/"{printf "Disk Usage: %d/%dGB (%s)\n", $3,$2,$5}'
    top -bn1 | grep load | awk '{printf "CPU Load: %.2f\n", $(NF-2)}' 
    ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%mem | head
    

    找到罪魁祸首后,您可以进一步磨练并深入挖掘一些细节。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-24
      • 2015-03-11
      • 2011-11-05
      • 1970-01-01
      • 2014-04-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多