系統級別perf top, dstat -tam, vtune 都能很好分析beam 瓶頸,本文主要erlang 級別排查:
1. 反編譯
確認線上運行代碼是否正確,reltools沒掌握好,升級偶爾出現問題
decompile(Mod) -> {ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(code:which(Mod), [abstract_code]), io:format("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).
2. 進程棧
類似於jstack,發現大量進程掛起,進程數過高,運行慢,hang住等問題用到
pstack(Reg) when is_atom(Reg) -> case whereis(Reg) of undefined -> undefined; Pid -> pstack(Pid) end; pstack(Pid) -> io:format("~s~n", [element(2, process_info(Pid, backtrace))]).
3. etop
分析內存、cpu占用進程,即使數十w進程node 也能正常使用
%進程CPU占用排名 etop() -> spawn(fun() -> etop:start([{output, text}, {interval, 10}, {lines, 20}, {sort, reductions}]) end). %進程Mem占用排名 etop_mem() -> spawn(fun() -> etop:start([{output, text}, {interval, 10}, {lines, 20}, {sort, memory}]) end). %停止etop etop_stop() -> etop:stop().
4. gc all
進程內存過高時,來一發,看看是內存泄露還是gc不過來
% 對所有process做gc gc_all() -> [erlang:garbage_collect(Pid) || Pid <- processes()].
5. fprof
% 對MFA 執行分析,會嚴重減緩運行,建議只對小量業務執行 % 結果: % fprof 結果比較詳細,能夠輸出熱點調用路徑 fprof(M, F, A) -> fprof:start(), fprof:apply(M, F, A), fprof:profile(), fprof:analyse(), fprof:stop().
6. eprof
% 對整個節點內所有進程執行eprof, eprof 對線上業務有一定影響,慎用! % 建議TimeoutSec<10s,且進程數< 1000,否則可能導致節點crash % 結果: % 輸出每個方法實際執行時間(不會累計方法內其他mod調用執行時間) % 只能得到mod - Fun 執行次數 執行耗時 eprof_all(TimeoutSec) -> eprof(processes() -- [whereis(eprof)], TimeoutSec). eprof(Pids, TimeoutSec) -> eprof:start(), eprof:start_profiling(Pids), timer:sleep(TimeoutSec), eprof:stop_profiling(), eprof:analyze(total), eprof:stop().
7. scheduler usage
% 統計下1s每個調度器CPU的實際利用率(因為有spin wait、調度工作, 可能usage 比top顯示低很多) scheduler_usage() -> scheduler_usage(1000). scheduler_usage(RunMs) -> erlang:system_flag(scheduler_wall_time, true), Ts0 = lists:sort(erlang:statistics(scheduler_wall_time)), timer:sleep(RunMs), Ts1 = lists:sort(erlang:statistics(scheduler_wall_time)), erlang:system_flag(scheduler_wall_time, false), Cores = lists:map(fun({{I, A0, T0}, {I, A1, T1}}) -> {I, (A1 - A0) / (T1 - T0)} end, lists:zip(Ts0, Ts1)), {A, T} = lists:foldl(fun({{_, A0, T0}, {_, A1, T1}}, {Ai,Ti}) -> {Ai + (A1 - A0), Ti + (T1 - T0)} end, {0, 0}, lists:zip(Ts0, Ts1)), Total = A/T, io:format("~p~n", [[{total, Total} | Cores]]).
8. 進程調度
% 統計下1s內調度進程數量(含義:第一個數字執行進程數量,第二個數字遷移進程數量) scheduler_stat() -> scheduler_stat(1000). scheduler_stat(RunMs) -> erlang:system_flag(scheduling_statistics, enable), Ts0 = erlang:system_info(total_scheduling_statistics), timer:sleep(RunMs), Ts1 = erlang:system_info(total_scheduling_statistics), erlang:system_flag(scheduling_statistics, disable), lists:map(fun({{Key, In0, Out0}, {Key, In1, Out1}}) -> {Key, In1 - In0, Out1 - Out0} end, lists:zip(Ts0, Ts1)).
9. trace 日志
會把mod 每次調用詳細MFA log 下來,args 太大就不好看了
%trace Mod 所有方法的調用 trace(Mod) -> dbg:tracer(), dbg:tpl(Mod, '_', []), dbg:p(all, c). %trace Node上指定 Mod 所有方法的調用, 結果將輸出到本地shell trace(Node, Mod) -> dbg:tracer(), dbg:n(Node), dbg:tpl(Mod, '_', []), dbg:p(all, c). %停止trace trace_stop() -> dbg:stop_clear().
10. 內存高OOM 排查工具
etop 無法應對10w+ 進程節點, 下面代碼就沒問題了;找到可疑proc后通過pstack、message_queu_len 排查原因
proc_mem_all(SizeLimitKb) -> Procs = [{undefined, Pid} || Pid<- erlang:processes()], proc_mem(Procs, SizeLimitKb). proc_mem(SizeLimitKb) -> Procs = [{Name, Pid} || {_, Name, Pid, _} <- release_handler_1:get_supervised_procs(), is_process_alive(Pid)], proc_mem(Procs, SizeLimitKb). proc_mem(Procs, SizeLimitKb) -> SizeLimit = SizeLimitKb * 1024, {R, Total} = lists:foldl(fun({Name, Pid}, {Acc, TotalSize}) -> case erlang:process_info(Pid, total_heap_size) of {_, Size0} -> Size = Size0*8, case Size > SizeLimit of true -> {[{Name, Pid, Size} | Acc], TotalSize+Size}; false -> {Acc, TotalSize} end; _ -> {Acc, TotalSize} end end, {[], 0}, Procs), R1 = lists:keysort(3, R), {Total, lists:reverse(R1)}.