返回

Python测量服务器进程最大内存使用量

Linux

使用 Python 测量服务器进程最大内存使用量

如何测量服务器进程从启动到收到 SIGINT 信号期间的最大内存使用量?这个问题在性能分析和资源监控中至关重要。本文将探讨使用 /usr/bin/time 命令以及 Python 子进程模块实现该目标的方法,并分析可能遇到的问题和对应的解决方案。

/usr/bin/time 和 Python 子进程

/usr/bin/time 是一个实用工具,可以跟踪程序的执行时间和资源使用情况。结合 -f %M 选项,可以获取进程的最大常驻内存集大小(Maximum Resident Set Size)。配合 Python 的 subprocess 模块,我们可以方便地启动和管理子进程。

然而,直接使用 subprocess.Popen 并通过 communicate() 方法获取输出,在目标进程需要通过信号终止时,可能会遇到无法获取 stderr 输出的问题。这是因为 /usr/bin/time 将程序的输出信息写入到标准错误流,而直接向子进程发送 SIGINT 信号并调用 communicate() 方法,会导致缓存区无法及时刷新,从而丢失输出。

解决方案:使用 select 监控输出

为了解决这个问题,可以使用 select 模块监控子进程的输出流。select 可以帮助我们判断文件符是否可读,从而避免阻塞等待。

import subprocess
import select
import signal
import os

def measure_max_memory(command):
    process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    # poll object for process
    poll = select.poll()
    poll.register(process.stderr, select.POLLIN)

    max_memory = ""
    while True:
        if (process.poll() is not None): #process has terminated
             break
        #check if the fd is ready, otherwise check back soon (no blocking, 0 timeout)
        if poll.poll(0):  #check for output available with non blocking, 0 timeout, check back every little loop to break condition immediately after SIGINT signal is sent
            line = process.stderr.readline().decode().strip()
            # Filter lines, keep the lines for peak memory usage to be returned, skip rest
            try: # Check if number - safe to parse for result
              int(line) 
              max_memory=line  #assume multiple output only maximum rss usage as last line
            except:  #pass over rest, for /usr/bin/time non memory related loggin info lines
               pass   


        # Simulate waiting for external event and trigger
        # ... Your logic to determine when to send SIGINT ...

        # Sending signal after some condition, otherwise the process stays up running
        process.send_signal(signal.SIGINT) 

    return int(max_memory)



if __name__ == "__main__":
  # Example:
  command = ["/usr/bin/time", "-f", "%M", "python", "./terminate-me.py"] #command is composed as a list with script parameters and program name as in your question

  try: 
    max_mem = measure_max_memory(command) #use command to get a test value from the external example, assuming maximum mem will never be higher than MAXINT for python programs, check program behaviour before implementation if in other edge cases return codes are prefered
    print(f"Maximum memory usage: {max_mem} KB")

  except KeyboardInterrupt: #use KeyboardInterrupt Ctrl+c to break main script as well or remove for indefinite wait inside measure_max_memory() function in case there is another breaking point for wait to exit in the real server program measurement

       print("Main script interrupted by CTRL + c")

  except:
       print ("Memory data read and parse was interrupted")

上述代码的关键在于使用 select.poll 持续监控子进程的标准错误流(stderr)。当 poll() 返回非空值时,表明 stderr 中有数据可读,从而可以获取 /usr/bin/time 的输出。

安全建议

  • 确保 /usr/bin/time 的路径正确。
  • 对于长时间运行的进程,可以考虑设置超时机制,避免无限等待。
  • 处理潜在的异常,例如子进程启动失败或输出解析错误。
  • 注意目标程序的输出格式,根据实际情况调整解析逻辑。例如:对于含有其他输出信息的情况,需要过滤无关行,并获取正确的值。

通过结合 /usr/bin/time、Python subprocess 模块以及 select 机制,我们可以有效地测量服务器进程的最大内存使用量,从而为性能优化和资源管理提供重要依据。