我如何找到生产系统中Python进程中正在使用内存的内容?


春风助手
2025-02-28 11:14:40 (3小时前)

我的生产系统偶尔会出现内存泄漏,而这是我在开发环境中无法复制的。我在开发环境中使用了Python内存事件探查器(特别是Heapy)并取得了一些成功,但是它无法帮助我解决无法重现的问题,并且我不愿意使用Heapy来检测生产系统需要花点时间来完成它的工作,并且它的线程化远程接口在我们的服务器中无法正常工作。

我想我想要的是一种转储生产Python进程(或至少gc.get_objects)快照,然后离线分析快照以查看其在哪里使用内存的方法。 我如何获得像这样的python进程的核心转储? 一旦有了一个,我该如何做些有用的事情?

2 条回复
  1. 1# 只怕再见是故人 | 2020-08-24 14-37

    我将从最近的经历中进一步了解布雷特的回答。推土机包是 很好的维护,尽管进步,像添加tracemalloc在Python 3.4 STDLIB,其gc.get_objects计数图是我去到的工具来解决内存泄漏。在下面,我使用dozer > 0.7在撰写本文时尚未发布的内容(好吧,因为我最近在此处做出了一些修复)。


    让我们看一个不平凡的内存泄漏。我将在此处使用Celery 4.4,并将最终揭示导致泄漏的功能(由于这是一种bug /功能,可以将其称为纯粹的错误配置,由无知引起)。所以这是一个Python 3.6 VENV在哪里pip install celery < 4.5。并具有以下模块。

    演示

    1. import time
    2. import celery
    3. redis_dsn = 'redis://localhost'
    4. app = celery.Celery('demo', broker=redis_dsn, backend=redis_dsn)
    5. @app.task
    6. def subtask():
    7. pass
    8. @app.task
    9. def task():
    10. for i in range(10_000):
    11. subtask.delay()
    12. time.sleep(0.01)
    13. if __name__ == '__main__':
    14. task.delay().get()

    基本上是一个计划一堆子任务的任务。有什么问题吗?

    我将用于procpath分析Celery节点的内存消耗。pip install procpath。我有4个终端:

    procpath record -d celery.sqlite -i1 “$..children[?(‘celery’ in @.cmdline)]” 记录Celery节点的进程树统计信息
    docker run —rm -it -p 6379:6379 redis 运行Redis,它将充当Celery经纪人和结果后端
    celery -A demo worker —concurrency 2 用2个工人运行节点
    python demo.py 最终运行示例
    (4)将在2分钟内完成。

    然后,我使用Falcon SQL Client可视化procpath具有记录器的内容。我使用以下查询:

    SELECT datetime(ts, ‘unixepoch’, ‘localtime’) ts, stat_pid, stat_rss / 256.0 rss
    FROM record
    而在猎鹰我创建了一个折线图与跟踪X=ts,Y=rss并添加分流改造By=stat_pid。结果图为:

    芹菜节点泄漏

    对于那些与内存泄漏进行斗争的人来说,这种形状可能是非常熟悉的。

    寻找泄漏的物体
    现在是时候了dozer。我将展示无工具的情况(如果可以的话,您可以用类似的方式来检测代码)。要将Dozer服务器注入目标进程,我将使用Pyrasite。有两件事要知道:

    要运行它,必须将ptrace配置为“经典ptrace权限”:echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope,这可能会带来安全风险
    目标Python进程崩溃的可能性非零
    有了这个警告,我:

    1. pip install https://github.com/mgedmin/dozer/archive/3ca74bd8.zip (我上面提到的就是0.8)
    2. pip install pillowdozer用于制图)
    3. pip install pyrasite

    之后,我可以在目标进程中获取Python shell:

    1. pyrasite-shell 26572

    并注入以下内容,这将使用stdlib的wsgiref服务器运行Dozer的WSGI应用程序。

    1. import threading
    2. import wsgiref.simple_server
    3. import dozer
    4. def run_dozer():
    5. app = dozer.Dozer(app=None, path='/')
    6. with wsgiref.simple_server.make_server('', 8000, app) as httpd:
    7. print('Serving Dozer on port 8000...')
    8. httpd.serve_forever()
    9. threading.Thread(target=run_dozer, daemon=True).start()

    http://localhost:8000在浏览器中打开,应该看到类似以下内容的内容:

    推土机

    之后,我python demo.py再次从(4)运行并等待其完成。然后在推土机中,将“ Floor”设置为5000,这是我看到的内容:

    推土机显示芹菜泄漏

    随着子任务的调度,与Celery相关的两种类型有所增加:

    1. celery.result.AsyncResult
    2. vine.promises.promise

    weakref.WeakMethod 具有相同的形状和数字,并且必须由相同的事物引起。

    寻找根本原因
    此时,从泄漏类型和趋势来看,您的情况可能已经很清楚了。如果不是,则推土机每种类型都有“ TRACE”链接,该链接允许跟踪(例如,查看对象的属性)所选对象的引荐来源网址(gc.get_referrers)和引用对象(gc.get_referents),并继续遍历图形的过程。

    但是一张图片说一千个字吧?因此,我将展示如何用于objgraph呈现所选对象的依赖关系图。

    1. pip install objgraph
    2. apt-get install graphviz

    然后:

    我python demo.py再次从(4)开始
    在推土机我一套floor=0,filter=AsyncResult
    然后点击“ TRACE”
    跟踪

    然后在Pyrasite shell中运行:

    objgraph.show_backrefs([objgraph.at(140254427663376)], filename=’backref.png’)
    PNG文件应包含:

    反向参照图

    基本上,有些Context对象包含一个list称为的对象,而该对象_children又包含许多celery.result.AsyncResult泄漏的实例。Filter=celery.*context我看到的是推土机的变化:

    芹菜上下文

    因此,罪魁祸首是celery.app.task.Context。搜索该类型肯定会导致您进入Celery任务页面。在这里快速搜索“孩子”,它的意思是:

    trail = True

    如果启用,请求将跟踪由该任务启动的子任务,并且此信息将与结果(result.children)一起发送。

    通过设置trail=False如下来禁用跟踪:

    1. @app.task(trail=False)
    2. def task():
    3. for i in range(10_000):
    4. subtask.delay()
    5. time.sleep(0.01)

    然后从(3)python demo.py再次从(4)重新启动Celery节点,将显示此内存消耗。

    解决了

登录 后才能参与评论