diff --git a/src/dataType/utString.erl b/src/dataType/utString.erl new file mode 100644 index 0000000..66d19ad --- /dev/null +++ b/src/dataType/utString.erl @@ -0,0 +1,31 @@ +-module(utString). + +%% API +-export([ + toLowerStr/1, + toUpperStr/1 +]). + +toLowerStr(List) when is_list(List) -> + [begin + case C >= $A andalso C =< $Z of + true -> + C - $A + $a; + _ -> + C + end + end || C <- List]; +toLowerStr(Bin) when is_binary(Bin) -> + toLowerStr(utTypeCast:toList(Bin)). + +toUpperStr(List) when is_list(List) -> + [begin + case C >= $a andalso C =< $z of + true -> + C - $a + $A; + _ -> + C + end + end || C <- List]; +toUpperStr(Bin) when is_binary(Bin) -> + toUpperStr(utTypeCast:toList(Bin)). diff --git a/src/dataType/utTuple.erl b/src/dataType/utTuple.erl new file mode 100644 index 0000000..38eae29 --- /dev/null +++ b/src/dataType/utTuple.erl @@ -0,0 +1,62 @@ +-module(utTuple). + +-define(LIST_TO_RECORD(RecName, List), list_to_tuple([RecName])). + +%% ==================================================================== +%% API functions +%% ==================================================================== +-export([ + copy_elements/2, + update/2, + update_counter/2, + filter_value/2, + update/3 +]). + +copy_elements(DestTuple, SrcTuple) -> + DestList = tuple_to_list(DestTuple), + SrcList = tuple_to_list(SrcTuple), + List = util_list:copy_elements(DestList, SrcList), + list_to_tuple(List). + +update([], _FunMap, Tuple) -> + Tuple; +update([{K, V} | T], FunMap, Tuple) -> + List = FunMap(K), + UpdateList = make_update_list(List, Tuple, []), + NewTuple = update1(UpdateList, V), + update(T, FunMap, NewTuple). + +update1([], V) -> + V; +update1([{K, R} | T], V) -> + NV = setelement(K, R, V), + update1(T, NV). + +make_update_list([], _R, List) -> + List; +make_update_list([K | T], R, List) -> + NR = element(K, R), + make_update_list(T, NR, [{K, R} | List]). + +update(Tuple, UpList) -> + lists:foldl(fun({Index, Value}, TupleAcc) -> + setelement(Index, TupleAcc, Value) + end, Tuple, UpList). + +update_counter(Tuple, UpList) -> + lists:foldl(fun({Index, Change}, TupleAcc) -> + Value = element(Index, TupleAcc), + setelement(Index, TupleAcc, Value + Change) + end, Tuple, UpList). + +filter_value(Tuple, Value) -> + List = tuple_to_list(Tuple), + ListNew = lists:map(fun(Val) -> + case Val == Value of + true -> undefined; + false -> Val + end + end, List), + list_to_tuple(ListNew). + diff --git a/src/srvNodeMgr/u.erl b/src/srvNodeMgr/u.erl index aa77c14..647218d 100644 --- a/src/srvNodeMgr/u.erl +++ b/src/srvNodeMgr/u.erl @@ -148,4 +148,64 @@ load([FileName | T]) -> a() -> c(), - u(). \ No newline at end of file + u(). + +%% @spec hotswap() -> ok +%% @doc 用于远程热更新 +hotswap(NodeArg) -> + Node = util_data:to_atom(hd(NodeArg)), + net_adm:ping(Node), + rpc:call(Node, ?MODULE, do_hotswap, []). + +do_hotswap() -> + CommandFile = "../../hotswap/hotswap_command.txt", + case filelib:is_file(CommandFile) andalso filelib:file_size(CommandFile) > 2 of + true -> + {{Y, M, D}, {H, I, S}} = erlang:localtime(), + TimeString = io_lib:format("[~w-~w-~w ~w:~w:~w]", [Y, M, D, H, I, S]), + try + info("===> command running ..."), + case file:open(CommandFile, read) of + {ok, IoDevice} -> + case file:eval(CommandFile) of + ok -> info("Update Time: ~s", [TimeString]); + {error, EvalErr} -> info("~s Eval Error:~w", [TimeString, EvalErr]) + end, + info("command:"), + ResultData = parse(IoDevice, <<>>), + case byte_size(ResultData) < 5 of + true -> info("Empty Command (<5byte): ~s", [TimeString]); + false -> info("=> ~s", [ResultData]) + end, + file:close(IoDevice), + file:delete(CommandFile); + {error, OpenErr} -> + info("~s Open Error:~w\n", [TimeString, OpenErr]) + end + catch T : X -> + info("~s Error: ~w : ~w\nUpdate Server Is Stopped!\n", [TimeString, T, X]) + end; + false -> ignore + end, + ok. + +parse(IoDevice, D) -> + case io:get_line(IoDevice, '') of + eof -> + %% info("\n--- DONE ---"), + D; + Data -> + %% io:format("=> ~s", [Data]), + parse(IoDevice, list_to_binary([D, Data])) + end. + +%% @doc 保证正确的工作路径 +%% @spec make_sure_working_dir() -> any(). +make_sure_working_dir() -> + Cwd = config:get_cwd(), + case file:get_cwd() == Cwd of + true -> + skip; + _ -> + c:cd(Cwd) + end. \ No newline at end of file diff --git a/src/srvNodeMgr/utVMInfo.erl b/src/srvNodeMgr/utVMInfo.erl new file mode 100644 index 0000000..c14278a --- /dev/null +++ b/src/srvNodeMgr/utVMInfo.erl @@ -0,0 +1,167 @@ +-module(utVMInfo). + +-compile([export_all, nowarn_export_all]). + +%% 打印并排序各个表的缓存消耗 +show_cache() -> + io:format("table name | memory | size~n", []), + lists:reverse(lists:keysort(2, [{T, ets:info(T, memory), ets:info(T, size)} || T <- ets:all()])). + +%% 打印进程消耗内存的信息 +show_process() -> + lists:reverse(lists:keysort(2, [{erlang:process_info(P, registered_name), erlang:process_info(P, heap_size)} || P <- erlang:processes()])). + +%% 打印当前进程数量 +show_process_count() -> + length(erlang:processes()). + +%% 反编译 +%% 确认线上运行代码是否正确,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))]). + +%% 进程栈 +%% 类似于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))]). + +%% ==================================================================== +%% 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(). +%% ==================================================================== + +%% 对所有process做gc +%% 进程内存过高时,来一发,看看是内存泄露还是gc不过来 +gc_all() -> + [erlang:garbage_collect(Pid) || Pid <- processes()]. + +%% 对MFA 执行分析,会严重减缓运行,建议只对小量业务执行 +%% 结果: +%% fprof 结果比较详细,能够输出热点调用路径 +fprof(M, F, A) -> + fprof:start(), + fprof:apply(M, F, A), + fprof:profile(), + fprof:analyse(), + fprof:stop(). + +%% 对整个节点内所有进程执行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(). + +%% 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]]). + +%% 进程调度 +%% 统计下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)). + +%% ==================================================================== +%% 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(). +%% ==================================================================== + +%% 内存高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)}. + +%% ==================================================================== +%% Internal functions +%% ==================================================================== + +