python內(nèi)存泄漏排查技巧總結(jié)
首先搞清楚了本次問題的現(xiàn)象:
- 1. 服務(wù)在13號(hào)上線過一次,而從23號(hào)開始,出現(xiàn)內(nèi)存不斷攀升問題,達(dá)到預(yù)警值重啟實(shí)例后,攀升速度反而更快。
- 2. 服務(wù)分別部署在了A、B 2種芯片上,但除模型推理外,幾乎所有的預(yù)處理、后處理共享一套代碼。而B芯片出現(xiàn)內(nèi)存泄漏警告,A芯片未出現(xiàn)任何異常。
思路一:研究新舊源碼及二方庫(kù)依賴差異
根據(jù)以上兩個(gè)條件,首先想到的是13號(hào)的更新引入的問題,而更新可能來自兩個(gè)方面:
- 自研代碼
- 二方依賴代碼
從上述兩個(gè)角度出發(fā):
- 一方面,分別用
Git
歷史信息和BeyondCompare
工具對(duì)比了兩個(gè)版本的源碼,并重點(diǎn)走讀了下A、B兩款芯片代碼單獨(dú)處理的部分,均未發(fā)現(xiàn)任何異常。 - 另一方面,通過pip list命令對(duì)比兩個(gè)鏡像包中的二方包,發(fā)現(xiàn)僅有pytz時(shí)區(qū)工具依賴的版本有變化。
經(jīng)過研究分析,認(rèn)為此包導(dǎo)致的內(nèi)存泄漏的可能性不大,因此暫且放下
至此,通過研究新舊版本源碼變化找出內(nèi)存泄漏問題這條路,似乎有點(diǎn)走不下去了。
思路二:監(jiān)測(cè)新舊版本內(nèi)存變化差異
目前python常用的內(nèi)存檢測(cè)工具有pympler
、objgraph
、tracemalloc
等。
首先,通過objgraph工具,對(duì)新舊服務(wù)中的TOP50變量類型進(jìn)行了觀察統(tǒng)計(jì)
objraph常用命令如下:
# 全局類型數(shù)量 objgraph.show_most_common_types(limit=50) # 增量變化 objgraph.show_growth(limit=30)
這里為了更好的觀測(cè)變化曲線,我簡(jiǎn)單做了個(gè)封裝,使數(shù)據(jù)直接輸出到了csv文件以便觀察。
stats = objgraph.most_common_types(limit=50) stats_path = "./types_stats.csv" tmp_dict = dict(stats) req_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) tmp_dict['req_time'] = req_time df = pd.DataFrame.from_dict(tmp_dict, orient='index').T if os.path.exists(stats_path): df.to_csv(stats_path, mode='a', header=True, index=False) else: df.to_csv(stats_path, index=False)
如下圖所示,用一批圖片在新舊兩個(gè)版本上跑了1個(gè)小時(shí),一切穩(wěn)如老狗,各類型的數(shù)量沒有一絲波瀾。
此時(shí),想到自己一般在轉(zhuǎn)測(cè)或上線前都會(huì)將一批異常格式的圖片拿來做個(gè)邊界驗(yàn)證。
雖然這些異常,測(cè)試同學(xué)上線前肯定都已經(jīng)驗(yàn)證過了,但死馬當(dāng)成活馬醫(yī)就順手拿來測(cè)了一下。
平靜數(shù)據(jù)就此被打破了,如下圖紅框所示:dict
、function
、method
、tuple
、traceback
等重要類型的數(shù)量開始不斷攀升。
而此時(shí)鏡像內(nèi)存亦不斷增加且毫無收斂跡象。
由此,雖無法確認(rèn)是否為線上問題,但至少定位出了一個(gè)bug
。而此時(shí)回頭檢查日志,發(fā)現(xiàn)了一個(gè)奇怪的現(xiàn)象:
正常情況下特殊圖片導(dǎo)致的異常,日志應(yīng)該輸出如下信息,即check_image_type
方法在異常棧中只會(huì)打印一次。
但現(xiàn)狀是check_image_type
方法循環(huán)重復(fù)打印了多次,且重復(fù)次數(shù)隨著測(cè)試次數(shù)在一起變多。
重新研究了這塊兒的異常處理代碼。
異常聲明如下:
拋異常代碼如下:
問題所在
思考后大概想清楚了問題根源:
這里每個(gè)異常實(shí)例相當(dāng)于被定義成了一個(gè)全局變量,而在拋異常的時(shí)候,拋出的也正是這個(gè)全局變量。當(dāng)此全局變量被壓入異常棧處理完成之后,也并不會(huì)被回收。
因此隨著錯(cuò)誤格式圖片調(diào)用的不斷增多,異常棧中的信息也會(huì)不斷增多。而且由于異常中還包含著請(qǐng)求圖片信息,因此內(nèi)存會(huì)呈MB級(jí)別的增加。
但這部分代碼上線已久,線上如果真的也是這里導(dǎo)致的問題,為何之前沒有任何問題,而且為何在A芯片上也沒有出現(xiàn)任何問題?
帶著以上兩個(gè)疑問,我們做了兩個(gè)驗(yàn)證:
首先,確認(rèn)了之前的版本以及A芯片上同樣會(huì)出現(xiàn)此問題。
其次,我們查看了線上的調(diào)用記錄,發(fā)現(xiàn)最近剛好新接入了一個(gè)客戶,而且出現(xiàn)了大量使用類似問題的圖片調(diào)用某局點(diǎn)(該局點(diǎn)大部分為B芯片)服務(wù)的現(xiàn)象。我們找了些線上實(shí)例,從日志中也觀測(cè)到了同樣的現(xiàn)象。
由此,以上疑問基本得到了解釋,修復(fù)此bug后,內(nèi)存溢出問題不再出現(xiàn)。
進(jìn)階思路
講道理,問題解決到這個(gè)地步似乎可以收工了。但我問了自己一個(gè)問題,如果當(dāng)初沒有打印這一行日志,或者開發(fā)人員偷懶沒有把異常棧全部打出來,那應(yīng)該如何去定位?
帶著這樣的問題我繼續(xù)研究了下objgraph
、pympler
工具。
前文已經(jīng)定位到了在異常圖片情況下會(huì)出現(xiàn)內(nèi)存泄漏,因此重點(diǎn)來看下此時(shí)有哪些異樣情況:
通過如下命令,我們可以看到每次異常出現(xiàn)時(shí),內(nèi)存中都增加了哪些變量以及增加的內(nèi)存情況。
1.使用objgraph工具
objgraph.show_growth(limit=20)
2.使用pympler工具
from pympler import tracker tr = tracker.SummaryTracker() tr.print_diff()
通過如下代碼,可以打印出這些新增變量來自哪些引用,以便進(jìn)一步分析。
gth = objgraph.growth(limit=20) for gt in gth: logger.info("growth type:%s, count:%s, growth:%s" % (gt[0], gt[1], gt[2])) if gt[2] > 100 or gt[1] > 300: continue objgraph.show_backrefs(objgraph.by_type(gt[0])[0], max_depth=10, too_many=5, filename="./dots/%s_backrefs.dot" % gt[0]) objgraph.show_refs(objgraph.by_type(gt[0])[0], max_depth=10, too_many=5, filename="./dots/%s_refs.dot" % gt[0]) objgraph.show_chain( objgraph.find_backref_chain(objgraph.by_type(gt[0])[0], objgraph.is_proper_module), filename="./dots/%s_chain.dot" % gt[0] )
通過graphviz
的dot工具,對(duì)上面生產(chǎn)的graph格式數(shù)據(jù)轉(zhuǎn)換成如下圖片:
dot -Tpng xxx.dot -o xxx.png
這里,由于dict
、list
、frame
、tuple
、method
等基本類型數(shù)量太多,觀測(cè)較難,因此這里先做了過濾。
內(nèi)存新增的ImageReqWrapper
的調(diào)用鏈
內(nèi)存新增的traceback的調(diào)用鏈:
雖然帶著前面的先驗(yàn)知識(shí),使我們很自然的就關(guān)注到了traceback和其對(duì)應(yīng)的IMAGE_FORMAT_EXCEPTION異常。
但通過思考為何上面這些本應(yīng)在服務(wù)調(diào)用結(jié)束后就被回收的變量卻沒有被回收,尤其是所有的traceback變量在被IMAGE_FORMAT_EXCEPTION
異常調(diào)用后就無法回收等這些現(xiàn)象;同時(shí)再做一些小實(shí)驗(yàn),相信很快就能定位到問題根源。
另,關(guān)于 python3中 緩存Exception導(dǎo)致的內(nèi)存泄漏問題,我們可以看看這篇文章:http://chabaoo.cn/article/231759.htm
至此,我們可以得出結(jié)論如下:
由于拋出的異常無法回收,導(dǎo)致對(duì)應(yīng)的異常棧、請(qǐng)求體等變量都無法被回收,而請(qǐng)求體中由于包含圖片信息因此每次這類請(qǐng)求都會(huì)導(dǎo)致MB級(jí)別的內(nèi)存泄漏。
另外,研究過程中還發(fā)現(xiàn)python3
自帶了一個(gè)內(nèi)存分析工具tracemalloc
,通過如下代碼就可以觀察代碼行與內(nèi)存之間的關(guān)系,雖然可能未必精確,但也能大概提供一些線索。
import tracemalloc tracemalloc.start(25) snapshot = tracemalloc.take_snapshot() global snapshot gc.collect() snapshot1 = tracemalloc.take_snapshot() top_stats = snapshot1.compare_to(snapshot, 'lineno') logger.warning("[ Top 20 differences ]") for stat in top_stats[:20]: if stat.size_diff < 0: continue logger.warning(stat) snapshot = tracemalloc.take_snapshot()
到此這篇關(guān)于python內(nèi)存泄漏排查技巧總結(jié)的文章就介紹到這了,更多相關(guān)python內(nèi)存泄漏排查技巧內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
參考文章:
https://testerhome.com/articles/19870?order_by=created_at&
https://blog.51cto.com/u_3423936/3019476
https://segmentfault.com/a/1190000038277797
https://www.cnblogs.com/zzbj/p/13532156.html
https://drmingdrmer.github.io/tech/programming/2017/05/06/python-mem.html
https://zhuanlan.zhihu.com/p/38600861
相關(guān)文章
Pandas 對(duì)Dataframe結(jié)構(gòu)排序的實(shí)現(xiàn)方法
下面小編就為大家分享一篇Pandas 對(duì)Dataframe結(jié)構(gòu)排序的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-04-04python批量設(shè)置多個(gè)Excel文件頁(yè)眉頁(yè)腳的腳本
這篇文章主要介紹了python批量設(shè)置多個(gè)Excel文件頁(yè)眉頁(yè)腳的源碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03Django框架之登錄后自定義跳轉(zhuǎn)頁(yè)面的實(shí)現(xiàn)方法
這篇文章主要介紹了Django框架之登錄后自定義跳轉(zhuǎn)頁(yè)面的實(shí)現(xiàn)方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07python無法引用另一個(gè)文件夾的py文件問題及解決
這篇文章主要介紹了python無法引用另一個(gè)文件夾的py文件問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08Python實(shí)現(xiàn)微信消息防撤回功能的實(shí)例代碼
這篇文章主要介紹了Python實(shí)現(xiàn)微信消息防撤回 ,需要的朋友可以參考下2019-04-04利用Pycharm + Django搭建一個(gè)簡(jiǎn)單Python Web項(xiàng)目的步驟
這篇文章主要介紹了利用Pycharm + Django搭建一個(gè)簡(jiǎn)單Python Web項(xiàng)目的步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10分析Python編程時(shí)利用wxPython來支持多線程的方法
這篇文章主要介紹了Python編程時(shí)利用wxPython來支持多線程的方法,本文主要以開發(fā)GUI程序時(shí)做線程通訊作為一個(gè)示例來講解,需要的朋友可以參考下2015-04-04Python的進(jìn)程,線程和協(xié)程實(shí)例詳解
這篇文章主要為大家詳細(xì)介紹了Python進(jìn)程,線程和協(xié)程,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03