【问题标题】:CPU-usage or CPU-cycles of a process/pid with respect to the maximum CPU frequency相对于最大 CPU 频率的进程/pid 的 CPU 使用率或 CPU 周期
【发布时间】:2017-05-09 15:12:18
【问题描述】:

目前,我通过 Python 的 psutil 模块监控多个进程并检索基于 execution_time/total_time 的 CPU 使用率百分比。这样做的问题是动态电压和频率缩放(DVFS,或 ACPI 的 P 状态,或 cpufreq 等)。当前CPU频率越低,进程需要执行的时间越长,CPU使用率就越高。与此相反,我需要相对于 CPU 最大性能的固定 CPU 使用率。

为避免因“当前频率”永久变化而进行多次重新调整,一种方法是直接使用进程使用的 CPU 周期。原则上,这可以通过 C 中的 perf_event.h 或 Linux 命令行中的 perf 来完成。不幸的是,我找不到提供类似功能的 Python 模块(基于上述功能)。

【问题讨论】:

    标签: python python-3.x


    【解决方案1】:

    感谢 BlackJack 的评论

    如何在 C 中将其实现为共享库并在 Python 中通过 ctypes 使用它?

    库调用引入的开销更少。每次需要值时,子进程调用都会启动整个外部进程并通过管道将结果作为字符串进行通信。共享库被加载一次到当前进程,结果在内存中传递。

    我将它实现为共享库。 cpucycles.c库的源码是(大量基于perf_event_open's man page的例子):

    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <linux/perf_event.h>
    #include <asm/unistd.h>
    
    static long
    perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
                    int cpu, int group_fd, unsigned long flags)
    {
        int ret;
    
        ret = syscall(__NR_perf_event_open, hw_event, pid, cpu,
                        group_fd, flags);
        return ret;
    }
    
    long long
    cpu_cycles(unsigned int microseconds,
                pid_t pid,
                int cpu,
                int exclude_user,
                int exclude_kernel,
                int exclude_hv,
                int exclude_idle)
    {
        struct perf_event_attr pe;
        long long count;
        int fd;
    
        memset(&pe, 0, sizeof(struct perf_event_attr));
        pe.type = PERF_TYPE_HARDWARE;
        pe.size = sizeof(struct perf_event_attr);
        pe.config = PERF_COUNT_HW_CPU_CYCLES;
        pe.disabled = 1;
        pe.exclude_user = exclude_user;
        pe.exclude_kernel = exclude_kernel;
        pe.exclude_hv = exclude_hv;
        pe.exclude_idle = exclude_idle;
    
        fd = perf_event_open(&pe, pid, cpu, -1, 0);
        if (fd == -1) {
            return -1;
        }
        ioctl(fd, PERF_EVENT_IOC_RESET, 0);
        ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
        usleep(microseconds);
        ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
        read(fd, &count, sizeof(long long));
    
        close(fd);
        return count;
    }
    

    这段代码通过以下两个命令编译成共享库:

    $ gcc -c -fPIC cpucycles.c -o cpucycles.o
    $ gcc -shared -Wl,-soname,libcpucycles.so.1 -o libcpucycles.so.1.0.1 cpucycles.o
    

    最后,cpucycles.py中的库可以被Python使用:

    import ctypes
    import os
    
    cdll = ctypes.cdll.LoadLibrary(os.path.join(os.path.dirname(__file__), "libcpucycles.so.1.0.1"))
    cdll.cpu_cycles.argtypes = (ctypes.c_uint, ctypes.c_int, ctypes.c_int,
                                ctypes.c_int, ctypes.c_int, ctypes.c_int,
                                ctypes.c_int)
    cdll.cpu_cycles.restype = ctypes.c_longlong
    
    def cpu_cycles(duration=1.0, pid=0, cpu=-1,
                    exclude_user=False, exclude_kernel=False,
                    exclude_hv=True, exclude_idle=True):
        """
        See man page of perf_event_open for all the parameters.
    
        :param duration: duration of counting cpu_cycles [seconds]
        :type duration: float
        :returns: cpu-cycle count of pid
        :rtype: int
        """
        count = cdll.cpu_cycles(int(duration*1000000), pid, cpu,
                                exclude_user, exclude_kernel,
                                exclude_hv, exclude_idle)
        if count < 0:
                    raise OSError("cpu_cycles(pid={}, duration={}) from {} exited with code {}.".format(
                        pid, duration, cdll._name, count))
    
        return count
    

    【讨论】:

      【解决方案2】:

      最后,我通过perf 命令行工具读取 CPU 周期并打包到 Python 中(简化代码):

      import subprocess
      maximum_cpu_frequency = 3e9
      cpu_percent = []
      while True:    # some stop criteria
          try:
              cpu_percent.append(int(
                      subprocess.check_output(["perf", "stat", "-e", "cycles",
                              "-p", pid, "-x", ",", "sleep", "1"],
                              stderr=subprocess.STDOUT).decode().split(",")[0]
                      )/maximum_cpu_frequency)
          except ValueError:
              cpu_percent.append(0.0)
      

      不幸的是,由于sleep 命令不准确,这并不准确,并且由于为每个样本生成一个新的perf 进程而效率很高。

      【讨论】:

      • 如何在 C 中将其实现为共享库并在 Python 中通过 ctypes 使用它?
      • 库调用引入的开销是否比进程调用少?如果是这样,这可能是一种更有效的方法。不幸的是,它需要更多我不太熟悉的 C 语言实现。
      • 库调用引入的开销更少。每次需要值时,子进程调用都会启动整个外部进程并通过管道将结果作为字符串进行通信。共享库一次加载到当前进程中,结果在内存中传递。
      • @BlackJack,您能否在单独的答案中复制/草绘您使用最小库的方法,好吗?即使没有任何代码,我也愿意接受这个想法作为答案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-08
      • 1970-01-01
      • 2015-06-06
      • 1970-01-01
      • 2011-02-26
      • 1970-01-01
      相关资源
      最近更新 更多