-module(misc).

%%
%% Include files
%%
-include("common.hrl").
-include("cache.hrl").
-include("item.hrl").
-include_lib("kernel/include/file.hrl").
%%
%% Exported Functions
%%
-export([
   whereis_name/1,
   register/3,
   unregister/2,
   is_process_alive/1,
   create_process_name/2,
   to_atom/1,
   % t/1,
   % t/2,
   % tr/1,
   % tun/1,
   % tan/1,
   % t_clear/0,
   dump_process_info/1
   , u/1
   , hot/0
   , hot_beam/1  %% 直接加载更新变动beam文件，运营服使用
   , ets_mem/0
   , tcp_links/0
   , top_back/0
   , top/0
   , request/1
   , u_one/1
   , stop/0
   , del_ref/1
]).

-export([
   role_cmd/3
   , gm/2
   , check_mem/0
   , check_mem/1
   , online_num/0
   , is_cross/0
]).

-export([
   decompile/1
   , pstack/1
   , etop/0
   , etop_mem/0
   , etop_stop/0
   , gc_all/0

   , fprof/3
   , eprof_all/1
   , eprof/2

   , scheduler_usage/0
   , scheduler_usage/1

   , scheduler_stat/0
   , scheduler_stat/1

   , trace/1
   , trace/2
   , trace_stop/0

   , proc_mem_all/1
   , proc_mem/1

   , crash_dump/0
   , process_infos/0
]).

-export([check_item/0, ints2str/2]).

%%
%% API Functions
%%

%% @doc 删除ref
del_ref(Ref) when is_reference(Ref) ->
   erlang:cancel_timer(Ref);
del_ref(_) ->
   ok.

%% @doc 节点所有进程信息
process_infos() ->
   filelib:ensure_dir("./logs/"),
   File = "./logs/processes_infos.log",
   {ok, Fd} = file:open(File, [write, raw, binary, append]),
   Fun = fun(Pi) ->
      Info = io_lib:format("=>~p \n\n", [Pi]),
      case filelib:is_file(File) of
         true -> file:write(Fd, Info);
         false ->
            file:close(Fd),
            {ok, NewFd} = file:open(File, [write, raw, binary, append]),
            file:write(NewFd, Info)
      end,
      timer:sleep(20)
         end,
   [Fun(erlang:process_info(P)) || P <- erlang:processes()].


%% @doc erlang_dump
crash_dump() ->
   Date = erlang:list_to_binary(rfc1123_local_date()),
   Header = binary:list_to_bin([<<"=erl_crash_dump:0.2\n">>, Date, <<"\nSystem version: ">>]),
   Ets = ets_info(),
   Report = binary:list_to_bin([Header, erlang:list_to_binary(erlang:system_info(system_version)),
      erlang:system_info(info), erlang:system_info(procs), Ets, erlang:system_info(dist),
      <<"=loaded_modules\n">>, binary:replace(erlang:system_info(loaded),
         <<"\n">>, <<"\n=mod:">>, [global])]),
   file:write_file("erl_crash.dump", Report).

ets_info() ->
   binary:list_to_bin([ets_table_info(T) || T <- ets:all()]).

ets_table_info(Table) ->
   Info = ets:info(Table),
   Owner = erlang:list_to_binary(erlang:pid_to_list(proplists:get_value(owner, Info))),
   TableN = erlang:list_to_binary(erlang:atom_to_list(proplists:get_value(name, Info))),
   Name = erlang:list_to_binary(erlang:atom_to_list(proplists:get_value(name, Info))),
   Objects = erlang:list_to_binary(erlang:integer_to_list(proplists:get_value(size, Info))),
   binary:list_to_bin([<<"=ets:">>, Owner, <<"\nTable: ">>, TableN, <<"\nName: ">>, Name,
      <<"\nObjects: ">>, Objects, <<"\n">>]).

rfc1123_local_date() ->
   rfc1123_local_date(os:timestamp()).
rfc1123_local_date({A, B, C}) ->
   rfc1123_local_date(calendar:now_to_local_time({A, B, C}));
rfc1123_local_date({{YYYY, MM, DD}, {Hour, Min, Sec}}) ->
   DayNumber = calendar:day_of_the_week({YYYY, MM, DD}),
   lists:flatten(
      io_lib:format("~s, ~2.2.0w ~3.s ~4.4.0w ~2.2.0w:~2.2.0w:~2.2.0w GMT",
         [httpd_util:day(DayNumber), DD, httpd_util:month(MM), YYYY, Hour, Min, Sec]));
rfc1123_local_date(Epoch) when erlang:is_integer(Epoch) ->
   rfc1123_local_date(calendar:gregorian_seconds_to_datetime(Epoch + 62167219200)).

%% @doc 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(), erlang: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)}.

%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().

% 统计下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)).

% 统计下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]]).

% 对整个节点内所有进程执行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().

% @doc 对MFA 执行分析，会严重减缓运行，建议只对小量业务执行
% 结果:
% fprof 结果比较详细，能够输出热点调用路径
fprof(M, F, A) ->
   fprof:start(),
   fprof:apply(M, F, A),
   fprof:profile(),
   fprof:analyse([{dest, "fprof.analysis"}, {sort, own}]),
   fprof:stop().

% @doc 对所有process做gc
gc_all() ->
   [erlang:garbage_collect(Pid) || Pid <- processes()].

% @doc 进程CPU占用排名
etop() ->
   spawn(fun() -> etop:start([{output, text}, {interval, 10}, {lines, 20}, {sort, runtime}]) end).

% @doc 进程Mem占用排名
etop_mem() ->
   spawn(fun() -> etop:start([{output, text}, {interval, 10}, {lines, 20}, {sort, memory}]) end).

% @doc 停止etop
etop_stop() ->
   etop:stop().

%% @doc 类似于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))]).


%% @doc 检测占用总内存大运600M的全部强制GC
check_mem() ->
   Total = erlang:memory(total),
   case Total > 600000000 of
      true ->
         spawn(fun() -> [erlang:garbage_collect(Pid) || Pid <- processes()] end);
      _ ->
         ok
   end.
% check_mem(1000000000).

%% 检查溢出的内存，强制gc, 并写入日志分析
check_mem(MemLim) ->
   lists:foreach(
      fun(P) ->
         case is_pid(P) andalso erlang:is_process_alive(P) of
            true ->
               {memory, Mem} = erlang:process_info(P, memory),
               case Mem > MemLim of
                  true ->
                     erlang:garbage_collect(P);
                  false ->
                     []
               end;
            false ->
               []
         end
      end, erlang:processes()).

%% @doc 发送GM命令
gm(RoleId, Content) ->
   role_cmd(RoleId, 11600, [Content]).

%% @doc 发送玩家协议
role_cmd(RoleId, Cmd, Data) ->
   svr_role:apply(async, RoleId, {lib_role, async_cmd, [Cmd, Data]}).

%% @doc 关闭应用
stop() ->
   case is_cross() of
      true ->
         cross_stop();
      _ ->
         common_stop()
   end.

%% @doc 游戏服关闭
common_stop() ->
   AllRoles = ets:tab2list(?ETS_ONLINE),
   [Pid ! {'stop', server_stop} || #role{pid = Pid} <- AllRoles],%玩家下线
   cowboy:stop_listener(http),
   catch svr_cache_mgr:do_save(),
   catch svr_chat:save_chat(),
   catch svr_rank:hand_save(),
   catch svr_log:hand_save(),

   case catch check_item() of
      {'EXIT', Res} ->
         ?INF("error:~p", [Res]);
      _ ->
         ok
   end,


   ?INF("server_stop"),

   init:stop().

%% @doc 检测物品消失
check_item() ->
   List = ets:tab2list(?ETS_CACHE_ITEM_ROLE),
   check_item(List).

check_item([{RoleId, Ids = [_ | _]} | T]) ->
   SelectSql = io_lib:format(<<"select id from player_item where role_id=~p;">>, [RoleId]),
   IdVals = db:execute(SelectSql),
   case filter_item_ids(IdVals, Ids, []) of
      [_ | IdStrs] ->
         Sql = io_lib:format(<<"delete from player_item where id in (~s);">>, [IdStrs]),
         db:execute(Sql);
      _ ->
         ok
   end,

   save_eq(Ids),

   check_item(T);
check_item([_ | T]) ->
   check_item(T);
check_item([]) ->
   ok.

%% @doc 保存玩家装备信息
save_eq(Ids) ->
   case find_eq_item(Ids, []) of
      [_ | SqlVals] ->
         Sql = io_lib:format(?SQL_ITEM_BATCH_REPLACE, [SqlVals]),
         db:execute(Sql);
      _ ->
         ok
   end.

find_eq_item([Id | T], L) ->
   L1 =
      case svr_cache_item:find_item(Id) of
         Item = #item{slot = Slot} ->
            case Slot > 0 orelse lib_awake_equip:is_awake(Item) of
               true ->
                  "," ++ lib_item_dict:make_batch_insert_sql_one(Item) ++ L;
               _ ->
                  L
            end;
         _ ->
            L
      end,
   find_eq_item(T, L1);
find_eq_item([], L) ->
   L.

%% @doc 筛选物品
filter_item_ids([[Id] | T], Ids, L) ->
   L1 =
      case lists:member(Id, Ids) of
         false ->
            IdStr = integer_to_list(Id),
            "," ++ IdStr ++ L;
         _ ->
            L
      end,
   filter_item_ids(T, Ids, L1);
filter_item_ids([], _, L) ->
   L.

ints2str([Id | T], L) ->
   IdStr = integer_to_list(Id),
   case T of
      [] ->
         ints2str(T, IdStr ++ L);
      _ ->
         ints2str(T, "," ++ IdStr ++ L)
   end;
ints2str([], L) ->
   L.

%% @doc 中央服关闭
cross_stop() ->
   catch svr_kfboss_awake_mgr:stop(),
   catch svr_kfnode:stop(),
   init:stop().

%% @doc 是否是中央服
is_cross() ->
   case config:get_platform() of
      "cross" ->
         true;
      _ ->
         false
   end.

%% @doc 寻找PID
whereis_name({local, Atom}) ->
   erlang:whereis(Atom);

whereis_name({global, Atom}) ->
   global:whereis_name(Atom).

register(local, Name, Pid) ->
   erlang:register(Name, Pid);

register(global, Name, Pid) ->
   global:re_register_name(Name, Pid).

unregister(local, Name) ->
   erlang:unregister(Name);
unregister(global, Name) ->
   global:unregister_name(Name).

is_process_alive(Pid) ->
   try
      case Pid of
         _ when is_pid(Pid) ->
            Node = node(),
            Result = case node(Pid) of
                        Node -> erlang:is_process_alive(Pid);
                        Node1 -> rpc:call(Node1, erlang, is_process_alive, [Pid])
                     end,
            case Result of
               {badrpc, _Reason} -> false;
               Res -> Res
            end;
         _ -> false
      end
   catch
      _:_ -> false
   end.

create_process_name(Prefix, List) ->
   to_atom(lists:concat(lists:flatten([Prefix] ++ lists:map(fun(T) -> ['_', T] end, List)))).

to_atom(Msg) when is_atom(Msg) ->
   Msg;
to_atom(Msg) when is_binary(Msg) ->
   list_to_atom2(binary_to_list(Msg));
to_atom(Msg) when is_list(Msg) ->
   list_to_atom2(Msg);
to_atom(_) ->
   throw(other_value).  %%list_to_atom("").

list_to_atom2(List) when is_list(List) ->
   case catch (list_to_existing_atom(List)) of
      {'EXIT', _} -> erlang:list_to_atom(List);
      Atom when is_atom(Atom) -> Atom
   end.

%%------------------------------------------------
%% Admin API
%%------------------------------------------------
%% @spec hot/0 -> ok
%% @doc 编译源码并热更 TODO:会加载修改时间在六分钟之内的模块
%% @doc 开发环境这里需要编译，线上环境直接加载更新的beam文件
hot() ->
   HotTime = svr_handle_mgr:hot_time() - 360,
   Now = ?NOW,
   case max(0, Now - HotTime) of
      TimeLimit when TimeLimit > 0 ->
         hot_beam(TimeLimit),

         % 热更后需要刷新的活动
         svr_activity_fl:reflash(),

         ok;
      _ ->
         ok
   end.

hot_beam(TimeLimit) when TimeLimit > 0 ->
   Now = ?NOW,
   FilePaths = all_ebin_file(),
   io:format("-----------------------------------------------~n"),
   io:format("hots ~p ~p~n", [node(), config:server_ids()]),
   io:format("~n"),
   lists:foreach(fun(FilePath) ->
      case file:read_file_info(FilePath) of
         {ok, #file_info{mtime = Mtime}} ->
            Time = util:datetime_to_seconds(Mtime),
            case Now - Time =< TimeLimit of
               true ->
                  Module = filename:basename(FilePath, ".beam"),
                  u(list_to_atom(Module));
               _ ->
                  ok
            end;
         _ ->
            ok
      end
                 end, FilePaths),
   io:format("~n"),
   io:format("-----------------------------------------------~n");
hot_beam(_) ->
   ok.

%% @doc 获取代码所有
all_ebin_file() ->
   LogicNames = filelib:wildcard("../ebin/*.beam"),
   DepsNames = filelib:wildcard("../deps/*/ebin/*.beam"),
   DepsNames ++ LogicNames.

%% @doc 热更
u(M) when not is_list(M) ->
   u([M], []);
u(List) ->
   u(List, []).

u([], List) ->
   List;
u([Module | T], List) ->
   Ret = u_one(Module),
   u(T, [{Ret, Module} | List]).

u_one(Module) ->
   case c:nl(Module) of
      abcast ->
         ?PRINT("~w:load ~20w ok", [calendar:local_time(), Module]),
         ok;
      _ ->
         ?PRINT("~w:load ~20w fail", [calendar:local_time(), Module]),
         error
   end.

%% @spec top() -> ok
%% @doc 查看系统当前的综合信息
top() ->
   Release = erlang:system_info(otp_release),
   SchedNum = erlang:system_info(schedulers),
   ProcCount = erlang:system_info(process_count),
   ProcLimit = erlang:system_info(process_limit),
   ProcMemUsed = erlang:memory(processes_used),
   EtsMemAlc = erlang:memory(ets),
   MemTot = erlang:memory(total),
   RoleNum = ets:info(?ETS_ONLINE, size),
   %PetNum       = all_pets(),
   io:format(
      "++++++++++++++++++++++++++++++++++++++++++~n"
      "   ServerId:                         ~p~n"
      "   Node:                             ~p~n"
      "   Erlang Ver:                       ~p~n"
      "   Free Threads:                     ~p~n"
      "   Process Used Memory:              ~pMb~n"
      "   Ets Used Memory:                  ~pMb~n"
      "   Erlang VM Used Memory:            ~pMb~n"
      "   Process Limit:                    ~p~n"
      "   Process Used:                     ~p~n"
      "   Online Players:                   ~p~n"
      "++++++++++++++++++++++++++++++++++++++++++~n"
      , [config:server_ids(), node(), Release, SchedNum, ProcMemUsed / 1024 / 1024, EtsMemAlc / 1024 / 1024, MemTot / 1024 / 1024, ProcLimit, ProcCount, RoleNum]),
   ok.

%% @doc 运维要用
top_back() ->
   Release = erlang:system_info(otp_release),
   SchedNum = erlang:system_info(schedulers),
   ProcCount = erlang:system_info(process_count),
   ProcLimit = erlang:system_info(process_limit),
   ProcMemUsed = erlang:memory(processes_used),
   EtsMemAlc = erlang:memory(ets),
   MemTot = erlang:memory(total),
   RoleNum = ets:info(?ETS_ONLINE, size),
   Str = io_lib:format(
      "   Erlang 版本:                          ~p~n"
      "   可使用的调度线程:                     ~p~n"
      "   所有进程使用的内存:                   ~pMb~n"
      "   所有ets使用的内存:                    ~pMb~n"
      "   Erlang系统占用内存:                   ~pMb~n"
      "   可创建进程数量上限:                   ~p~n"
      "   当前进程数:                           ~p~n"
      "   在线角色数:                           ~p~n"
      , [Release, SchedNum, ProcMemUsed / 1024 / 1024, EtsMemAlc / 1024 / 1024, MemTot / 1024 / 1024, ProcLimit, ProcCount, RoleNum]),
   binary_to_list(list_to_binary(Str)).

%% @spec ets_mem() -> term()
%% @doc 查看内存占用最多的30张ets表
ets_mem() ->
   L = ets:all(),
   Mems = lists:map(fun(Tab) ->
      Info = ets:info(Tab),
      case lists:keyfind(memory, 1, Info) of
         {memory, Mem} -> {Tab, Mem};
         _ -> {Tab, 0}
      end
                    end, L),
   L1 = lists:sublist(lists:reverse(lists:keysort(2, Mems)), 30),
   ?PRINT("~n--------------------------------------------------~n"
   "~-30w ~w~n--------------------------------------------------~n"
      , [table, used_memory]),
   lists:foreach(fun({Tab, Mem}) ->
      ?PRINT("~-30w ~wKb~n", [Tab, Mem / 1024])
                 end, L1).

%% @spec tcp_links() -> Info
%% @doc 统计tcp链接
tcp_links() ->
   L = erlang:ports(),
   F = fun(P) ->
      Pinfo = erlang:port_info(P),
      case lists:keyfind(name, 1, Pinfo) of
         {name, "tcp_inet"} -> true;
         _ -> false
      end
       end,
   L1 = lists:filter(F, L),
   ?PRINT("~n当前socket数量(包括链接数据库的socket): ~w~n", [length(L1)]).

% %% @doc 根据账户名trace
% tan(AcountName) ->
% 	Bin = unicode:characters_to_binary(AcountName),
% 	[t(UserID)||#ets_user{account_name = Name, id = UserID} <- ets:tab2list(?ETS_ONLINE), Name =:= Bin].

% %% @doc 根据角色名trace
% tun(UserName) ->
% 	Bin = unicode:characters_to_binary(UserName),
% 	[t(UserID) ||#ets_user{name = Name, id = UserID} <- ets:tab2list(?ETS_ONLINE), Name =:= Bin].

% %% @doc 根据角色ID trace 方法
% t(UserID, MFN) ->
% 	t(UserID),
% 	t(MFN).

% t({M, F, Num}) ->
% 	dbg:tpl(M, F, Num, []);
% t(UserID) ->
% 	dbg:tracer(),
% 	Pid = misc:whereis_name({global, player_process_name(UserID)}),
% 	dbg:p(Pid, c).

% tr({M, F, Num}) ->
% 	dbg:tpl(M, F, Num, [{'_', [], [{return_trace}]}]).

% %% @doc 停止 trace
% t_clear() ->
% 	dbg:stop_clear().


%% @doc 备份进程信息
dump_process_info(Pid) ->
   {{Year, Month, Day}, {Hour, Minutes, Second}} = util:local_time(),
   {ok, FileHandle} = file:open(util:fbin("~s-~w-~w-~w-~w-~w-~w", [<<"../logs/pid_info.dump">>, Year, Month, Day, Hour, Minutes, Second]), write),
   case erlang:process_info(Pid) of
      Info when is_list(Info) ->
         lists:foreach(fun({messages, Messages}) ->
            case Messages =:= [] of
               true ->
                  io:format(FileHandle, "~w~n", [{messages, Messages}]);
               _ ->
                  io:format(FileHandle, "{messages,~n", []),
                  lists:foreach(fun(M) ->
                     io:format(FileHandle, "  ~w~n", [M])
                                end, Messages),
                  io:format(FileHandle, "}~n", [])
            end;
            ({dictionary, Dics}) ->
               case Dics =:= [] of
                  true ->
                     io:format(FileHandle, "~w~n", [{dictionary, Dics}]);
                  _ ->
                     io:format(FileHandle, "{dictionary,~n", []),
                     lists:foreach(fun(M) ->
                        io:format(FileHandle, "  ~w~n", [M])
                                   end, Dics),
                     io:format(FileHandle, "}~n", [])
               end;
            (E) ->
               io:format(FileHandle, "~w~n", [E])
                       end, Info);
      _ -> ?PRINT("not find pid info")
   end,
   file:close(FileHandle).

request(Url) ->
   {ok, RequestId} = httpc:request(get, {Url, []}, [], [{sync, false}]),
   receive
      {http, {RequestId, {{_Version, 200, _ReasonPhrase}, _Headers, Body}}} ->
         Body;
      E ->
         E
   after 500 ->
      timeout
   end.