使用 jemalloc profile memory

對于長時間運行的服務(wù)端程序,內(nèi)存的使用一直是一個非常重要的監(jiān)控指標(biāo),當(dāng)內(nèi)存的使用量一直在上升的時候词裤,我們就需要警覺起來,因為很有可能整個系統(tǒng)出現(xiàn)了內(nèi)存泄露鳖宾。那么剩下的問題就比較簡單了吼砂,如何動態(tài)的獲知哪里有內(nèi)存泄露呢?

對 Go 的程序來說鼎文,我們可以使用語言內(nèi)置的 pprof 工具非常方便的對內(nèi)存進(jìn)行 profile渔肩,我們只需要在程序里面 import _ "net/http/pprof",這樣啟動的 HTTP server 服務(wù)器就能夠被直接 profile 了拇惋。

但對 Rust周偎,情況就沒那么簡單了抹剩。因為語言并沒有內(nèi)置這個功能,所以我們得想其他辦法來解決蓉坎。Rust 默認(rèn)使用的是 jemalloc 這個內(nèi)存分配器澳眷,jemalloc 提供了非常方便的 profile 功能。所以我們自然將目光放在了如何用 jemalloc 來 profile memory 以及如何與 Rust 整合上面了蛉艾。

要打開 jemalloc 的 profile 功能钳踊,在編譯的時候我們需要顯示的帶上 --enable-prof 選項,通常在 Linux 下面我們會安裝 libunwind 庫勿侯,這樣 prof 默認(rèn)就會使用 libunwind 了拓瞪。另外,為了不跟系統(tǒng)的 malloc 這些函數(shù)有命名沖突助琐,這里顯示的給 jemalloc 加上了前綴祭埂,使用 --with-jemalloc-prefix="je_",這樣我們外面就會使用 je_malloc 這種的函數(shù)名字了弓柱。

我們用官網(wǎng)非常簡單的例子來說明內(nèi)存泄露問題沟堡,如下:

void do_something(size_t i)
{
    // Leak some memory.
    je_malloc(i * 100);
}

上面的函數(shù)有一個典型的內(nèi)存泄漏,我們調(diào)用 1000 次:

for (i = 0; i < 1000; i++) {
    do_something(i);
}

剩下的就是如何來定位內(nèi)存問題了矢空。

Mem Statistics

首先我們來看看 jemalloc 自己提供的統(tǒng)計信息航罗,我們可以直接使用 je_malloc_stats_print(NULL, NULL, NULL) 來將 memory 的統(tǒng)計輸出到 stderr 上面,但這個函數(shù)輸出的東西比較多屁药,并不利于實時的查看粥血。多數(shù)時候,我們都是使用 je_mallctl 函數(shù)酿箭,得到一些關(guān)鍵的統(tǒng)計數(shù)據(jù)复亏,然后發(fā)送給 Prometheus 來展示,這樣我們就能夠在 Prometheus 里面觀察到整個 jemalloc 內(nèi)存變化的曲線缭嫡,如果持續(xù)上升缔御,就需要報警了。

uint64_t epoch = 1;
size_t sz = sizeof(epoch);
je_mallctl("epoch", &epoch, &sz, &epoch, sz);

size_t allocated, active, mapped;
sz = sizeof(size_t);
je_mallctl("stats.allocated", &allocated, &sz, NULL, 0);
je_mallctl("stats.active", &active, &sz, NULL, 0);
je_mallctl("stats.mapped", &mapped, &sz, NULL, 0);

printf("allocated/active/mapped: %zu/%zu/%zu\n", allocated, active, mapped);

上面我們在每次 do_something 后面得到 allocated妇蛀,active 以及 mapped 這些指標(biāo)耕突,然后輸出:

allocated/active/mapped: 54919648/58540032/64831488
allocated/active/mapped: 55034336/58658816/64950272
allocated/active/mapped: 55149024/58777600/65069056
allocated/active/mapped: 55263712/58896384/65187840

上面需要注意,我們需要用 epoch 來讓統(tǒng)計的 cache 更新评架。

Leak Check

通過統(tǒng)計眷茁,我們能看到整個內(nèi)存的變化曲線,但到底哪里有內(nèi)存問題呢纵诞?我們可以在程序結(jié)束的時候顯示的輸出內(nèi)存泄露上祈。仍然使用上面的程序,我們使用 JE_MALLOC_CONF="prof_leak:true,lg_prof_sample:0,prof_final:true" ./leak 來執(zhí)行,當(dāng)程序退出之后登刺,會生成一個 prof heap 的文件籽腕,我們用 jeprof 工具就可以知道內(nèi)存泄露了。

jeprof leak jeprof.9001.0.f.heap

Using local file leak.
Using local file jeprof.9001.0.f.heap.
Welcome to jeprof!  For help, type 'help'.
(jeprof) top
Total: 52.1 MB
    52.1 100.0% 100.0%     52.1 100.0% je_prof_backtrace
    0.0   0.0% 100.0%     52.1 100.0% __libc_start_main
    0.0   0.0% 100.0%     52.1 100.0% _start
    0.0   0.0% 100.0%     52.1 100.0% do_something
    0.0   0.0% 100.0%     52.1 100.0% imalloc (inline)
    0.0   0.0% 100.0%     52.1 100.0% imalloc_body (inline)
    0.0   0.0% 100.0%     52.1 100.0% je_malloc
    0.0   0.0% 100.0%     52.1 100.0% je_prof_alloc_prep (inline)
    0.0   0.0% 100.0%     52.1 100.0% main

Heap Profiling

使用上面的方式塘砸,我們只能在程序結(jié)束的時候輸出內(nèi)存泄露节仿,實際并不適用于長時間運行的程序晤锥,幸運的時候掉蔬,我們可以通過 jemalloc 的一些參數(shù)以及 mallctl 函數(shù)來顯示的對內(nèi)存進(jìn)行 profile。在運行程序之前矾瘾,我們需要設(shè)置 export JE_MALLOC_CONF="prof:true,prof_prefix:jeprof.out"女轿,它用來告訴 jemalloc 顯示的打開 prof,同時自動的生成 profile 文件名壕翩。

在代碼里面蛉迹,我們可以使用 mallctl("prof.dump", NULL, NULL, NULL, 0); 來對當(dāng)前執(zhí)行的程序生成一個 mem dump,然后過一段時間之后放妈,用相同的方法再次生成一個北救,在用 jeprof 工具對比兩次的 dump,就大概能知道是否有內(nèi)存問題了芜抒。

具體到上面的例子珍策,我們在程序的開始和結(jié)束都使用 mallctl dump 一次 memory,然后對兩次生成的 profile 文件進(jìn)行對比:

jeprof --base=jeprof.out.19792.0.m0.heap profile jeprof.out.19792.1.m1.heap

Using local file profile.
Using local file jeprof.out.19792.1.m1.heap.
Welcome to jeprof!  For help, type 'help'.
(jeprof) top
Total: 53.1 MB
    53.1 100.0% 100.0%     53.1 100.0% je_prof_backtrace
    0.0   0.0% 100.0%     53.1 100.0% __libc_start_main
    0.0   0.0% 100.0%     53.1 100.0% _start
    0.0   0.0% 100.0%     53.1 100.0% do_something
    0.0   0.0% 100.0%     53.1 100.0% imalloc (inline)
    0.0   0.0% 100.0%     53.1 100.0% imalloc_body (inline)
    0.0   0.0% 100.0%     53.1 100.0% je_malloc
    0.0   0.0% 100.0%     53.1 100.0% je_prof_alloc_prep (inline)
    0.0   0.0% 100.0%     53.1 100.0% main

Rust Customized Allocator

上面說完了在 C 里面使用 jemalloc 來看內(nèi)存問題宅倒,那么對于 Rust 語言來說攘宙,我們怎么處理呢?Rust 默認(rèn)使用的就是 jemalloc拐迁,但發(fā)布的版本里面 jemalloc 并沒有帶上 profile 的功能蹭劈,所以需要重新編譯 Rust,對于我們來說线召,因為要實時的跟進(jìn) Rust 的版本铺韧,這并不是一個好辦法。

幸運的是缓淹,Rust 提供了 custom allocator 的功能哈打,也就是能使用自定義的 allocator,這樣對我們來說就簡單很多了割卖,使用一個打開了 profile 功能的 jemalloc 用作自定義的 allocator前酿,這樣就能通過 mallctl 來 profile memory 了。更幸運的是鹏溯,Rust 的一個開發(fā)者已經(jīng)提供了相關(guān)的 allocator罢维,我們可以直接使用。

我們可以構(gòu)造一個非常簡單的 case,使用 mem::forget

fn do_something()
{
   let mut bad_vec = Vec::new();
   for _ in 0..1024 {
       bad_vec.push('0');
   }
   mem::forget(bad_vec);
}

在這個函數(shù)前后肺孵,我們都使用 mallctl 匀借,如下:

let epoch_name = "prof.dump";
let epoch_c_name = CString::new(epoch_name).unwrap();
mallctl(epoch_c_name.as_ptr(), null_mut(), null_mut(), null_mut(), 0);

執(zhí)行之后,就會生成兩個 profile 文件平窘,使用 jeprof 之后吓肋,得到:

(jeprof) top
Total: 0.5 MB
     0.5 100.0% 100.0%      0.5 100.0% jemallocator::__rust_reallocate
     0.0   0.0% 100.0%      0.5 100.0% __libc_start_main
     0.0   0.0% 100.0%      0.5 100.0% _start
     0.0   0.0% 100.0%      0.5 100.0% alloc::heap::reallocate::h1264a9399460da6c
     0.0   0.0% 100.0%      0.5 100.0% alloc::raw_vec::{{impl}}::double
     0.0   0.0% 100.0%      0.5 100.0% collections::vec::{{impl}}::push
     0.0   0.0% 100.0%      0.5 100.0% core::ops::FnOnce::call_once (inline)
     0.0   0.0% 100.0%      0.5 100.0% main
     0.0   0.0% 100.0%      0.5 100.0% my_allocator::do_something::h4ffe20b1f68a3f80
     0.0   0.0% 100.0%      0.5 100.0% my_allocator::main::hffe46171bdd5ea12

小結(jié)

內(nèi)存問題一直是長時間運行程序需要處理的一個棘手問題,雖然 Rust 相比 C 以及 CPP瑰艘,在內(nèi)存處理上面有了很大的改善是鬼,但我們?nèi)匀豢赡軙幸眯孤兜葐栴}出現(xiàn),這些問題很難通過直接瀏覽代碼紫新,看 log 和 metrics 來看出來的均蜜,而 profile 恰恰能很好的解決,所以這也是我們一直想在 TiKV 上面加入 profile memory 的原因芒率。

需要注意囤耳,加入 profile 之后,會影響系統(tǒng)的性能偶芍,所以通常充择,我們都會采用 sample 的方式或者動態(tài)的打開或者關(guān)閉 profile 功能。譬如匪蟀,假設(shè)我們要 profile memory椎麦,就使用 mallctlprof.active 打開,一段時間萄窜,在使用 mallctl dump 出 memory铃剔,然后在關(guān)閉 profile。

另外查刻,除了 jemalloc键兜,其實 tcmalloc 也照樣能支持 profile memory,只是因為 Rust 默認(rèn)使用的是 jemalloc穗泵,我們最終我們還是決定基于 jemalloc 來使用普气。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市佃延,隨后出現(xiàn)的幾起案子现诀,更是在濱河造成了極大的恐慌,老刑警劉巖履肃,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仔沿,死亡現(xiàn)場離奇詭異,居然都是意外死亡尺棋,警方通過查閱死者的電腦和手機封锉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人成福,你說我怎么就攤上這事碾局。” “怎么了奴艾?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵净当,是天一觀的道長。 經(jīng)常有香客問我蕴潦,道長像啼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任品擎,我火速辦了婚禮埋合,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘萄传。我一直安慰自己,他們只是感情好蜜猾,可當(dāng)我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布秀菱。 她就那樣靜靜地躺著,像睡著了一般蹭睡。 火紅的嫁衣襯著肌膚如雪衍菱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天肩豁,我揣著相機與錄音脊串,去河邊找鬼。 笑死清钥,一個胖子當(dāng)著我的面吹牛琼锋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播祟昭,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼缕坎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了篡悟?” 一聲冷哼從身側(cè)響起谜叹,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎搬葬,沒想到半個月后荷腊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡急凰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年女仰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡董栽,死狀恐怖码倦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锭碳,我是刑警寧澤袁稽,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站擒抛,受9級特大地震影響推汽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜歧沪,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一歹撒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧诊胞,春花似錦暖夭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至邪码,卻和暖如春裕菠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背闭专。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工奴潘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人影钉。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓画髓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親斧拍。 傳聞我的和親對象是個殘疾皇子雀扶,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容