diff --git a/src/srvNodeMgr/baizhan/misc/config.erl b/src/srvNodeMgr/baizhan/misc/config.erl new file mode 100644 index 0000000..ef52014 --- /dev/null +++ b/src/srvNodeMgr/baizhan/misc/config.erl @@ -0,0 +1,124 @@ +%%%----------------------------------- +%%% @Author : gaomeng 595088880@qq.com +%%% @Created : 2017/10/17 +%%% @Doc : APP启动相关 +%%%----------------------------------- +-module(config). +-include("common.hrl"). +-define(APP, server). + +-export([ + config/1 + , server_ids/0 + , listen_port/0 + , get_ticket/0 + , get_enter_ticket/0 + , get_platform/0 + , get_create_max_num/0 + , is_gm_open/0 + , set_gm_open/1 + , get_white_ip/0 + , add_white_ip/1 + , del_white_ip/1 + , open_date/0 + , merge_date/0 + , set_open_date/1 + , set_merge_date/1 + , print_cmd/0 + , set_print_cmd/1 + , version/0 + , is_https/0 +]). + +%% @doc 是否是HTTPS; return true|false +is_https() -> + case catch config(is_https) of + {'EXIT', _} -> + false; + Val -> + Val + end. + +%% @doc 获取版本 +version() -> + case catch config(version) of + {'EXIT', _} -> + "未知版本"; + Val -> + Val + end. + +%% @doc 获取协议打印状态 +print_cmd() -> + config(print_cmd). + +%% @doc 设置协议打印状态 +set_print_cmd(Flag) -> + application:set_env(?APP, print_cmd, Flag). + +%% @doc 设置开服时间 +set_open_date(Date) -> + application:set_env(?APP, open_date, Date, [{persistent, true}]). + +%% @doc 设置合服时间 +set_merge_date(Date) -> + application:set_env(?APP, merge_date, Date, [{persistent, true}]). + +%% @spec open_date() -> {Year, Month, Day} +%%@ doc 获取开服日期 +open_date() -> + config(open_date). + +%% @spec merge_date() -> {Year, Month, Day} +%%@ doc 获取合服日期 +merge_date() -> + config(merge_date). + +%% @doc 添加白名单 +add_white_ip(Ip) -> + List = get_white_ip(), + List1 = [Ip | List], + application:set_env(?APP, white_ip, List1). + +%% @doc 删除白名单 +del_white_ip(Ip) -> + List = get_white_ip(), + List1 = lists:delete(Ip, List), + application:set_env(?APP, white_ip, List1). + +%% @doc 获取白名单 +get_white_ip() -> + config(white_ip). + +set_gm_open(Flag) -> + application:set_env(?APP, gm, Flag). + +is_gm_open() -> + config(gm). + +get_enter_ticket() -> + config(enter_ticket). + +get_ticket() -> + config(ticket). + +listen_port() -> + config(listen_port). + +server_ids() -> + config(server_ids). + +get_platform() -> + config(platform). + +get_create_max_num() -> + config(create_max_num). + +%% @doc 根据KEY获取对应的值 +config(Key) -> + case application:get_env(?APP, Key) of + {ok, Val} -> + Val; + _ -> + throw(undefined) + end. \ No newline at end of file diff --git a/src/srvNodeMgr/baizhan/misc/lib_send.erl b/src/srvNodeMgr/baizhan/misc/lib_send.erl new file mode 100644 index 0000000..2018190 --- /dev/null +++ b/src/srvNodeMgr/baizhan/misc/lib_send.erl @@ -0,0 +1,202 @@ +%%%----------------------------------- +%%% @Author : gaomeng 595088880@qq.com +%%% @Created : 2017/1/30 +%%% @Doc : 发送相关 +%%%----------------------------------- +-module(lib_send). +-include("common.hrl"). +-include("scene.hrl"). +-include("move.hrl"). +-include("guild.hrl"). +-include_lib("stdlib/include/ms_transform.hrl"). + +-export([ + send/2, + pack_send/3, + pack_send/4, + pack_send_msg/2, + + send_to_scene/1, + send_to_scene/2, + + async_send_scene/2, + async_send_scene/3, + + send_to_world/1, + send_to_world/2, + send_to_world/3, + + send_to_guild/3, + + async_online/3, + async_online/4 +]). + +%% @doc 全服异步在线玩家执行函数 +async_online(M, F, Args) -> + async_online(0, M, F, Args). + +async_online(RoleId, M, F, Args) -> + spawn(fun() -> + case ets:info(?ETS_ONLINE) of + undefined -> + ok; + _ -> + L = ets:tab2list(?ETS_ONLINE), + [svr_role:apply(async, Pid, {M, F, Args}) || #role{pid = Pid, id = TemRoleId} <- L, RoleId =/= TemRoleId] + end + end). + +%% @doc 公会广播 +send_to_guild(GuildId, Cmd, List) when is_integer(GuildId) -> + svr_guild:apply(async, GuildId, {lib_send, send_to_guild, [Cmd, List]}); +send_to_guild(#guild{members = Members}, Cmd, List) -> + case catch prot:encode(Cmd, List) of + {ok, Bin} -> + F = fun(#guild_member{role_id = RoleId}) -> + case lib_account:get_role_pid(RoleId) of + Pid when is_pid(Pid) -> + send(Pid, Bin); + _ -> + ok + end + end, + [F(M) || M <- Members]; + Ret -> + ?ERR("Cmd:~w use pack_send, reason:~w", [Cmd, Ret]), + ok + end, + ok. + +%%世界广播 +send_to_world(RoleId, Bin) -> + spawn(fun() -> + [send(Sid, Bin) || #role{id = Id, sid = Sid} <- ets:tab2list(?ETS_ONLINE), Id =/= RoleId] + end). + +send_to_world(Bin) -> + spawn(fun() -> + [send(Sid, Bin) || #role{sid = Sid} <- ets:tab2list(?ETS_ONLINE)] + end). + +%% @doc 世界广播(有等级限制) +send_to_world(level, ReqLevel, Bin) -> + spawn(fun() -> + [send(Sid, Bin) || #role{level = Level, sid = Sid} <- ets:tab2list(?ETS_ONLINE), Level >= ReqLevel] + end); + +send_to_world(RoleId, Cmd, List) -> + case catch prot:encode(Cmd, List) of + {ok, Bin} -> + spawn(fun() -> + [send(Sid, Bin) || #role{id = Id, sid = Sid} <- ets:tab2list(?ETS_ONLINE), Id =/= RoleId] + end); + Ret -> + ?ERR("Cmd:~w use pack_send, reason:~w", [Cmd, Ret]), + ok + end. + +%%场景广播 +send_to_scene(Rid, BinData) -> + AllRole = svr_scene_role:get_all(), + F = fun(Role) -> + case Role#scene_role.id =/= Rid of + true -> + send(Role#scene_role.sid, BinData); + _ -> + ok + end + end, + [F(P) || P <- AllRole]. + +send_to_scene(BinData) -> + AllRole = svr_scene_role:get_all(), + F = fun(Role) -> + send(Role#scene_role.sid, BinData) + end, + [F(P) || P <- AllRole]. + +%% @doc 其他进程发送场景广播 +async_send_scene(ScenePid, BinData) -> + async_send_scene(ScenePid, BinData, 0). + +async_send_scene(ScenePid, BinData, RoleId) when is_pid(ScenePid) -> + svr_scene:apply(async, ScenePid, {svr_scene_info, async_send_scene, [RoleId, BinData]}); +async_send_scene(_, _, _) -> + ok. + +%% @doc 打包 +pack_send(Node, Rid = #node_rid{id = RoleId}, Cmd, List) -> + case Node =:= node() of + true -> + pack_send(RoleId, Cmd, List); + _ -> + svr_kfnode:send_role(Node, [Rid], lib_send, pack_send, [Cmd, List]) + end. + +pack_send(RoleId, Cmd, List) when is_integer(RoleId) -> + case lib_account:get_role_pid(RoleId) of + Pid when is_pid(Pid) -> + pack_send(Pid, Cmd, List); + _ -> + ok + end; +pack_send(#node_rid{id = RoleId}, Cmd, List) -> + pack_send(RoleId, Cmd, List); +pack_send(#role{sid = Sid}, Cmd, List) -> + pack_send(Sid, Cmd, List); +pack_send(Sid, Cmd, List) -> + case misc:is_process_alive(Sid) of + true -> + case catch prot:encode(Cmd, List) of + {ok, Bin} -> + send(Sid, Bin); + Ret -> + ?ERR("Cmd:~w use pack_send, reason:~w", [Cmd, Ret]), + ok + end; + _R -> + ok + end, + ok. + +send(#role{sid = Sid}, Bin) -> + send(Sid, Bin); +send(Sid, Bin) -> + case catch config:config(print_send_cmd) of + true -> + <<_L:32, Cmd:32, _Data/binary>> = Bin, + svr_handle_mgr:listen_cmd(Cmd, byte_size(Bin)); + _ -> + ok + end, + Sid ! {'send', Bin}, + ok. + +pack_send_msg(#node_rid{id = RoleId}, List) -> + pack_send_msg(RoleId, List); +pack_send_msg(RoleId, List) when is_integer(RoleId) -> + case ets:lookup(?ETS_ONLINE, RoleId) of + [#role{sid = Sid} | _] -> + case misc:is_process_alive(Sid) of + true -> + pack_send_msg(Sid, List); + _ -> + ok + end; + _ -> + ok + end; +pack_send_msg(#role{sid = Sid}, List) -> + pack_send_msg(Sid, List); +pack_send_msg(Sid, [Type, {Code, Vals}]) -> + pack_send_msg(Sid, [Type, 0, Code, Vals]); +pack_send_msg(Sid, [Type, Code]) -> + pack_send_msg(Sid, [Type, 0, Code, ""]); +pack_send_msg(Sid, [Type, SubType, {Code, Vals}]) -> + pack_send_msg(Sid, [Type, SubType, Code, Vals]); +pack_send_msg(Sid, [Type, SubType, Code]) -> + pack_send_msg(Sid, [Type, SubType, Code, ""]); +pack_send_msg(Sid, List) -> + pack_send(Sid, 10100, List), + ok. diff --git a/src/srvNodeMgr/baizhan/misc/lib_sys.erl b/src/srvNodeMgr/baizhan/misc/lib_sys.erl new file mode 100644 index 0000000..e1113b7 --- /dev/null +++ b/src/srvNodeMgr/baizhan/misc/lib_sys.erl @@ -0,0 +1,24 @@ +%%%----------------------------------- +%%% @Author : gaomeng 595088880@qq.com +%%% @Created : 2017/10/17 +%%% @Doc : 系统数据库保存相关信息 +%%%----------------------------------- +-module(lib_sys). +-include("common.hrl"). +-export([ + get_var/1 +]). + +%% @doc 获取对应数据库字段配置 +get_var(TarName) when is_list(TarName) -> + Sql = io_lib:format(<<"SELECT `value` FROM `sys_var` WHERE `name` = '~s' limit 1;">>, [TarName]), + case db:get_one(Sql) of + [] -> + util:term_to_bitstring([]); + <<>> -> + util:term_to_bitstring([]); + Ret -> + Ret + end; +get_var(Obj) -> + get_var(util:to_list(Obj)). \ No newline at end of file diff --git a/src/srvNodeMgr/baizhan/misc/misc.erl b/src/srvNodeMgr/baizhan/misc/misc.erl new file mode 100644 index 0000000..9d6c94d --- /dev/null +++ b/src/srvNodeMgr/baizhan/misc/misc.erl @@ -0,0 +1,729 @@ +%%%----------------------------------- +%%% @Module : misc +%%% @Author : lizemao +%%% @Created : 2014.2.24 +%%% @Description: misc +%%%----------------------------------- +-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 反编译代码 +decompile(Mod) -> + {ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(code:which(Mod), [abstract_code]), + ?INF("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]). + +%% @doc 在线人数 +online_num() -> + ets:info(?ETS_ONLINE, size). + +%% @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() -> + ?INF("server_starting_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. \ No newline at end of file diff --git a/src/srvNodeMgr/baizhan/misc/util.erl b/src/srvNodeMgr/baizhan/misc/util.erl new file mode 100644 index 0000000..5ae5028 --- /dev/null +++ b/src/srvNodeMgr/baizhan/misc/util.erl @@ -0,0 +1,1184 @@ +%%%----------------------------------- +%%% @Author : gaomeng 595088880@qq.com +%%% @Created : 2017/1/23 +%%% @Doc : 辅助函数库 +%%%----------------------------------- + +-module(util). +-include("common.hrl"). + +-export([ + unixtime/0, + longunixtime/0, + local_time/0, + + cn/1, + datetime_to_seconds/1, + ifelse/3, + string_width/1, + safe_create_ets/2, + rand_list/1, + term_to_string/1, + term_to_bitstring/1, + bitstring_to_term/1, + bitstring_to_obj/1, + string_to_term/1, + get_max_svrid/0, + to_list/1, + check_name_length/1, + rm_str_space/1, + rm_str_lr_space/1, + same_str/2, + unixtime_to_now/1, + unixtime_to_localtime/1, + get_day_of_week/0, + get_day_of_week/1, + get_secs_from_midnight/0, + get_secs_from_midnight/1, + time_to_seconds/1, + other_day/2, + make_cdkey/2, + parse_idstr/1, + robot_name/0, + localtime2date/0, + localtime2date/1, + check_sen_keyword/2, + check_round_level/4, + next_sec/1 +]). + +-export([ + quick_sort/2 + , upset_list/1 + , insert_last/2 +]). + +-export([ + open_day/0 + , merge_day/0 + , is_merge/0 + , week_diff_day/2 + , del_timer_ref/1 + , time_diff_sec/2 + , escape/1 + , under_week_time/2 + , calc_week_sec/1 + , calc_week_sec/2 + + , boolen2num/1 + , check_roleid/1 + , sort_red/0 + , decompile/1 +]). + +-compile(export_all). + +%% @doc 测试 +test() -> + L1 = [], + L2 = [], + Keys = [{2, ?true}], + L11 = quick_sort(L1, Keys), + ?INF("cmd1:~p~n", [L11]), + L22 = quick_sort(L2, Keys), + ?INF("cmd2:~p~n", [L22]), + ok. + +%% @doc 反编代码 +decompile(Mod) -> + {ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(code:which(Mod), [abstract_code]), + ?PRINT("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]). + +%% @doc 打印最大reductions计数, PID +sort_red() -> + [Pid | T] = erlang:processes(), + {_, MaxVal} = erlang:process_info(Pid, reductions), + Pid1 = find_max(MaxVal, T, Pid), + ?INF("max_reductions_pid:~p~n", [erlang:process_info(Pid1)]). + +find_max(MaxVal, [TemPid | T], Pid) -> + {_, TemMaxVal} = erlang:process_info(TemPid, reductions), + case TemMaxVal > MaxVal of + true -> + find_max(TemMaxVal, T, TemPid); + _ -> + find_max(MaxVal, T, Pid) + end; +find_max(_, [], Pid) -> + Pid. + +%% @doc 检测是否符合玩家id , true|false +check_roleid(RoleId) -> + <> = <>, + SvrIds = config:server_ids(), + lists:member(SvrId, SvrIds). + +%% @doc 状态转数字 +boolen2num(true) -> + ?true; +boolen2num(_) -> + ?false. + +%% @doc 获取当前日期和时间,相当于 elrang:localtime() -> {{Y, M, D}, {H, Mi, S}} +local_time() -> + UnixTime = unixtime(), + unixtime_to_localtime(UnixTime). + +%% @doc 计算距离日期时间(秒) +%% @spec calc_week_sec(WeekTime) -> TimeSec +%% where WeekTime = {Week, Time} ; Week=int() ; Time={hour, minute, second} +calc_week_sec(WeekTime) -> + CurrWeek = get_day_of_week(), + {_, Time} = local_time(), + calc_week_sec({CurrWeek, Time}, WeekTime). + +calc_week_sec({CurrWeek, CurrTime}, {TarWeek, TarTime}) -> + CurrSec = calendar:time_to_seconds(CurrTime), + TarSec = calendar:time_to_seconds(TarTime), + DiffDay = week_diff_day(CurrWeek, TarWeek), + case CurrWeek =:= TarWeek of + true -> + case CurrSec >= TarSec of + true -> + 7 * 86400 - CurrSec + TarSec; + _ -> + TarSec - CurrSec + end; + _ -> + DiffDay * 86400 - CurrSec + TarSec + end. + +%% @doc 检测是否小于指定时间 +%% under_week_time(A, B) -> true|false +%% A 是否小于 B +under_week_time({Week1, _}, {Week2, _}) when Week1 < Week2 -> + true; +under_week_time({Week1, Time1}, {Week1, Time2}) -> + TimeSec1 = calendar:time_to_seconds(Time1), + TimeSec2 = calendar:time_to_seconds(Time2), + TimeSec1 < TimeSec2; +under_week_time(_, _) -> + false. + +%% @doc 字符串转义 ' => \\' +escape(Str) -> + re:replace(Str, "'", "\\\\'", [global, {return, list}]). + +%% @doc 插入到数组尾部 +insert_last(List, Info) when is_list(List) -> + Other = lists:reverse(List), + Other1 = [Info | Other], + lists:reverse(Other1); +insert_last(_, _) -> + []. + +%% @doc 打乱数组(只适合比较少的数组) +upset_list(List) when is_list(List) -> + Len = length(List), + case List of + [_ | _] -> + upset_list_loop(Len, List, []); + _ -> + List + end; +upset_list(_) -> + []. + +upset_list_loop(_, [], L) -> + L; +upset_list_loop(Len, List, L) when Len > 0 -> + Index = util:random(1, Len), + Tuple = lists:nth(Index, List), + Other = lists:delete(Tuple, List), + upset_list_loop(Len - 1, Other, [Tuple | L]); +upset_list_loop(_, _, L) -> + L. + +%% @doc 两个时间的间隔(秒) +time_diff_sec(Time1, Time2) -> + Time1Sec = calendar:time_to_seconds(Time1), + Time2Sec = calendar:time_to_seconds(Time2), + max(0, Time2Sec - Time1Sec). + +%% @doc 删除指定定时器 +del_timer_ref(TimerRef) when is_reference(TimerRef) -> + erlang:cancel_timer(TimerRef); +del_timer_ref(_) -> + ok. + +%% @doc 距离指定星期多少天 +week_diff_day(Curr, List) when is_list(List) -> + week_diff_day(Curr, List, 0); +week_diff_day(Curr, Tar) -> + week_diff_day(Curr, [Tar]). + +week_diff_day(Curr, [TarWeek | _T], _Min) when TarWeek >= Curr -> + TarWeek - Curr; +week_diff_day(Curr, [TarWeek | T], Min) -> + case TarWeek < Min of + true -> + week_diff_day(Curr, T, TarWeek); + _ -> + week_diff_day(Curr, T, Min) + end; +week_diff_day(Curr, [], Min) -> + case Min > 0 of + true -> + 7 - Curr + Min; + _ -> + 1 + end. + +%% @spec next_sec(Time) -> Sec +%% where Time =:= {hour, min, sec}, Sec =:= integer() +%% @doc 判断时间间隔 +next_sec(Time) -> + OpenTimeSec = calendar:time_to_seconds(Time), + {_, NowTime} = local_time(), + NowTimeSec = calendar:time_to_seconds(NowTime), + case NowTimeSec < OpenTimeSec of + true -> + OpenTimeSec - NowTimeSec; + _ -> + (?ONE_DAY_SECONDS - NowTimeSec) + OpenTimeSec + end. + +%% @doc 快速排序 +quick_sort([X | L], Keys) -> + {L1, L2} = sort_helper(L, X, Keys, [], []), + quick_sort(L1, Keys) ++ [X] ++ quick_sort(L2, Keys); +quick_sort([], _) -> []. + +%% 辅助函数 +sort_helper([H | L], X, Keys, L1, L2) -> + case match_sort(Keys, X, H) of + true -> + sort_helper(L, X, Keys, [H | L1], L2); + _ -> + sort_helper(L, X, Keys, L1, [H | L2]) + end; +sort_helper([], _, _, L1, L2) -> {L1, L2}. + +%% @doc 计算顺序 +match_sort([{Index, Flag} | T], X1, X2) -> + Val1 = element(Index, X1), + Val2 = element(Index, X2), + if + Val1 > Val2 -> + Flag =< ?false; + Val1 < Val2 -> + Flag > ?false; + true -> + match_sort(T, X1, X2) + end; +match_sort([], _, _) -> + false. + +%%@doc 检测等级和转数是否符合 +check_round_level(Lv, ReLv, Round, ReRound) -> + case ReRound > 0 of + true -> + Round >= ReRound; + _ -> + Lv >= ReLv + end. + +%% @doc 是否是合服 +is_merge() -> + MergeSec = merge_time(), + OpenSec = open_time(), + OpenSec < MergeSec. + +%% @doc 获取合服天数 +merge_day() -> + case is_merge() of + true -> + MergeDate = config:merge_date(), + MergeTime = {MergeDate, {0, 0, 0}}, + {CurrDate, _} = local_time(), + NowTime = {CurrDate, {0, 0, 0}}, + {DiffDay, _} = calendar:time_difference(MergeTime, NowTime), + max(1, DiffDay + 1); + _ -> + 0 + end. + +%% @doc 获取开服时间戳 +open_time() -> + OpenDate = config:open_date(), + OpenTime = {OpenDate, {0, 0, 0}}, + datetime_to_seconds(OpenTime). + +%% @doc 获取合服时间戳 +merge_time() -> + MergeDate = config:merge_date(), + MergeTime = {MergeDate, {0, 0, 0}}, + datetime_to_seconds(MergeTime). + +%% @doc 获取开服天数 +open_day() -> + Date = config:open_date(), + OpenTime = {Date, {0, 0, 0}}, + {CurrDate, _} = local_time(), + NowTime = {CurrDate, {0, 0, 0}}, + {DiffDay, _} = calendar:time_difference(OpenTime, NowTime), + max(1, DiffDay + 1). + +%% @doc 拼凑日期数2017.1.2=>20170102 +localtime2date() -> + {Date, _} = local_time(), + localtime2date(Date). + +localtime2date({Y, M, D}) -> + Str = io_lib:format("~w~2..0w~2..0w", [Y, M, D]), + Str1 = lists:flatten(Str), + list_to_integer(Str1). + +%% @doc 随机机器人名称 +robot_name() -> + Max = data_robotname:get_max(), + HitNum = util:random(1, Max), + data_robotname:get(HitNum). + +%% @doc 整数列表转字符串==> [1,2,3] => "1,2,3" +parse_idstr(Ids) -> + parse_idstr(Ids, []). + +parse_idstr([H | T], L) -> + StrH = + case T of + [] -> + to_list(H); + _ -> + "," ++ to_list(H) + end, + parse_idstr(T, StrH ++ L); +parse_idstr([], L) -> + L. + +%% @doc 拼凑CDKEY +make_cdkey(Key, Num) when Num > 0 -> + CDkEY = Key ++ md5(lists:concat([?MIL_NOW, Num])), + ?PRINT("~s~n", [CDkEY]), + make_cdkey(Key, Num - 1); +make_cdkey(_, _) -> + ok. + +%% @doc 判断两个时间戳是否不是同一天 +other_day(Time1, Time2) -> + {Date1, _} = unixtime_to_localtime(Time1), + {Date2, _} = unixtime_to_localtime(Time2), + Date1 =/= Date2. + +%% Unix时间转换成对应的Erlang时间 +%% 返回 Now = {MegaSecs, Secs, _MicroSecs} Erlang时间 +unixtime_to_now(UnixTime) -> + M = UnixTime div 1000000, + S = UnixTime rem 1000000, + {M, S, 0}. + +%% Unix时间转换成对应的Localtime时间 +%% 返回 LocalTime = {Date, Time} Localtime时间 +unixtime_to_localtime(UnixTime) -> + Now = unixtime_to_now(UnixTime), + calendar:now_to_local_time(Now). + +%% 获取今天是星期几 +get_day_of_week() -> + NowTime = unixtime(), + get_day_of_week(NowTime). + +%% 获取指定的Unix时间是星期几 +get_day_of_week(UnixTime) -> + {Date, _Time} = unixtime_to_localtime(UnixTime), + calendar:day_of_the_week(Date). + +%% 获取当天0点到现在的秒数 +get_secs_from_midnight() -> + NowTime = unixtime(), + get_secs_from_midnight(NowTime). + +%% 获取指定的Unix时间当天0点到当时的秒数 +get_secs_from_midnight(UnixTime) -> + {{_Year, _Month, _Day}, Time} = unixtime_to_localtime(UnixTime), + calendar:time_to_seconds(Time). + +%% @doc 获取指定时间距离当天0点的秒数 +time_to_seconds(Time) -> + calendar:time_to_seconds(Time). + +%% @doc 检测两个字符串是否相等 +same_str(Str1, Str2) -> + TemStr1 = + case is_binary(Str1) of + true -> + binary_to_list(Str1); + _ -> + Str1 + end, + TemStr2 = + case is_binary(Str2) of + true -> + binary_to_list(Str2); + _ -> + Str2 + end, + TemStr1 =:= TemStr2. + + +%% @doc 去除字符串左右空格 +rm_str_lr_space(String) -> + String1 = rm_str_lr_space1(String), + String2 = lists:reverse(String1), + String3 = rm_str_lr_space1(String2), + lists:reverse(String3). + +rm_str_lr_space1([32 | T]) -> + rm_str_lr_space1(T); +rm_str_lr_space1(T) -> + T. + +%% @doc 去除字符串所有空格 +rm_str_space(String) -> + rm_str_space(String, []). + +rm_str_space([32 | T], L) -> + rm_str_space(T, L); +rm_str_space([H | T], L) -> + rm_str_space(T, [H | L]); +rm_str_space([], L) -> + lists:reverse(L). + +%% @doc 检测名称长度 +check_name_length(Name) -> + case lists:member(32, Name) of + true -> + {error, ?GMSG(svrmsg_10001)}; + _ -> + case unicode:characters_to_list(list_to_binary(Name)) of + {error, _, _Reason} -> + %%非法字符 + {error, ?GMSG(svrmsg_10001)}; + CharList -> + CharList1 = re:replace(CharList, "[\x{00010000}-\x{0010ffff}]", "", [{return, list}, global, unicode]),%过滤4字节的字符 + case CharList1 =:= CharList of + true -> + Len = util:string_width(CharList1), + case Len < 15 andalso Len > 2 of + true -> + true; + _ -> + %%名称长度为2~6个汉字 + {error, ?GMSG(svrmsg_10002)} + end; + _ -> + {error, ?GMSG(svrmsg_10001)} + end + end + end. + +%% 转换其他类型为list +to_list(X) when is_integer(X) -> integer_to_list(X); +to_list(X) when is_float(X) -> float_to_list(X); +to_list(X) when is_atom(X) -> atom_to_list(X); +to_list(X) when is_binary(X) -> binary_to_list(X); +to_list(X) when is_tuple(X) -> tuple_to_list(X); +to_list(X) when is_list(X) -> X. + +%% @doc 获取最高的服务器ID +get_max_svrid() -> + SvrIds = config:server_ids(), + lists:max(SvrIds). + +%% @doc %% @spec bitstring_to_obj(String::list()) -> term() +%% @doc bitstring转换为term +bitstring_to_obj(BitObj) -> + case catch bitstring_to_term(BitObj) of + {ok, Obj} -> + Obj; + _ -> + to_list(BitObj) + end. + +%% @spec bitstring_to_term(String::list()) -> {error, Why::term()} | {ok, term()} +%% @doc term反序列化,bitstring转换为term +bitstring_to_term(undefined) -> {ok, undefined}; +bitstring_to_term(BitString) -> string_to_term(binary_to_list(BitString)). + +%% @spec string_to_term(String::list()) -> {error, Why::term()} | {ok, term()} +%% @doc term反序列化,string转换为term,e.g., "[{a},1]" => [{a},1] +string_to_term(String) -> + case erl_scan:string(String ++ ".") of + {ok, Tokens, _} -> + erl_parse:parse_term(Tokens); + {error, Err, _} -> {error, Err}; + Err -> {error, Err} + end. + +%% term序列化,term转换为string格式,e.g., [{a},1] => "[{a},1]" +term_to_string(Term) -> + binary_to_list(list_to_binary(io_lib:format("~p", [Term]))). + +%% term序列化,term转换为bitstring格式,e.g., [{a},1] => <<"[{a},1]">> +term_to_bitstring(Term) -> + erlang:list_to_bitstring(io_lib:format("~p", [Term])). + +%% @doc 随机列表 +rand_list([Obj]) -> + Obj; +rand_list(L) -> + Len = length(L), + case Len > 0 of + true -> + Index = random(1, Len), + lists:nth(Index, L); + _ -> + false + end. + +%% @doc 安全创建ets,不抛任何异常~~ +safe_create_ets(Name, Args) -> + try + case ets:info(Name) of + undefined -> + ets:new(Name, Args); + _ -> %%已经存在就不重复创建了 + Name + end + catch _:EM -> + ?ERR("safe_create_ets(~p,~p) exception ~p", [Name, Args, EM]), + false + end. + +%% @doc 三目运算 +ifelse(A, B, C) -> + case A of + true -> + B; + _ -> + C + end. + +%% @doc 获取unix时间戳 +unixtime() -> + {MegaSecs, Secs, _MicroSecs} = os:timestamp(), + MegaSecs * 1000000 + Secs. + +longunixtime() -> + {A, B, C} = os:timestamp(), + A * 1000000000 + B * 1000 + C div 1000. + +%%========================================================================= +%% 列表操作 +%%========================================================================= + +make_sign(Accid, Accname) -> + TICKET = config:get_ticket(), + Tstamp = unixtime(), + Hex = md5(lists:concat([Accid, Accname, Tstamp, TICKET])), + {Tstamp, Hex}. + +%% 随机取出list元素 +list_rand([]) -> null; +list_rand(List) -> + Len = length(List), + Index = random(1, Len), + lists:nth(Index, List). + +%% 随机从列表中选n个元素 +%% @return:: null | List +list_rand_n([], _PickNum) -> null; +list_rand_n(List, PickNum) -> + list_rand_n(List, PickNum, []). + +list_rand_n([], _PickNum, AccList) -> AccList; +list_rand_n(_List, 0, AccList) -> AccList; +list_rand_n(List, PickNum, AccList) -> + PickOne = list_rand(List), + LeftList = List -- [PickOne], + list_rand_n(LeftList, PickNum - 1, [PickOne | AccList]). + +%% 依据权重,从元组列表中随机挑选N个元素,返回被抽中的元组列表 +%% @args:: +%% Tuples:: 元组列表 +%% Index:: 是权重所在的位置 +%% PickNum:: 随机出的数量 +rand_by_weight(Tuples, Index, PickNum) when PickNum >= 0 -> + rand_N_by_weight__(Tuples, Index, PickNum, []). + +rand_N_by_weight__(_Tuples, _Index, 0, Ret) -> Ret; +rand_N_by_weight__([], _Index, _PickNum, Ret) -> Ret; +rand_N_by_weight__(Tuples, Index, PickNum, Ret) -> + PickOne = rand_by_weight(Tuples, Index), + LeftTuples = lists:delete(PickOne, Tuples), + rand_N_by_weight__(LeftTuples, Index, PickNum - 1, [PickOne | Ret]). + + +%% 依据权重,从元组列表中随机挑选一个元素,返回被抽中的元组, +%% 如果没有对应的元素,则抛出异常 +%% Index是权重所在的位置 +%% @return: Tuple +rand_by_weight([], _Index) -> + error(badargs); +rand_by_weight(Tuples, Index) -> + Sum = lists:sum([element(Index, Tuple) || Tuple <- Tuples]), + P = random(1, Sum), + rand_one_by_weight__(Tuples, Index, P). + +rand_one_by_weight__([Tuple], _, _) -> + Tuple; +rand_one_by_weight__([Tuple | T], Index, P) -> + case element(Index, Tuple) of + Weight when P =< Weight -> + Tuple; + Weight -> + rand_one_by_weight__(T, Index, P - Weight) + end. + +%% 过滤掉元组列表中某个元素相同的列表 +%% eg:L=[{1,2},{2,2},{3,1}]. list_filter(L, 2) -> [{1,2},{3,1}] +list_filter(List, N) -> + list_filter_helper(List, N, [], []). + +list_filter_helper([H | T], N, ResultList, KeyList) -> + Key = element(N, H), + case lists:member(Key, KeyList) of + true -> list_filter_helper(T, N, ResultList, KeyList); + false -> list_filter_helper(T, N, [H | ResultList], [Key | KeyList]) + end; +list_filter_helper([], _, ResultList, _) -> ResultList. + +%% 随机打乱list元素顺序 +list_shuffle(List) -> + Len = length(List), + List1 = [{random(1, Len + 10000), X} || X <- List], + List2 = lists:sort(List1), + [E || {_, E} <- List2]. + +%% 根据下标替换list元素值 +list_replace(Index, NewElem, List) -> + list_replace_helper(List, Index, NewElem, 1, []). +list_replace_helper([], _Index, _NewElem, _CurIndex, NewList) -> + NewList; +list_replace_helper([H | T], Index, NewElem, CurIndex, NewList) -> + if Index =:= CurIndex -> + list_replace_helper(T, Index, NewElem, CurIndex + 1, NewList ++ [NewElem]); + true -> + list_replace_helper(T, Index, NewElem, CurIndex + 1, NewList ++ [H]) + end. + +%% 根据list的元素值获得下标 +list_get_index(Elem, List) -> + list_get_index_helper(List, Elem, 0). +list_get_index_helper([], _Elem, _Index) -> + 0; +list_get_index_helper([H | T], Elem, Index) -> + if H =:= Elem -> + Index + 1; + true -> + list_get_index_helper(T, Elem, Index + 1) + end. + +%% 根据list的元素值获得下标(加强版) +%% @param: (Elem, N, List), List为元组列表,N为元组中第N个元素等于Elem +%% @return {0,null} | {Index, H} +list_get_index_ex(Elem, N, List) when is_list(List), is_integer(N) -> + list_get_index_ex(Elem, N, List, 0); +list_get_index_ex(_, _, _) -> {0, null}. +list_get_index_ex(_Elem, _N, [], _) -> {0, null}; +list_get_index_ex(Elem, N, [H | _], Index) when element(N, H) =:= Elem -> {Index + 1, H}; +list_get_index_ex(Elem, N, [_ | L], Index) -> list_get_index_ex(Elem, N, L, Index + 1). + + +%% 多个列表数值相加,结果以第一个列表的长度为准 +lists_add([ResultList]) -> ResultList; +lists_add([List1, List2 | T]) -> + ResultList = lists_add_helper(List1, List2, []), + lists_add([ResultList | T]). + +lists_add_helper([], _List2, ResultList) -> + lists:reverse(ResultList); +lists_add_helper(List1, [], ResultList) -> + lists:reverse(ResultList) ++ List1; +lists_add_helper([H1 | T1], [H2 | T2], ResultList) -> + lists_add_helper(T1, T2, [H1 + H2 | ResultList]). + +%% 扩展版lists:min/1 +%% @param: (List, N), List为元组列表,N为元组中第N个元素 +min_ex([H | T], N) -> min_ex(T, H, N). +min_ex([H | T], Min, N) when element(N, H) < element(N, Min) -> min_ex(T, H, N); +min_ex([_ | T], Min, N) -> min_ex(T, Min, N); +min_ex([], Min, _) -> Min. + +%% 扩展版lists:max/1 +%% @param: (List, N), List为元组列表,N为元组中第N个元素 +max_ex([H | T], N) -> max_ex(T, H, N); +max_ex([], _N) -> 0. + +max_ex([H | T], Max, N) when element(N, H) > element(N, Max) -> max_ex(T, H, N); +max_ex([_ | T], Max, N) -> max_ex(T, Max, N); +max_ex([], Max, _) -> Max. + +%% 扩展版lists:max/1 +%% @param: (List, N), List为元组列表,N为元组中第N个元素, Record为列表为空时调用者预期返回的内容 +keymax([H | T], N, Record) -> keymax(T, H, N, Record); +keymax([], _N, Record) -> Record. + +keymax([H | T], Max, N, Record) when element(N, H) > element(N, Max) -> keymax(T, H, N, Record); +keymax([_ | T], Max, N, Record) -> keymax(T, Max, N, Record); +keymax([], Max, _, _) -> Max. + +%% 列表中的元素是否全部相同 +%% @param: (List, N), List为元组列表,N为元组中第N个元素 +is_all_same([H | T], N) -> is_all_same(T, H, N). + +is_all_same([H | T], Min, N) when element(N, H) =:= element(N, Min) -> is_all_same(T, H, N); +is_all_same(L, _, _) when L =/= [] -> false; +is_all_same([], _, _) -> true. + +%% 列表中某元素的总和 +sum_ex(L, N) -> sum_ex(L, 0, N). +sum_ex([H | T], Sum, N) -> sum_ex(T, Sum + element(N, H), N); +sum_ex([], Sum, _) -> Sum. + +%% for循环 +for(Max, Max, F) -> + F(Max); +for(I, Max, F) -> + F(I), + for(I + 1, Max, F). + +%% 带返回状态的for循环 +%% @return {ok, State} +for(Max, Min, _F, State) when Min < Max -> {ok, State}; +for(Max, Max, F, State) -> F(Max, State); +for(I, Max, F, State) -> {ok, NewState} = F(I, State), for(I + 1, Max, F, NewState). + +%% 在List中的每两个元素之间插入一个分隔符 +implode(_S, []) -> + [<<>>]; +implode(S, L) when is_list(L) -> + implode(S, L, []). +implode(_S, [H], NList) -> + lists:reverse([type:object_to_list(H) | NList]); +implode(S, [H | T], NList) -> + L = [type:object_to_list(H) | NList], + implode(S, T, [S | L]). + +%% 字符->列 +explode(S, B) -> + re:split(B, S, [{return, list}]). +explode(S, B, int) -> + [list_to_integer(Str) || Str <- explode(S, B), length(Str) > 0]. + +%% 扩展的map函数 +map_ex(_Fun, [], _Arg) -> + []; +map_ex(Fun, [H | T], Arg) -> + [Fun(H, Arg) | map_ex(Fun, T, Arg)]. + +%% 截取列表的第Begin个到第End个 +sublist(L, Begin, End) -> + sublist(L, Begin, End, {1, []}). +sublist([], _Begin, _End, {_NowNth, RetL}) -> + lists:reverse(RetL); +sublist([_ | _L], _Bigen, End, {NowNth, RetL}) when NowNth > End -> + lists:reverse(RetL); +sublist([Item | L], Begin, End, {NowNth, RetL}) when Begin =< NowNth andalso NowNth =< End -> + sublist(L, Begin, End, {NowNth + 1, [Item | RetL]}); +sublist([_ | L], Begin, End, {NowNth, RetL}) -> + sublist(L, Begin, End, {NowNth + 1, RetL}). + + +%%========================================================================= +%% 字符文本操作 +%%========================================================================= + +%% 字符加密 +check_char_encrypt(Id, Time, TK) -> + TICKET = "7YnELt8MmA4jVED7", + Hex = md5(lists:concat([Time, Id, TICKET])), + %NowTime = time:unixtime(), + %Hex =:= TK andalso NowTime - Time >= -10 andalso NowTime - Time < 300. + Hex =:= TK. + +%% 转换成HEX格式的md5 +md5(S) -> + lists:flatten([io_lib:format("~2.16.0b", [N]) || N <- binary_to_list(erlang:md5(S))]). + +%% Function: 检查客户端发过来的内容,false为不合法,true为合法 +%% @param: String: 客户端发来的字符串 +%% @param: Length: 服务端限制的字符串长度 +check_string(String, Length) -> + case check_length(String, Length) of + true -> + case check_keyword(String, ["'", "/", "\"", "_", "<", ">"]) of + false -> + case check_keyword(String) of + false -> true; + true -> false + end; + true -> + false + end; + false -> + false + end. + +%% 检查关键字,存在非法字符返回true,否则false +%% @spec check_keyword(Text, Words) -> false | true +%% @param Text : 需要检查的字符串(或字符串的二进制形式) +%% @param Words: 非法字符列表 +check_keyword(_, []) -> + false; +check_keyword(Text, [Word | Words]) -> + case re:run(Text, Word, [{capture, none}]) of + match -> + true; + nomatch -> + check_keyword(Text, Words) + end. + +filter_text_gm(Text) when is_bitstring(Text) -> + Text; +filter_text_gm(Text) when is_list(Text) -> + list_to_bitstring(Text). + +%% 敏感词过滤 +%% @param Text list() | bitstring() +%% @return bitstring() 过滤后的文本 +filter_text(Text, Lv) when is_bitstring(Text) -> + S = bitstring_to_list(Text), + filter_text(S, Lv); +filter_text(Text, Lv) when is_list(Text) -> + [Term] = io_lib:format("~ts", [Text]), + svr_word:replace_sensitive_talk(Term, Lv). + +%% 名字过滤 +%% @param Text list() | bitstring() +%% @return bitstring() 过滤后的文本 +filter_name(Text) when is_bitstring(Text) -> + S = bitstring_to_list(Text), + filter_name(S); +filter_name(Text) when is_list(Text) -> + [Term] = io_lib:format("~ts", [Text]), + svr_word:replace_sensitive_name(Term). + +%% 敏感词检测 +%% @return true 存在关键词 +%% false 不存在关键词 +%% @var Text:字符串 +check_keyword(Text) -> + if + is_list(Text) -> + svr_chat:word_base_other(Text); + true -> + true + end. + +%% @doc 敏感词检测 +%% @spec check_sen_keyword(Type, Text) -> true|false +%% where Type::base or base_other ; true 存在关键词 ; false 不存在关键词 +check_sen_keyword(base, Text) when is_list(Text) -> + svr_chat:word_base(Text); +check_sen_keyword(base_other, Text) when is_list(Text) -> + svr_chat:word_base_other(Text); +check_sen_keyword(_, _) -> + true. + + +%% 长度合法性检查 +check_length(Item, LenLimit) -> + check_length(Item, 1, LenLimit). + +check_length(Item, MinLen, MaxLen) -> + % case asn1rt:utf8_binary_to_list(list_to_binary(Item)) of + % {ok, UnicodeList} -> + % Len = string_width(UnicodeList), + % Len =< MaxLen andalso Len >= MinLen; + % {error, _Reason} -> + % false + % end. + case unicode:characters_to_list(list_to_binary(Item)) of + UnicodeList when is_list(UnicodeList) -> + Len = string_width(UnicodeList), + Len =< MaxLen andalso Len >= MinLen; + _ -> + false + end. + +%% 字符宽度,1汉字=2单位长度,1数字字母=1单位长度 +string_width(String) -> + string_width(String, 0). +string_width([], Len) -> + Len; +string_width([H | T], Len) -> + case H > 255 of + true -> + string_width(T, Len + 2); + false -> + string_width(T, Len + 1) + end. + +%% IP元组转字符 +ip2bin({A, B, C, D}) -> + [integer_to_list(A), ".", integer_to_list(B), ".", integer_to_list(C), ".", integer_to_list(D)]. + +%% 过滤掉字符串中的特殊字符 +filter_string(String, CharList) -> + case is_list(String) of + true -> + filter_string_helper(String, CharList, []); + false when is_binary(String) -> + ResultString = filter_string_helper(binary_to_list(String), CharList, []), + list_to_binary(ResultString); + false -> + ?ERR("filter_string: Error string=[~w]", [String]), + String + end. + +filter_string_helper([], _CharList, ResultString) -> + ResultString; +filter_string_helper([H | T], CharList, ResultString) -> + case lists:member(H, CharList) of + true -> filter_string_helper(T, CharList, ResultString); + false -> filter_string_helper(T, CharList, ResultString ++ [H]) + end. + +%%========================================================================= +%% 其他操作 +%%========================================================================= + +%% 产生一个介于Min到Max之间的随机整数 +random(Same, Same) -> Same; +random(Min, Max) when Max < Min -> 0; +random(Min, Max) -> + %% 以保证不同进程都可取得不同的种子 + case get("rand_seed") of + undefined -> + rand:seed(exs64, os:timestamp()), + put("rand_seed", 1); + _ -> skip + end, + M = Min - 1, + if + Max - M =< 0 -> 0; + true -> rand:uniform(Max - M) + M + end. + +%% 向上取整 +ceil(N) -> + T = trunc(N), + case N == T of + true -> T; + false -> 1 + T + end. + +%% 向下取整 +floor(X) -> + T = trunc(X), + case (X < T) of + true -> T - 1; + _ -> T + end. + +%% 睡眠 +sleep(T) -> + receive + after T -> ok + end. + +sleep(T, F) -> + receive + after T -> F() + end. + +%% 获取客户端ip +get_ip(Socket) -> + case catch inet:peername(Socket) of + {ok, {Ip, _Port}} -> + Ip; + _ -> + case catch ssl:peername(Socket) of + {ok, {Ip, _Port}} -> + Ip; + _ -> + {0, 0, 0, 0} + end + end. + +%%计数器 +counter(Type) -> + counter(Type, 1). +counter(Type, Inc) -> + ets:update_counter(ets_counter, Type, Inc). + +%% 获取运营服ID +get_server_id() -> + Server = case application:get_env(game, card_server) of + {ok, [_Ser]} -> _Ser; + _ -> "" + end, + %{ok, [Server]} = application:get_env(card_server), + Len = string:len(Server), + case Len > 1 of + true -> + ServerId = string:sub_string(Server, 2, Len), + case string:to_integer(ServerId) of + {Id, _} -> Id; + _ -> 0 + end; + false -> + 0 + end. + +%% 显示record +%% 用法 : r2p(#ets_online{...}, record_info(fields, ets_online)) +r2p(A, B) -> record_to_proplist(A, B). +record_to_proplist(Record, Fields) -> + record_to_proplist(Record, Fields, record__). + +record_to_proplist(Record, Fields, TypeKey) + when tuple_size(Record) - 1 =:= length(Fields) -> + lists:zip([TypeKey | Fields], tuple_to_list(Record)). + +%% 角度和cos的转换,cos(60') = 0.5 +angle_to_float(Angle) -> + math:cos(math:pi() * Angle / 180). + +%% 分页取数据 +% @param Data 所有数据(列表) +% @param Page 第几页数据(大于总页数则默认最后页) +% @param PageNum 每一页数量 +% @return {总页数, 当前页, 当前页数据} +page_data(Data, Page, PageNum) -> + Len = length(Data), + PageTotal = ceil(Len / PageNum), + PageNow = case Page > PageTotal of true -> max(1, PageTotal); false -> Page end, + StartIndex = (PageNow - 1) * PageNum + 1, + PickData = lists:sublist(Data, StartIndex, PageNum), + {PageTotal, PageNow, PickData}. + + +%% -------------------------- +%% 计算字符串的相似度 +%% -------------------------- +% 用于聊天检测 +calc_string_compare(A, B) -> + AWordDict = word_dict(unicode:characters_to_list(type:object_to_binary(A))), + BWordDict = word_dict(unicode:characters_to_list(type:object_to_binary(B))), + Dict = merge_dict(AWordDict, BWordDict), + F = fun(_K, {V1, V2}, {DenominatorAcc, Sqdoc1Acc, Sqdoc2Acc}) -> + {DenominatorAcc + V1 * V2 + , Sqdoc1Acc + V1 * V1 + , Sqdoc2Acc + V2 * V2 + } + end, + {Denominator, Sqdoc1, Sqdoc2} = dict:fold(F, {0, 0, 0}, Dict), + case Sqdoc1 =:= 0 orelse Sqdoc2 =:= 0 of + true -> 0; + false -> Denominator / math:sqrt(Sqdoc1 * Sqdoc2) + end. + +merge_dict(D1, D2) -> + F1 = fun(_K, V) -> {V, 0} end, + D1_ = dict:map(F1, D1), + F2 = fun(K, V, Dict) -> + case dict:find(K, D1_) of + error -> dict:store(K, {0, V}, Dict); + {ok, {V1, 0}} -> dict:store(K, {V1, V}, Dict); + _ -> Dict + end + end, + D2_ = dict:fold(F2, D1_, D2), + D2_. +% % 过滤常用词 +% F3 = fun(K, _V) -> not lists:member(K, ?GENERAL_WORD) end, +% D3_ = dict:filter(F3, D2_), +% D3_. + +% 取字(连续数字、连续字符当作一个字) +word_dict(L) -> word__(L, [], dict:new()). +% A-Z 65-90 +% a-z 97-122 +% 0-9 48-57 +word__([A | L], Word, WordDict) when (A >= 65 andalso A =< 90) orelse + (A >= 97 andalso A =< 122) orelse + (A >= 48 andalso A =< 57) -> + word__(L, [A | Word], WordDict); +word__([I | L], [], WordDict) -> + word__(L, [], dict:update_counter([I], 1, WordDict)); +word__([I | L], Word, WordDict) -> + WordDict1 = dict:update_counter(Word, 1, WordDict), + WordDict2 = dict:update_counter([I], 1, WordDict1), + word__(L, [], WordDict2); +word__([], [], WordList) -> + WordList; +word__([], Word, WordDict) -> + dict:update_counter(Word, 1, WordDict). + +%% @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()]. + +cn(Data) -> + io:format("~ts~n", [Data]). + +%% @spec datetime_to_seconds(DateTime) -> false | SecondsTime +%% DateTime = {{2011,11,15},{16,14,57}} = {{Y, M, D}, {h, m, s}} +%% @doc 将日期转换unix时间戳 +datetime_to_seconds(0) -> 0; +datetime_to_seconds(DateTime) -> + case calendar:local_time_to_universal_time_dst(DateTime) of + [] -> false; + [_, Udate] -> + calendar:datetime_to_gregorian_seconds(Udate) - 719528 * 24 * 3600; + [Udate] -> + calendar:datetime_to_gregorian_seconds(Udate) - 719528 * 24 * 3600 + end. + +%% @doc 重新定向中央服务器 +redirect_center(NodeName, Cookie) -> + case svr_node_kf:get_node_id() =:= 0 of + true -> + case nodes(hidden) of + [] -> + ok; + Nodes -> + F = fun(TemNodeName) -> + catch rpc:call(TemNodeName, svr_kfcenter, del_node, [node()]), + net_kernel:disconnect(TemNodeName) + end, + [F(TemNodeName) || TemNodeName <- Nodes] + end, + erlang:set_cookie(NodeName, Cookie), + case net_adm:ping(NodeName) of + pong -> + Sql = io_lib:format(<<"replace into base_kf(`id`,`name`,`cookie`) values(~p, ~p, '~p')">>, [100, NodeName, Cookie]), + db:execute_nohalt(Sql); + _ -> + ok + end; + _ -> + ok + end. + +%% @doc 检测是否在范围内 +check_range(X, Y, TarX, TarY, Range) -> + X1 = abs(X - TarX), + Y1 = abs(Y - TarY), + math:sqrt(X1 * X1 + Y1 * Y1) =< Range. \ No newline at end of file diff --git a/src/srvNodeMgr/misc.erl b/src/srvNodeMgr/misc.erl new file mode 100644 index 0000000..4c17e12 --- /dev/null +++ b/src/srvNodeMgr/misc.erl @@ -0,0 +1,714 @@ +-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. \ No newline at end of file diff --git a/src/srvNodeMgr/tools/gameWorld/test/excel2mysql.erl b/src/srvNodeMgr/tools/gameWorld/test/excel2mysql.erl new file mode 100644 index 0000000..871b116 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/excel2mysql.erl @@ -0,0 +1,186 @@ +-module(excel2mysql). + +%% +%% Include files +%% +-include("common.hrl"). +-compile(export_all). + +-define(CONFIG_FILE, "../config/gateway.config"). + +-define(TMP_TABLE_PATH, "./tmptable/"). +-define(SRC_TABLE_PATH, "../src/table/"). +-define(RECORD_FILENAME, "../include/table_to_record.hrl"). +-define(BEAM_PATH, "./"). + +-define(EXCEL_PATH, "../temp/excel2mysql/"). + +%% +%% API Functions +%% +start() -> + case get_db_config(?CONFIG_FILE) of + [Host, Port, User, Password, DB, Encode] -> + start_erlydb(Host, Port, User, Password, DB), + mysql:start_link(?DB_POOL, Host, Port, User, Password, DB, fun(_, _, _, _) -> ok end, Encode), + mysql:connect(?DB_POOL, Host, Port, User, Password, DB, Encode, true), + excel_to_mysql_base_goods_practise(); + _ -> mysql_config_fail + end, + ok. + +get_db_config(Config_file) -> + try + {ok, [L]} = file:consult(Config_file), + {_, C} = lists:keyfind(gateway, 1, L), + {_, Mysql_config} = lists:keyfind(mysql_config, 1, C), + {_, Host} = lists:keyfind(host, 1, Mysql_config), + {_, Port} = lists:keyfind(port, 1, Mysql_config), + {_, User} = lists:keyfind(user, 1, Mysql_config), + {_, Password} = lists:keyfind(password, 1, Mysql_config), + {_, DB} = lists:keyfind(db, 1, Mysql_config), + {_, Encode} = lists:keyfind(encode, 1, Mysql_config), + [Host, Port, User, Password, DB, Encode] + catch + _:_ -> no_config + end. + +%% +%% Local Functions +%% +start_erlydb(IP, Port, User, Password, Db) -> + erlydb:start(mysql, [{pool_id, erlydb_mysql}, + {hostname, IP}, + {port, Port}, + {username, User}, + {password, Password}, + {database, Db}, + {encoding, utf8}, + {pool_size, 10}]). + + +excel_to_mysql_base_goods_practise() -> + Practise_path = lists:concat([?EXCEL_PATH, 'base_goods_practise']), + case file:list_dir(Practise_path) of + {ok, Filenames} -> + lists:foreach(fun(F) -> + case string:rstr(F, ".txt") > 0 of + true -> + base_goods_practise_0(Practise_path ++ "/" ++ F), + ok; + _ -> no_action + end, + ok + end, + lists:sort(Filenames)); + {error, _} -> ignore + end, + ok. + +base_goods_practise_0(Filename) -> + io:format("__1__~p~n", [Filename]), + {ok, IoDevice} = file:open(Filename, [read]), + try +%% 神弓(09)_绿色(1)_ +%% 攻击力 单属性 +%% 最大 最小 命中 敏捷 体质 +%% 9/1 +%% max_attack/min_attack/hit/agile/physique + + {ok, _Line1} = file:read_line(IoDevice), + {ok, _Line2} = file:read_line(IoDevice), + {ok, _Line3} = file:read_line(IoDevice), + {ok, Line4} = file:read_line(IoDevice), + {ok, Line5} = file:read_line(IoDevice), + [Line4_0] = string:tokens(Line4, "\n"), + [Line5_0] = string:tokens(Line5, "\n"), +%% io:format(" ~p~n ~p~n", [Line4_0, Line5_0]), + [Subtype, Color] = string:tokens(Line4_0, "/"), + Attrs = string:tokens(Line5_0, "/"), + base_goods_practise_1(IoDevice, + tool:to_integer(Subtype), + tool:to_integer(Color), + Attrs + ), + file:close(IoDevice), + ok + catch + _:_ -> file:close(IoDevice) + end, + ok. + +base_goods_practise_1(IoDevice, Subtype, Color, Attrs) -> +%% io:format(" ~p~n", [[Subtype, Color, Attrs, Att_num]]), + case file:read_line(IoDevice) of + {ok, Line} -> + [Line_0] = string:tokens(Line, "\n"), + Vals0 = string:tokens(Line_0, ","), + [Grade0 | Vals] = Vals0, + Grade = tool:to_integer(Grade0), +%% io:format(" ~p~n", [Grade]), + base_goods_practise_2(Subtype, Color, Attrs, Grade, Vals, 10), + base_goods_practise_1(IoDevice, Subtype, Color, Attrs); + eof -> + {ok, finshed}; + {error, Reason} -> + {error, Reason} + end. + +base_goods_practise_2(_Subtype, _Color, _Attrs, _Grade, [], _Step) -> + ok; +base_goods_practise_2(Subtype, Color, Attrs, Grade, Vals, Step) -> +%% io:format(" ~p/~p/~p~n", [Grade, Step, length(Vals)]), + case length(Attrs) of + 5 -> Att_num = 1, + {L1, L2} = lists:split(length(Vals) - 5, Vals), +%% 神弓(09)_绿色(1)_ +%% 攻击力 单属性 +%% 最大 最小 命中 敏捷 体质 +%% 9/1 +%% max_attack/min_attack/hit/agile/physique + io:format("Here_1_~n", []), + [Field1, Field2, Field3, Field4, Field5] = Attrs, + io:format("Here_2_~n", []), + [Val1, Val2, Val3, Val4, Val5] = L2, + io:format("Here_3_~n", []), + Field_Value_List = [{att_num, Att_num}, {subtype, Subtype}, {step, Step}, {color, Color}, {grade, Grade}] + ++ [{tool:to_atom(Field1), tool:to_integer(Val1)}] + ++ [{tool:to_atom(Field2), tool:to_integer(Val2)}] + ++ [{tool:to_atom(Field3), tool:to_integer(Val3)}] + ++ [{tool:to_atom(Field4), tool:to_integer(Val4)}] + ++ [{tool:to_atom(Field5), tool:to_integer(Val5)}], + io:format("Here_4_/~p/~n", [Field_Value_List]), + Sql = make_replace_sql(base_goods_practise, Field_Value_List), +%% io:format(" ~p/~p/~p/~p~n", [Grade, Step, length(Vals), Sql]), + db_sql:execute(Sql), + base_goods_practise_2(Subtype, Color, Attrs, Grade, L1, Step - 1), + ok; + 7 -> _Att_num = 2, + {L1, _L2} = lists:split(length(Vals) - 7, Vals), + base_goods_practise_2(Subtype, Color, Attrs, Grade, L1, Step - 1), + ok + end, + ok. + + +make_replace_sql(Table_name, Field_Value_List) -> + {Vsql, _Count1} = + lists:mapfoldl( + fun(Field_value, Sum) -> + Expr = case Field_value of + {Field, Val} -> + case is_binary(Val) orelse is_list(Val) of + true -> + io_lib:format("`~s`='~s'", [Field, re:replace(Val, "'", "''", [global, {return, binary}])]); + _ -> io_lib:format("`~s`='~p'", [Field, Val]) + end + end, + S1 = if Sum == length(Field_Value_List) -> io_lib:format("~s ", [Expr]); + true -> io_lib:format("~s,", [Expr]) + end, + {S1, Sum + 1} + end, + 1, Field_Value_List), + lists:concat(["replace into `", Table_name, "` set ", + lists:flatten(Vsql) + ]). \ No newline at end of file diff --git a/src/srvNodeMgr/tools/gameWorld/test/misc/auto_id.erl b/src/srvNodeMgr/tools/gameWorld/test/misc/auto_id.erl new file mode 100644 index 0000000..f455f1d --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/misc/auto_id.erl @@ -0,0 +1,124 @@ +%%-------------------------------------- +%% @Module : auto_id +%% @Author : smxx +%% @Created : 2013.03.01 +%% @Description: 设置数据表的自增ID初值 +%%-------------------------------------- +-module(auto_id). +-compile([export_all]). +-include("auto_id.hrl"). + +%%设置自增ID初值(服务节点才需要处理. 其他不管) +set_auto_increment(server) -> + ServerNum = config:get_server_num(), + io:format("~n--------------------------------------------------~n"), + io:format("Start checking tables Auto Increment...~n"), + io:format("Current Server Num: ~p~n", [ServerNum]), + io:format("--------------------------------------------------~n"), + F = fun(TableName) -> + case get_auto_increment_width(TableName) of + no_match -> + io:format("Table: ~p found NO AUTO_INCREMENT fields, check your configuration!~n", [TableName]); + Width -> %%字段宽度 + case Width of + 20 -> + Start = ServerNum * ?SPACE20 + 1, + End = (ServerNum + 1) * ?SPACE20; + 11 -> + Start = ServerNum * ?SPACE11 + 1, + End = (ServerNum + 1) * ?SPACE11 + end, + CurrentOffset = get_auto_increment_offset(TableName), + if +%% CurrentOffset >= Start andalso CurrentOffset < End -> %%已经设置好了 +%% io:format("Table: ~p \tAUTO_INCREMENT OFFSET ->\tOK~n", [TableName]), +%% io:format("\t\tCurrent: ~p, Start: ~p, End:~p~n~n", [CurrentOffset, Start, End]); +%% CurrentOffset >= End -> +%% io:format("Table: ~p \tAUTO_INCREMENT OFFSET -> OUT OF RANGE~n", [TableName]), +%% io:format("\t\tCurrent: ~p, Start: ~p, End:~p~n~n", [CurrentOffset, Start, End]); + CurrentOffset > ?SPACE20 -> %% 已经设置过了偏移量 + io:format("Table: ~p \t AUTO_INCREMENT OFFSET ->\tOK~n", [TableName]), + io:format("\t\tCurrent: ~p, Start: ~p, End:~p~n~n", [CurrentOffset, Start, End]); + + true -> + io:format("Table: ~p \tAUTO_INCREMENT OFFSET -> NOT SET~n", [TableName]), + io:format("\t\tCurrent: ~p, Start: ~p, End:~p~n", [CurrentOffset, Start, End]), + io:format("\t\tSetting to: ~p", [Start]), + case set_auto_increment_offset(TableName, Start) of + true -> io:format(" -> OK~n~n"); + _ -> io:format(" -> Failed~n~n") + end + end + end + end, + lists:foreach(F, ?AUTO_ID_TABLES), + io:format("~nTables Auto Increment Done~n"), + io:format("--------------------------------------------------~n"); +set_auto_increment(_) -> + skip. + +%%Auto_Increment字段在第11位 +get_auto_increment_offset(TableName) -> + Sql = lists:concat(["show table status where name='", TableName, "'"]), + case lists:nth(11, db_esql:get_row(Sql)) of + Offset when is_integer(Offset) -> + Offset; + _Error -> + io:format("ERROR when getting Auto_Increment for table ~p~n", [TableName]), + error + end. + +%%获取数据表的AUTO_INCREMENT字段的宽度 +get_auto_increment_width(TableName) -> + Sql = lists:concat(["show create table ", TableName]), + case db_esql:get_row(Sql) of + {db_error, _} -> + error; + [_, A | _] -> + CreateTableList = re:split(A, "[\n]", [{return, binary}]), + search_auto_increment(CreateTableList) + end. + +%%设置AUTO_INCREMENT的值 +set_auto_increment_offset(TableName, Offset) -> + Sql = io_lib:format("alter table ~s auto_increment=~s;", [atom_to_list(TableName), integer_to_list(Offset)]), + case db_esql:execute_sql(Sql) of + {db_error, _} -> + false; + _Result -> + %io:format("Result: ~p~n", [Result]) + true + end. + +%%搜索有没有含AUTO_INCRMENT字段的行 +%%如果没有,返回no_match +%%如果有: 搜索AUTO_INCREMENT行中"int(xx)"并返回xx的值, 没有返回no_match +search_auto_increment([]) -> + no_match; +search_auto_increment([L | T]) -> + Line = binary_to_list(L), + case re:run(Line, "AUTO_INCREMENT", [caseless]) of %%匹配AUTO_INCREMENT行 + {match, _} -> %%有 + search_int_width(Line); + _Other -> + search_auto_increment(T) + end. + +%%搜索"int(xx)"并返回xx的值, 没有返回no_match +search_int_width(Line) -> + case re:run(Line, "int", [caseless]) of + {match, [{Idx1, L1} | _]} -> %%匹配上,Idx1, L1是"int"起始坏置及长度 + Idx = min(Idx1 + L1 + 1, length(Line)), + case lists:sublist(Line, Idx, 4) of %%取"(xx)"这一段, 最多取4字符 + [40, A, B, 41] -> %%40为"(", 41为")",长度为两数字 + list_to_integer([A, B]); + [40, A, 41] -> %%长度为1数字 + list_to_integer([A]); + _Other -> + no_match + end; + _Other -> + no_match + end. + + diff --git a/src/srvNodeMgr/tools/gameWorld/test/misc/config.erl b/src/srvNodeMgr/tools/gameWorld/test/misc/config.erl new file mode 100644 index 0000000..dbc4f1d --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/misc/config.erl @@ -0,0 +1,332 @@ +-module(config). +-include("record.hrl"). +-include("common.hrl"). +-include_lib("stdlib/include/ms_transform.hrl"). + +-compile(export_all). + +%% 获取 .config里的配置信息 +get_log_path(App) -> + case application:get_env(App, log_path) of + {ok, LogPath} -> LogPath; + _ -> "logs" + end. + +%% 获取 .config里的配置信息 +get_log_level(App) -> + case application:get_env(App, log_level) of + {ok, LogLevel} -> LogLevel; + _ -> 3 + end. + +%% 获取 .config里的配置信息 +get_server_node(App) -> + case application:get_env(App, game_server_node) of + {ok, Node} -> Node; + _ -> '' + end. +%% 获取平台重置的key +get_charge_key(App) -> + case application:get_env(App, charge_key) of + {ok, Node} -> Node; + _ -> '' + end. + +%% 获取 .config里的配置信息 +get_md5_key(App) -> + case application:get_env(App, md5_key) of + {ok, Node} -> Node; + _ -> '' + end. + +%% 获取 .config里的配置信息 +get_platform_key(App) -> + case application:get_env(App, platform_key) of + {ok, Node} -> Node; + _ -> '' + end. + +get_infant_ctrl(App) -> +%% 防沉迷开关读取 + case application:get_env(App, infant_ctrl) of + {ok, Mode} -> tool:to_integer(Mode); + _ -> 0 + end. + +get_tcp_listener(App) -> + case application:get_env(App, tcp_listener) of + {ok, false} -> throw(undefined); + {ok, Tcp_listener} -> + try + {_, Port} = lists:keyfind(port, 1, Tcp_listener), + {_, Acceptor_num} = lists:keyfind(acceptor_num, 1, Tcp_listener), + {_, Max_connections} = lists:keyfind(max_connections, 1, Tcp_listener), + [Port, Acceptor_num, Max_connections] + catch + _:_ -> exit({bad_config, {server, {tcp_listener, config_error}}}) + end; + undefined -> throw(undefined) + end. + +get_tcp_listener_ip(App) -> + case application:get_env(App, tcp_listener_ip) of + {ok, false} -> throw(undefined); + {ok, Tcp_listener_ip} -> + try + {_, Ip} = lists:keyfind(ip, 1, Tcp_listener_ip), + [Ip] + catch + _:_ -> exit({bad_config, {server, {tcp_listener, config_error}}}) + end; + undefined -> throw(undefined) + end. + +get_mysql_config(App) -> + case application:get_env(App, mysql_config) of + {ok, false} -> throw(undefined); + {ok, Mysql_config} -> + {_, Host} = lists:keyfind(host, 1, Mysql_config), + {_, Port} = lists:keyfind(port, 1, Mysql_config), + {_, User} = lists:keyfind(user, 1, Mysql_config), + {_, Password} = lists:keyfind(password, 1, Mysql_config), + {_, DB} = lists:keyfind(db, 1, Mysql_config), + {_, Encode} = lists:keyfind(encode, 1, Mysql_config), + {_, Conns} = lists:keyfind(conns, 1, Mysql_config), + [Host, Port, User, Password, DB, Encode, Conns]; + undefined -> throw(undefined) + end. + +get_mysql_log_config(App) -> + case application:get_env(App, mysql_log_config) of + {ok, false} -> throw(undefined); + {ok, Mysql_config} -> + {_, Host} = lists:keyfind(host, 1, Mysql_config), + {_, Port} = lists:keyfind(port, 1, Mysql_config), + {_, User} = lists:keyfind(user, 1, Mysql_config), + {_, Password} = lists:keyfind(password, 1, Mysql_config), + {_, DB} = lists:keyfind(db, 1, Mysql_config), + {_, Encode} = lists:keyfind(encode, 1, Mysql_config), + {_, Conns} = lists:keyfind(conns, 1, Mysql_config), + [Host, Port, User, Password, DB, Encode, Conns]; + undefined -> throw(undefined) + end. + +get_mongo_config(App) -> + case application:get_env(App, emongo_config) of + {ok, false} -> throw(undefined); + {ok, Emongo_config} -> + {_, PoolId} = lists:keyfind(poolId, 1, Emongo_config), + {_, EmongoSize} = lists:keyfind(emongoSize, 1, Emongo_config), + {_, EmongoHost} = lists:keyfind(emongoHost, 1, Emongo_config), + {_, EmongoPort} = lists:keyfind(emongoPort, 1, Emongo_config), + {_, EmongoDatabase} = lists:keyfind(emongoDatabase, 1, Emongo_config), + [PoolId, EmongoHost, EmongoPort, EmongoDatabase, EmongoSize]; + undefined -> throw(undefined) + end. + +get_slave_mongo_config(App) -> + case application:get_env(App, slave_emongo_config) of + {ok, false} -> throw(undefined); + {ok, Emongo_config} -> + {_, PoolId} = lists:keyfind(poolId, 1, Emongo_config), + {_, EmongoSize} = lists:keyfind(emongoSize, 1, Emongo_config), + {_, EmongoHost} = lists:keyfind(emongoHost, 1, Emongo_config), + {_, EmongoPort} = lists:keyfind(emongoPort, 1, Emongo_config), + {_, EmongoDatabase} = lists:keyfind(emongoDatabase, 1, Emongo_config), + [PoolId, EmongoHost, EmongoPort, EmongoDatabase, EmongoSize]; + undefined -> get_mongo_config(App) + end. + +has_slave_mongo_config() -> + case application:get_env(server, slave_emongo_config) of + {ok, false} -> false; + {ok, _Emongo_config} -> true + end. + +get_read_data_mode(App) -> + case application:get_env(App, base_data_from_db) of + {ok, Mode} -> tool:to_integer(Mode); + _ -> 0 + end. + +get_gateway_node(App) -> + case application:get_env(App, gateway_node) of + {ok, Gateway_node} -> Gateway_node; + _ -> undefined + end. + +get_gateway_async_time() -> + case application:get_env(gateway, gateway_async_time) of + {ok, Async_time} -> Async_time; + _ -> undefined + end. + + +get_scene_here(App) -> + case application:get_env(App, scene_here) of + undefined -> []; + {ok, all} -> + MS = ets:fun2ms(fun(S) when S#temp_scene.type =< 3 -> S#temp_scene.sid end), + ets:select(?ETS_TEMP_SCENE, MS); + {ok, SL} when is_list(SL) -> + SL1 = lists:filter(fun(SId) -> + case ets:lookup(?ETS_TEMP_SCENE, SId) of + [] -> false; + [S] -> + if S#temp_scene.type =< 3 -> + true; + true -> + false + end + end + end, + SL), + SL1; + _ -> [] + end. + +get_guest_account_url(App) -> + case application:get_env(App, guest_account_url) of + {ok, Guest_account_url} -> Guest_account_url; + _ -> "" + end. + +%% GM指令开关读取 +get_can_gmcmd() -> + case application:get_env(server, can_gmcmd) of + {ok, Mode} -> tool:to_integer(Mode); + _ -> throw(undefined) + end. + +get_strict_md5(App) -> + case application:get_env(App, strict_md5) of + {ok, Strict_md5} -> Strict_md5; + _ -> 1 + end. + +get_http_ips(App) -> + case application:get_env(App, http_ips) of + {ok, Http_ips} -> Http_ips; + _ -> [] + end. + +%% 获取 .config里的配置信息 +get_server_number(App) -> + case application:get_env(App, server_number) of + {ok, Server_number} -> + case is_integer(Server_number) == true of + false -> 0; + true -> Server_number + end; + _ -> 0 + end. + +%% 获取 .config里的配置信息 +get_max_id(App) -> + case application:get_env(App, max_id) of + {ok, Max_id} -> + case is_integer(Max_id) == true of + false -> 0; + true -> Max_id + end; + _ -> 0 + end. + +%% 获取平台名称 +get_platform_name() -> + case application:get_env(server, platform) of + {ok, Name} -> + Name; + _ -> + undefined + end. +%% 获取加密串号 +get_card_key() -> + case application:get_env(server, card_key) of + {ok, Crypto} -> + Crypto; + _ -> + undefined + end. + +%% 获取服号 +get_server_num() -> + case application:get_env(server, server_num) of + {ok, N} -> + N; + _ -> + undefined + end. + +%%获取数据库日志路径 +get_db_log_path() -> + case application:get_env(server, db_log_path) of + {ok, N} -> + N; + _ -> + undefined + end. + +%%获取开服时间 +get_opening_time() -> + case application:get_env(server, open_time) of + {ok, N} -> + N; + _ -> + 0 + end. + + +%%获取节点1的id +get_domain() -> + case application:get_env(server, domain) of + {ok, N} -> + N; + _ -> + 1 + end. + + +%%获取节点2的id +get_node_id_2() -> + case application:get_env(server, node_id_2) of + {ok, N} -> + N; + _ -> + 2 + end. + +%%获取客户端操作系统 +get_client_os(Os) -> + case Os of + 1 -> + <<"Android">>; + 2 -> + <<"iphone">>; + _ -> + <<"未知">> + end. +%%获取客户端设备类型 +get_client_device_type(DeviceType) -> + case DeviceType of + 1 -> <<"Android">>; + 2 -> <<"iphone">>; + 3 -> <<"ipad">>; + _ -> <<"未知">> + end. +%%获取客户端移动网络类型 +get_client_isp(Isp) -> + case Isp of + 1 -> <<"中国移动">>; + 2 -> <<"中国电信">>; + 3 -> <<"中国联通">>; + _ -> "未知" + end. +%%获取客户端上网方式 +get_client_net_type(Net) -> + case Net of + 1 -> <<"3G">>; + 2 -> <<"WIFI">>; + 3 -> <<"2G">>; + _ -> <<"未知">> + end. \ No newline at end of file diff --git a/src/srvNodeMgr/tools/gameWorld/test/misc/dynamic_compile.erl b/src/srvNodeMgr/tools/gameWorld/test/misc/dynamic_compile.erl new file mode 100644 index 0000000..e25e691 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/misc/dynamic_compile.erl @@ -0,0 +1,270 @@ +%% Copyright (c) 2007 +%% Mats Cronqvist +%% Chris Newcombe +%% Jacob Vorreuter +%% +%% Permission is hereby granted, free of charge, to any person +%% obtaining a copy of this software and associated documentation +%% files (the "Software"), to deal in the Software without +%% restriction, including without limitation the rights to use, +%% copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the +%% Software is furnished to do so, subject to the following +%% conditions: +%% +%% The above copyright notice and this permission notice shall be +%% included in all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +%% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +%% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +%% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +%% OTHER DEALINGS IN THE SOFTWARE. + +%%%------------------------------------------------------------------- +%%% File : dynamic_compile.erl +%%% Description : +%%% Authors : Mats Cronqvist +%%% Chris Newcombe +%%% Jacob Vorreuter +%%% - add support for limit include-file depth (and prevent circular references) +%%% prevent circular macro expansion set FILE correctly when -module() is found +%%% -include_lib support $ENVVAR in include filenames +%%% substitute-stringize (??MACRO) +%%% -undef/-ifdef/-ifndef/-else/-endif +%%% -file(File, Line) +%%%------------------------------------------------------------------- +-module(dynamic_compile). + +%% API +-export([from_string/1, from_string/2]). + +-import(lists, [reverse/1, keyreplace/4]). + +%%==================================================================== +%% API +%%==================================================================== +%%-------------------------------------------------------------------- +%% Function: +%% Description: +%% Returns a binary that can be used with +%% code:load_binary(Module, ModuleFilenameForInternalRecords, Binary). +%%-------------------------------------------------------------------- +from_string(CodeStr) -> + from_string(CodeStr, []). + +% takes Options as for compile:forms/2 +from_string(CodeStr, CompileFormsOptions) -> + %% Initialise the macro dictionary with the default predefined macros, + %% (adapted from epp.erl:predef_macros/1 + Filename = "compiled_from_string", + %%Machine = list_to_atom(erlang:system_info(machine)), + Ms0 = dict:new(), + % Ms1 = dict:store('FILE', {[], "compiled_from_string"}, Ms0), + % Ms2 = dict:store('LINE', {[], 1}, Ms1), % actually we might add special code for this + % Ms3 = dict:store('MODULE', {[], undefined}, Ms2), + % Ms4 = dict:store('MODULE_STRING', {[], undefined}, Ms3), + % Ms5 = dict:store('MACHINE', {[], Machine}, Ms4), + % InitMD = dict:store(Machine, {[], true}, Ms5), + InitMD = Ms0, + + %% From the docs for compile:forms: + %% When encountering an -include or -include_dir directive, the compiler searches for header files in the following directories: + %% 1. ".", the current working directory of the file server; + %% 2. the base name of the compiled file; + %% 3. the directories specified using the i option. The directory specified last is searched first. + %% In this case, #2 is meaningless. + IncludeSearchPath = ["." | reverse([Dir || {i, Dir} <- CompileFormsOptions])], + {RevForms, _OutMacroDict} = scan_and_parse(CodeStr, Filename, 1, [], InitMD, IncludeSearchPath), + Forms = reverse(RevForms), + + %% note: 'binary' is forced as an implicit option, whether it is provided or not. + case compile:forms(Forms, CompileFormsOptions) of + {ok, ModuleName, CompiledCodeBinary} when is_binary(CompiledCodeBinary) -> + {ModuleName, CompiledCodeBinary}; + {ok, ModuleName, CompiledCodeBinary, []} when is_binary(CompiledCodeBinary) -> % empty warnings list + {ModuleName, CompiledCodeBinary}; + {ok, _ModuleName, _CompiledCodeBinary, Warnings} -> + throw({?MODULE, warnings, Warnings}); + Other -> + throw({?MODULE, compile_forms, Other}) + end. + +%%==================================================================== +%% Internal functions +%%==================================================================== +%%% Code from Mats Cronqvist +%%% See http://www.erlang.org/pipermail/erlang-questions/2007-March/025507.html +%%%## 'scan_and_parse' +%%% +%%% basically we call the OTP scanner and parser (erl_scan and +%%% erl_parse) line-by-line, but check each scanned line for (or +%%% definitions of) macros before parsing. +%% returns {ReverseForms, FinalMacroDict} +scan_and_parse([], _CurrFilename, _CurrLine, RevForms, MacroDict, _IncludeSearchPath) -> + {RevForms, MacroDict}; + +scan_and_parse(RemainingText, CurrFilename, CurrLine, RevForms, MacroDict, IncludeSearchPath) -> + case scanner(RemainingText, CurrLine, MacroDict) of + {tokens, NLine, NRemainingText, Toks} -> + {ok, Form} = erl_parse:parse_form(Toks), + scan_and_parse(NRemainingText, CurrFilename, NLine, [Form | RevForms], MacroDict, IncludeSearchPath); + {macro, NLine, NRemainingText, NMacroDict} -> + scan_and_parse(NRemainingText, CurrFilename, NLine, RevForms, NMacroDict, IncludeSearchPath); + {include, NLine, NRemainingText, IncludeFilename} -> + IncludeFileRemainingTextents = read_include_file(IncludeFilename, IncludeSearchPath), + %%io:format("include file ~p contents: ~n~p~nRemainingText = ~p~n", [IncludeFilename,IncludeFileRemainingTextents, RemainingText]), + %% Modify the FILE macro to reflect the filename + %%IncludeMacroDict = dict:store('FILE', {[],IncludeFilename}, MacroDict), + IncludeMacroDict = MacroDict, + + %% Process the header file (inc. any nested header files) + {RevIncludeForms, IncludedMacroDict} = scan_and_parse(IncludeFileRemainingTextents, IncludeFilename, 1, [], IncludeMacroDict, IncludeSearchPath), + %io:format("include file results = ~p~n", [R]), + %% Restore the FILE macro in the NEW MacroDict (so we keep any macros defined in the header file) + %%NMacroDict = dict:store('FILE', {[],CurrFilename}, IncludedMacroDict), + NMacroDict = IncludedMacroDict, + + %% Continue with the original file + scan_and_parse(NRemainingText, CurrFilename, NLine, RevIncludeForms ++ RevForms, NMacroDict, IncludeSearchPath); + done -> + scan_and_parse([], CurrFilename, CurrLine, RevForms, MacroDict, IncludeSearchPath) + end. + +scanner(Text, Line, MacroDict) -> + case erl_scan:tokens([], Text, Line) of + {done, {ok, Toks, NLine}, LeftOverChars} -> + case pre_proc(Toks, MacroDict) of + {tokens, NToks} -> {tokens, NLine, LeftOverChars, NToks}; + {macro, NMacroDict} -> {macro, NLine, LeftOverChars, NMacroDict}; + {include, Filename} -> {include, NLine, LeftOverChars, Filename} + end; + {more, _Continuation} -> + %% This is supposed to mean "term is not yet complete" (i.e. a '.' has + %% not been reached yet). + %% However, for some bizarre reason we also get this if there is a comment after the final '.' in a file. + %% So we check to see if Text only consists of comments. + case is_only_comments(Text) of + true -> + done; + false -> + throw({incomplete_term, Text, Line}) + end + end. + +is_only_comments(Text) -> is_only_comments(Text, not_in_comment). + +is_only_comments([], _) -> true; +is_only_comments([$ | T], not_in_comment) -> + is_only_comments(T, not_in_comment); % skipping whitspace outside of comment +is_only_comments([$\t | T], not_in_comment) -> + is_only_comments(T, not_in_comment); % skipping whitspace outside of comment +is_only_comments([$\n | T], not_in_comment) -> + is_only_comments(T, not_in_comment); % skipping whitspace outside of comment +is_only_comments([$% | T], not_in_comment) -> is_only_comments(T, in_comment); % found start of a comment +is_only_comments(_, not_in_comment) -> false; +% found any significant char NOT in a comment +is_only_comments([$\n | T], in_comment) -> is_only_comments(T, not_in_comment); % found end of a comment +is_only_comments([_ | T], in_comment) -> is_only_comments(T, in_comment). % skipping over in-comment chars + +%%%## 'pre-proc' +%%% +%%% have to implement a subset of the pre-processor, since epp insists +%%% on running on a file. +%%% only handles 2 cases; +%% -define(MACRO, something). +%% -define(MACRO(VAR1,VARN),{stuff,VAR1,more,stuff,VARN,extra,stuff}). +pre_proc([{'-', _}, {atom, _, define}, {'(', _}, {_, _, Name} | DefToks], MacroDict) -> + false = dict:is_key(Name, MacroDict), + case DefToks of + [{',', _} | Macro] -> + {macro, dict:store(Name, {[], macro_body_def(Macro, [])}, MacroDict)}; + [{'(', _} | Macro] -> + {macro, dict:store(Name, macro_params_body_def(Macro, []), MacroDict)} + end; + +pre_proc([{'-', _}, {atom, _, include}, {'(', _}, {string, _, Filename}, {')', _}, {dot, _}], _MacroDict) -> + {include, Filename}; + +pre_proc(Toks, MacroDict) -> + {tokens, subst_macros(Toks, MacroDict)}. + +macro_params_body_def([{')', _}, {',', _} | Toks], RevParams) -> + {reverse(RevParams), macro_body_def(Toks, [])}; +macro_params_body_def([{var, _, Param} | Toks], RevParams) -> + macro_params_body_def(Toks, [Param | RevParams]); +macro_params_body_def([{',', _}, {var, _, Param} | Toks], RevParams) -> + macro_params_body_def(Toks, [Param | RevParams]). + +macro_body_def([{')', _}, {dot, _}], RevMacroBodyToks) -> + reverse(RevMacroBodyToks); +macro_body_def([Tok | Toks], RevMacroBodyToks) -> + macro_body_def(Toks, [Tok | RevMacroBodyToks]). + +subst_macros(Toks, MacroDict) -> + reverse(subst_macros_rev(Toks, MacroDict, [])). + +%% returns a reversed list of tokes +subst_macros_rev([{'?', _}, {_, LineNum, 'LINE'} | Toks], MacroDict, RevOutToks) -> + %% special-case for ?LINE, to avoid creating a new MacroDict for every line in the source file + subst_macros_rev(Toks, MacroDict, [{integer, LineNum, LineNum}] ++ RevOutToks); + +subst_macros_rev([{'?', _}, {_, _, Name}, {'(', _} = Paren | Toks], MacroDict, RevOutToks) -> + case dict:fetch(Name, MacroDict) of + {[], MacroValue} -> + %% This macro does not have any vars, so ignore the fact that the invocation is followed by "(...stuff" + %% Recursively expand any macro calls inside this macro's value + %% TODO: avoid infinite expansion due to circular references (even indirect ones) + RevExpandedOtherMacrosToks = subst_macros_rev(MacroValue, MacroDict, []), + subst_macros_rev([Paren | Toks], MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks); + ParamsAndBody -> + %% This macro does have vars. + %% Collect all of the passe arguments, in an ordered list + {NToks, Arguments} = subst_macros_get_args(Toks, []), + %% Expand the varibles + ExpandedParamsToks = subst_macros_subst_args_for_vars(ParamsAndBody, Arguments), + %% Recursively expand any macro calls inside this macro's value + %% TODO: avoid infinite expansion due to circular references (even indirect ones) + RevExpandedOtherMacrosToks = subst_macros_rev(ExpandedParamsToks, MacroDict, []), + subst_macros_rev(NToks, MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks) + end; + +subst_macros_rev([{'?', _}, {_, _, Name} | Toks], MacroDict, RevOutToks) -> + %% This macro invocation does not have arguments. + %% Therefore the definition should not have parameters + {[], MacroValue} = dict:fetch(Name, MacroDict), + + %% Recursively expand any macro calls inside this macro's value + %% TODO: avoid infinite expansion due to circular references (even indirect ones) + RevExpandedOtherMacrosToks = subst_macros_rev(MacroValue, MacroDict, []), + subst_macros_rev(Toks, MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks); + +subst_macros_rev([Tok | Toks], MacroDict, RevOutToks) -> + subst_macros_rev(Toks, MacroDict, [Tok | RevOutToks]); +subst_macros_rev([], _MacroDict, RevOutToks) -> RevOutToks. + +subst_macros_get_args([{')', _} | Toks], RevArgs) -> + {Toks, reverse(RevArgs)}; +subst_macros_get_args([{',', _}, {var, _, ArgName} | Toks], RevArgs) -> + subst_macros_get_args(Toks, [ArgName | RevArgs]); +subst_macros_get_args([{var, _, ArgName} | Toks], RevArgs) -> + subst_macros_get_args(Toks, [ArgName | RevArgs]). + +subst_macros_subst_args_for_vars({[], BodyToks}, []) -> + BodyToks; +subst_macros_subst_args_for_vars({[Param | Params], BodyToks}, [Arg | Args]) -> + NBodyToks = keyreplace(Param, 3, BodyToks, {var, 1, Arg}), + subst_macros_subst_args_for_vars({Params, NBodyToks}, Args). + +read_include_file(Filename, IncludeSearchPath) -> + case file:path_open(IncludeSearchPath, Filename, [read, raw, binary]) of + {ok, IoDevice, FullName} -> + {ok, Data} = file:read(IoDevice, filelib:file_size(FullName)), + file:close(IoDevice), + binary_to_list(Data); + {error, Reason} -> + throw({failed_to_read_include_file, Reason, Filename, IncludeSearchPath}) + end. \ No newline at end of file diff --git a/src/srvNodeMgr/tools/gameWorld/test/misc/game_alarm_handler.erl b/src/srvNodeMgr/tools/gameWorld/test/misc/game_alarm_handler.erl new file mode 100644 index 0000000..42854af --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/misc/game_alarm_handler.erl @@ -0,0 +1,41 @@ +%%%---------------------------------------- +%%% @Module : game_alarm_handler +%%% @Author : csj +%%% @Created : 2010.10.05 +%%% @Description: 警报 +%%%---------------------------------------- + +-module(game_alarm_handler). +-behaviour(gen_event). +-include("common.hrl"). + +%%gen_envent callbacks. +-export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2, code_change/3]). + +init(Args) -> + ?DEBUG("game_alarm_handler init : ~p.", [Args]), + {ok, Args}. + +handle_event({set_alarm, From}, _State) -> + ?DEBUG("csj depot clear by ~p started.", [From]), + {ok, _State}; + +handle_event({clear_alarm, From}, _State) -> + ?DEBUG("csj depot clear by ~p done.", [From]), + {ok, _State}; + +handle_event(Event, State) -> + ?DEBUG("unmatched event: ~p.", [Event]), + {ok, State}. + +handle_call(_Req, State) -> + {ok, State, State}. + +handle_info(_Info, State) -> + {ok, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/src/srvNodeMgr/tools/gameWorld/test/misc/game_timer.erl b/src/srvNodeMgr/tools/gameWorld/test/misc/game_timer.erl new file mode 100644 index 0000000..57d7dc4 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/misc/game_timer.erl @@ -0,0 +1,131 @@ +%%%---------------------------------------------------------------------- +%%% File : game_timer.erl +%%% Author : csj +%%% Created : 2010-10-17 +%%% Description: 时间生成器 +%%%---------------------------------------------------------------------- +-module(game_timer). + +-behaviour(gen_server). +%% -------------------------------------------------------------------- +%% Include files +%% -------------------------------------------------------------------- +-include("common.hrl"). + +%% -------------------------------------------------------------------- +%% External exports +-export([now/0, now_seconds/0, cpu_time/0, start_link/0, start/1, info/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). + +%% ==================================================================== +%% External functions +%% ==================================================================== +now() -> + [{timer, {Now, _}}] = ets:lookup(ets_timer, timer), + Now. + +now_seconds() -> + [{timer, {Now, _}}] = ets:lookup(ets_timer, timer), + {MegaSecs, Secs, _MicroSecs} = Now, + lists:concat([MegaSecs, Secs]). + +cpu_time() -> + [{timer, {_, Wallclock_Time_Since_Last_Call}}] = ets:lookup(ets_timer, timer), + Wallclock_Time_Since_Last_Call. + +info() -> + [ + ets:info(ets_timer), + ets:tab2list(ets_timer) + ]. + +-define(CLOCK, 100). + +start(Sup) -> + supervisor:start_child(Sup, + {game_timer, + {game_timer, start_link, []}, + permanent, brutal_kill, worker, [game_timer]}). + +%% ==================================================================== +%% Server functions +%% ==================================================================== +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +%% -------------------------------------------------------------------- +%% Function: init/1 +%% Description: Initiates the server +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% -------------------------------------------------------------------- +init([]) -> + ets:new(ets_timer, [set, protected, named_table]), + ets:insert(ets_timer, {timer, {erlang:now(), 0}}), + erlang:send_after(?CLOCK, self(), {event, clock}), + misc:write_monitor_pid(self(), ?MODULE, {}), + {ok, []}. + +%% -------------------------------------------------------------------- +%% Function: handle_call/3 +%% Description: Handling call messages +%% Returns: {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | (terminate/2 is called) +%% {stop, Reason, State} (terminate/2 is called) +%% -------------------------------------------------------------------- +handle_call(_Request, _From, State) -> + {reply, State, State}. + +%% -------------------------------------------------------------------- +%% Function: handle_cast/2 +%% Description: Handling cast messages +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%% -------------------------------------------------------------------- +handle_cast(_Msg, State) -> + {noreply, State}. + +%% -------------------------------------------------------------------- +%% Function: handle_info/2 +%% Description: Handling all non call/cast messages +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%% -------------------------------------------------------------------- +handle_info({event, clock}, State) -> + {_Total_Run_Time, Time_Since_Last_Call} = statistics(runtime), + ets:insert(ets_timer, {timer, {erlang:now(), Time_Since_Last_Call}}), + erlang:send_after(?CLOCK, self(), {event, clock}), + {noreply, State}; + +handle_info(_Info, State) -> + {noreply, State}. + +%% -------------------------------------------------------------------- +%% Function: terminate/2 +%% Description: Shutdown the server +%% Returns: any (ignored by gen_server) +%% -------------------------------------------------------------------- +terminate(_Reason, _State) -> + misc:delete_monitor_pid(self()), + ok. + +%% -------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Convert process state when code is changed +%% Returns: {ok, NewState} +%% -------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%% -------------------------------------------------------------------- +%%% Internal functions +%% -------------------------------------------------------------------- diff --git a/src/srvNodeMgr/tools/gameWorld/test/misc/http_lib.erl b/src/srvNodeMgr/tools/gameWorld/test/misc/http_lib.erl new file mode 100644 index 0000000..2fe39a0 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/misc/http_lib.erl @@ -0,0 +1,348 @@ +%% @author Ruslan Babayev +%% @copyright 2009 Ruslan Babayev +%% @doc HTTP Encoding and Utility Library. + +-module(http_lib). +-export([uri_to_path/1, encode/1, list_to_absoluteURI/1, etag/1, + local_time_to_rfc1123/1, rfc1123_to_date_time/1, mime_type/1, + mime_type/2, extension/1, is_compressible/1, month_to_list/1, + url_decode/1, chunk/1, response/1, response/2, + accept/1, recv/2, recv/3, send/2, setopts/2, close/1, peername/1, + dir/1, is_persistent/1, is_idempotent/1, is_modified/2, + reason_phrase/1]). + +-include("http.hrl"). +-include_lib("kernel/include/file.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +%% -------------------------------------------------------------------- +%% Macros +%% -------------------------------------------------------------------- +-define(CS_DEBUG(F, D), io:format(lists:concat(["D(", ?MODULE, ":", ?LINE, ") :", F, "~n"]), D)). + + +%% @doc Encodes HTTP request or response. +%% @spec encode(#http_request{} | #http_response{}) -> iolist() +encode(#http_request{version = {Major, Minor}, method = Method, + uri = URI, headers = Headers, body = Body}) -> + Headers1 = case proplists:is_defined('Host', Headers) of + true -> + Headers; + false when is_record(URI, absoluteURI) -> + [{'Host', URI#absoluteURI.host} | Headers]; + false -> + Headers + end, + [atom_to_list(Method), " ", uri_to_path(URI), " HTTP/", + integer_to_list(Major), ".", integer_to_list(Minor), "\r\n", + headers(Headers1), "\r\n", Body]; +encode(#http_response{version = {Major, Minor}, status = Status, + headers = Headers0, body = Body}) -> + LocalTime = calendar:now_to_local_time(now()), + Date = local_time_to_rfc1123(LocalTime), + {ok, Server} = application:get_env(http, server), + Headers1 = [{'Date', Date}, {'Server', Server} | Headers0], + ["HTTP/", integer_to_list(Major), ".", integer_to_list(Minor), " ", + integer_to_list(Status), " ", reason_phrase(Status), "\r\n", + headers(Headers1), "\r\n", Body]. + +uri_to_path(#abs_path{path = Path}) -> + Path; +uri_to_path(#absoluteURI{path = Path}) -> + Path; +uri_to_path('*') -> + "*". + +list_to_absoluteURI(List) -> + RE = "^([^:/?#]+):?//([^/?#]*)", + [_, S, HP, Rest] = re:split(List, RE, [{return, list}]), + Scheme = list_to_atom(S), + {Host, Port} = + case re:split(HP, ":", [{return, list}]) of + [H, P] -> {H, list_to_integer(P)}; + [H] when Scheme == http -> {H, 80}; + [H] when Scheme == https -> {H, 443} + end, + Path = case Rest of + "" -> "/"; + Else -> Else + end, + #absoluteURI{scheme = Scheme, host = Host, port = Port, path = Path}. + +headers(Headers) -> + headers(Headers, []). + +headers([], Acc) -> + Acc; +headers([{Name, Value} | T], Acc) -> + Header = [any_to_list(Name), ": ", any_to_list(Value), "\r\n"], + headers(T, Acc ++ Header). + +any_to_list(A) when is_atom(A) -> + atom_to_list(A); +any_to_list(I) when is_integer(I) -> + integer_to_list(I); +any_to_list(L) when is_list(L) -> + L. + +chunk(IoList) -> + [erlang:integer_to_list(iolist_size(IoList), 16), "\r\n", IoList, "\r\n"]. + +local_time_to_rfc1123(LocalTime) -> + [{{YYYY, MM, DD}, {H, M, S}} | _] = + calendar:local_time_to_universal_time_dst(LocalTime), + Day = 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", + [day(Day), DD, month_to_list(MM), YYYY, H, M, S])). + +rfc1123_to_date_time([_D, _A, _Y, $,, $ , D1, D2, $ , M, O, N, $ , Y1, Y2, Y3, Y4, $ , + H1, H2, $:, M1, M2, $:, S1, S2 | _]) -> + Year = list_to_integer([Y1, Y2, Y3, Y4]), + Day = list_to_integer([D1, D2]), + Month = list_to_month([M, O, N]), + Hour = list_to_integer([H1, H2]), + Min = list_to_integer([M1, M2]), + Sec = list_to_integer([S1, S2]), + {{Year, Month, Day}, {Hour, Min, Sec}}. + +day(1) -> "Mon"; +day(2) -> "Tue"; +day(3) -> "Wed"; +day(4) -> "Thu"; +day(5) -> "Fri"; +day(6) -> "Sat"; +day(7) -> "Sun". + +month_to_list(1) -> "Jan"; +month_to_list(2) -> "Feb"; +month_to_list(3) -> "Mar"; +month_to_list(4) -> "Apr"; +month_to_list(5) -> "May"; +month_to_list(6) -> "Jun"; +month_to_list(7) -> "Jul"; +month_to_list(8) -> "Aug"; +month_to_list(9) -> "Sep"; +month_to_list(10) -> "Oct"; +month_to_list(11) -> "Nov"; +month_to_list(12) -> "Dec". + +list_to_month("Jan") -> 1; +list_to_month("Feb") -> 2; +list_to_month("Mar") -> 3; +list_to_month("Apr") -> 4; +list_to_month("May") -> 5; +list_to_month("Jun") -> 6; +list_to_month("Jul") -> 7; +list_to_month("Aug") -> 8; +list_to_month("Sep") -> 9; +list_to_month("Oct") -> 10; +list_to_month("Nov") -> 11; +list_to_month("Dec") -> 12. + +is_modified(#file_info{mtime = Mtime}, Since) -> + erlang:localtime_to_universaltime(Mtime) > rfc1123_to_date_time(Since). + +is_idempotent('HEAD') -> true; +is_idempotent('GET') -> true; +is_idempotent('PUT') -> true; +is_idempotent('DELETE') -> true; +is_idempotent('TRACE') -> true; +is_idempotent('OPTIONS') -> true; +is_idempotent(_) -> false. + +is_persistent(#http_request{version = Version, headers = Headers}) -> + Connection = proplists:get_value('Connection', Headers, ""), + is_persistent(Version, string:to_lower(Connection)). + +is_persistent(Version, _) when Version < {1, 0} -> false; +is_persistent({1, 0}, "keep-alive") -> true; +is_persistent({1, 0}, _) -> false; +is_persistent(Version, "close") when Version >= {1, 1} -> false; +is_persistent(Version, _) when Version >= {1, 1} -> true. + +url_decode(URL) -> + url_decode(URL, []). + +url_decode([], Acc) -> + lists:reverse(Acc); +url_decode([37, H, L | T], Acc) -> + url_decode(T, [erlang:list_to_integer([H, L], 16) | Acc]); +url_decode([$+ | T], Acc) -> + url_decode(T, [32 | Acc]); +url_decode([H | T], Acc) -> + url_decode(T, [H | Acc]). + +url_decode_test() -> + ?assertEqual("I am decoded", url_decode("I+am+decoded")), + ?assertEqual("#$%&+=<> /", url_decode("%23%24%25%26%2B%3D%3C%3D%20%2F")). + +%% @doc Generates Etag based on file modification time and size. +%% @spec etag(#file_info{}) -> string() +etag(#file_info{mtime = MTime, size = Size}) -> + {{Year, Month, Day}, {Hour, Min, Sec}} = MTime, + F = fun(X) when X =< 25 -> X + $A; + (X) when X =< 50 -> X - 26 + $a; + (X) -> X - 3 + end, + SizeStr = integer_to_list(Size), + lists:map(F, [X rem 60 || X <- [Year, Month, Day, Hour, Min, Sec]]) ++ SizeStr. + +mime_type(Path) -> + mime_type(Path, undefined). + +mime_type(Path, Default) -> + Ext = extension(Path), + case ets:lookup(http_mime_types, Ext) of + [{mime_type, Ext, Type, _Compressible} | _] -> + Type; + [] -> + Default + end. + +extension(Path) -> + case filename:extension(Path) of + [] -> + []; + Extension -> + tl(Extension) + end. + +is_compressible(Path) -> + Ext = extension(Path), + case ets:lookup(http_mime_types, Ext) of + [{mime_type, Ext, _Type, Compressible} | _] -> + Compressible; + [] -> + true + end. + +response(Code) -> + response(Code, reason_phrase(Code)). + +response(Code, Reason) -> + Headers = [{'Content-Type', "text/plain"}, + {'Content-Length', iolist_size(Reason)}], + #http_response{status = Code, headers = Headers, body = Reason}. + +dir(priv_dir) -> + priv_dir(http); +dir({priv_dir, Module}) -> + priv_dir(Module); +dir({priv_dir, Module, SubDir}) -> + filename:join([priv_dir(Module), any_to_list(SubDir)]); +dir({lib_dir, SubDir}) -> + code:lib_dir(http, SubDir); +dir({lib_dir, App, SubDir}) -> + code:lib_dir(App, SubDir); +dir(Path) when is_list(Path) -> + Path. + +priv_dir(Module) -> + filename:join(get_base_dir(Module), "priv"). + +get_base_dir(Module) -> + {file, Here} = code:is_loaded(Module), + filename:dirname(filename:dirname(Here)). + +accept(Socket) when element(1, Socket) == sslsocket -> + case ssl:transport_accept(Socket) of + {ok, SSLSocket} -> + case ssl:ssl_accept(SSLSocket) of + ok -> + {ok, SSLSocket}; + Else -> + Else + end; + Else -> + Else + end; +accept(Socket) -> + gen_tcp:accept(Socket). + +recv(Socket, Length) when element(1, Socket) == sslsocket -> + ssl:recv(Socket, Length); +recv(Socket, Length) -> + gen_tcp:recv(Socket, Length). + +recv(Socket, Length, Timeout) when element(1, Socket) == sslsocket -> + ssl:recv(Socket, Length, Timeout); +recv(Socket, Length, Timeout) -> + gen_tcp:recv(Socket, Length, Timeout). + +send(Socket, Data) when element(1, Socket) == sslsocket -> + ssl:send(Socket, Data); +send(Socket, Data) -> + gen_tcp:send(Socket, Data). + +setopts(Socket, Options) when element(1, Socket) == sslsocket -> + ssl:setopts(Socket, Options); +setopts(Socket, Options) -> + inet:setopts(Socket, Options). + +close(Socket) when element(1, Socket) == sslsocket -> + ssl:close(Socket); +close(Socket) -> + gen_tcp:close(Socket). + +peername(Socket) when element(1, Socket) == sslsocket -> + ssl:peername(Socket); +peername(Socket) -> + gen_tcp:peername(Socket). + +%% RFC 2616, HTTP 1.1 Status codes +reason_phrase(100) -> "Continue"; +reason_phrase(101) -> "Switching Protocols"; +reason_phrase(200) -> "OK"; +reason_phrase(201) -> "Created"; +reason_phrase(202) -> "Accepted"; +reason_phrase(203) -> "Non-Authoritative Information"; +reason_phrase(204) -> "No Content"; +reason_phrase(205) -> "Reset Content"; +reason_phrase(206) -> "Partial Content"; +reason_phrase(300) -> "Multiple Choices"; +reason_phrase(301) -> "Moved Permanently"; +reason_phrase(302) -> "Moved Temporarily"; +reason_phrase(303) -> "See Other"; +reason_phrase(304) -> "Not Modified"; +reason_phrase(305) -> "Use Proxy"; +reason_phrase(306) -> "(unused)"; +reason_phrase(307) -> "Temporary Redirect"; +reason_phrase(400) -> "Bad Request"; +reason_phrase(401) -> "Unauthorized"; +reason_phrase(402) -> "Payment Required"; +reason_phrase(403) -> "Forbidden"; +reason_phrase(404) -> "Object Not Found"; +reason_phrase(405) -> "Method Not Allowed"; +reason_phrase(406) -> "Not Acceptable"; +reason_phrase(407) -> "Proxy Authentication Required"; +reason_phrase(408) -> "Request Time-out"; +reason_phrase(409) -> "Conflict"; +reason_phrase(410) -> "Gone"; +reason_phrase(411) -> "Length Required"; +reason_phrase(412) -> "Precondition Failed"; +reason_phrase(413) -> "Request Entity Too Large"; +reason_phrase(414) -> "Request-URI Too Large"; +reason_phrase(415) -> "Unsupported Media Type"; +reason_phrase(416) -> "Requested Range Not Satisfiable"; +reason_phrase(417) -> "Expectation Failed"; +reason_phrase(500) -> "Internal Server Error"; +reason_phrase(501) -> "Not Implemented"; +reason_phrase(502) -> "Bad Gateway"; +reason_phrase(503) -> "Service Unavailable"; +reason_phrase(504) -> "Gateway Time-out"; +reason_phrase(505) -> "HTTP Version not supported"; +%% RFC 2518, HTTP Extensions for Distributed Authoring -- WEBDAV +reason_phrase(102) -> "Processing"; +reason_phrase(207) -> "Multi-Status"; +reason_phrase(422) -> "Unprocessable Entity"; +reason_phrase(423) -> "Locked"; +reason_phrase(424) -> "Failed Dependency"; +reason_phrase(507) -> "Insufficient Storage"; +%% (Work in Progress) WebDAV Advanced Collections +reason_phrase(425) -> ""; +%% RFC 2817, HTTP Upgrade to TLS +reason_phrase(426) -> "Upgrade Required"; +%% RFC 3229, Delta encoding in HTTP +reason_phrase(226) -> "IM Used"; +reason_phrase(_) -> "Internal Server Error". diff --git a/src/srvNodeMgr/tools/gameWorld/test/misc/logger_h.erl b/src/srvNodeMgr/tools/gameWorld/test/misc/logger_h.erl new file mode 100644 index 0000000..4d8af8f --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/misc/logger_h.erl @@ -0,0 +1,238 @@ +%%%---------------------------------------------------------------------- +%%% File : logger_h.erl +%%% Created : 2010-08-20 +%%%---------------------------------------------------------------------- + +-module(logger_h). + +-behaviour(gen_event). + +-include("common.hrl"). +-include("debug.hrl"). + +-export([start_link/2, log_level/0]). +%% gen_event callbacks +-export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2, + code_change/3, reopen_log/0, rotate_log/1]). + +-record(state, {fd, file, level}). +-define(LOG_FILE_INTERVAL, 24 * 3600000). % 1小时新开1个log文件 + +start_link(Log_file, Log_level) -> + case gen_event:start_link({local, ?LOGMODULE}) of + Ret = {ok, _Pid} -> + gen_event:add_handler(?LOGMODULE, ?MODULE, [Log_file, Log_level]), + Ret; + Other -> + Other + end. + +log_level() -> + gen_event:call({local, ?LOGMODULE}, ?MODULE, log_level). + +%%%---------------------------------------------------------------------- +%%% Callback functions from gen_event +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, State} | +%% Other +%%---------------------------------------------------------------------- +init([File, Level]) -> + FileName = gen_full_log_filename(File, erlang:localtime()), + case file:open(FileName, [append, raw]) of + {ok, Fd} -> + erlang:send_after(?LOG_FILE_INTERVAL, self(), change_log_filename), + {ok, #state{fd = Fd, file = File, level = Level}}; + Error -> + Error + end. + +%%---------------------------------------------------------------------- +%% Func: handle_event/2 +%% Returns: {ok, State} | +%% {swap_handler, Args1, State1, Mod2, Args2} | +%% remove_handler +%%---------------------------------------------------------------------- +handle_event(Event, State) -> + write_event(State#state.fd, {erlang:localtime(), Event}), + {ok, State}. + +%%---------------------------------------------------------------------- +%% Func: handle_call/2 +%% Returns: {ok, Reply, State} | +%% {swap_handler, Reply, Args1, State1, Mod2, Args2} | +%% {remove_handler, Reply} +%%---------------------------------------------------------------------- +handle_call(log_level, State) -> + {ok, State#state.level, State}; +handle_call(_Request, State) -> + Reply = ok, + {ok, Reply, State}. + +%%---------------------------------------------------------------------- +%% Func: handle_info/2 +%% Returns: {ok, State} | +%% {swap_handler, Args1, State1, Mod2, Args2} | +%% remove_handler +%%---------------------------------------------------------------------- +handle_info({'EXIT', _Fd, _Reason}, _State) -> + remove_handler; +handle_info({emulator, _GL, reopen}, State) -> + file:close(State#state.fd), + rotate_log(State#state.file), + case file:open(State#state.file, [append, raw]) of + {ok, Fd} -> + {ok, State#state{fd = Fd}}; + Error -> + Error + end; +handle_info({emulator, GL, Chars}, State) -> + write_event(State#state.fd, {erlang:localtime(), {emulator, GL, Chars}}), + {ok, State}; + +handle_info(change_log_filename, State) -> + file:close(State#state.fd), + FileName = gen_full_log_filename(State#state.file, erlang:localtime()), + NewState = case file:open(FileName, [append, raw]) of + {ok, Fd} -> + State#state{fd = Fd}; + Error -> + ?TRACE("open ~s fail, reason:~p~n", [FileName, Error]), + State + end, + erlang:send_after(?LOG_FILE_INTERVAL, self(), change_log_filename), + {ok, NewState}; + +handle_info(_Info, State) -> + {ok, State}. + +%%---------------------------------------------------------------------- +%% Func: terminate/2 +%% Purpose: Shutdown the server +%% Returns: any +%%---------------------------------------------------------------------- +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +reopen_log() -> + ?LOGMODULE ! {emulator, noproc, reopen}. + +%%%---------------------------------------------------------------------- +%%% Internal functions +%%%---------------------------------------------------------------------- + +% Copied from erlang_logger_file_h.erl +write_event(Fd, {Time, {error, _GL, {Pid, Format, Args}}}) -> + T = write_time(Time), + case catch io_lib:format(add_node(Format, Pid), Args) of + S when is_list(S) -> + file:write(Fd, io_lib:format(T ++ S, [])); + _ -> + F = add_node("ERROR: ~p - ~p~n", Pid), + file:write(Fd, io_lib:format(T ++ F, [Format, Args])) + end; +write_event(Fd, {Time, {emulator, _GL, Chars}}) -> + T = write_time(Time), + case catch io_lib:format(Chars, []) of + S when is_list(S) -> + file:write(Fd, io_lib:format(T ++ S, [])); + _ -> + file:write(Fd, io_lib:format(T ++ "ERROR: ~p ~n", [Chars])) + end; +write_event(Fd, {Time, {info, _GL, {Pid, Info, _}}}) -> + T = write_time(Time), + file:write(Fd, io_lib:format(T ++ add_node("~p~n", Pid), [Info])); +write_event(Fd, {Time, {error_report, _GL, {Pid, std_error, Rep}}}) -> + T = write_time(Time), + S = format_report(Rep), + file:write(Fd, io_lib:format(T ++ S ++ add_node("", Pid), [])); +write_event(Fd, {Time, {info_report, _GL, {Pid, std_info, Rep}}}) -> + T = write_time(Time, "INFO REPORT"), + S = format_report(Rep), + file:write(Fd, io_lib:format(T ++ S ++ add_node("", Pid), [])); +write_event(Fd, {Time, {info_msg, _GL, {Pid, Format, Args}}}) -> + T = write_time(Time, "INFO REPORT"), + case catch io_lib:format(add_node(Format, Pid), Args) of + S when is_list(S) -> + file:write(Fd, io_lib:format(T ++ S, [])); + _ -> + F = add_node("ERROR: ~p - ~p~n", Pid), + file:write(Fd, io_lib:format(T ++ F, [Format, Args])) + end; +write_event(_, _) -> + ok. + +format_report(Rep) when is_list(Rep) -> + case string_p(Rep) of + true -> + io_lib:format("~s~n", [Rep]); + _ -> + format_rep(Rep) + end; +format_report(Rep) -> + io_lib:format("~p~n", [Rep]). + +format_rep([{Tag, Data} | Rep]) -> + io_lib:format(" ~p: ~p~n", [Tag, Data]) ++ format_rep(Rep); +format_rep([Other | Rep]) -> + io_lib:format(" ~p~n", [Other]) ++ format_rep(Rep); +format_rep(_) -> + []. + +add_node(X, Pid) when is_atom(X) -> + add_node(atom_to_list(X), Pid); +add_node(X, Pid) when node(Pid) /= node() -> + lists:concat([X, "** at node ", node(Pid), " **~n"]); +add_node(X, _) -> + X. + +string_p([]) -> + false; +string_p(Term) -> + string_p1(Term). + +string_p1([H | T]) when is_integer(H), H >= $\s, H < 255 -> + string_p1(T); +string_p1([$\n | T]) -> string_p1(T); +string_p1([$\r | T]) -> string_p1(T); +string_p1([$\t | T]) -> string_p1(T); +string_p1([$\v | T]) -> string_p1(T); +string_p1([$\b | T]) -> string_p1(T); +string_p1([$\f | T]) -> string_p1(T); +string_p1([$\e | T]) -> string_p1(T); +string_p1([H | T]) when is_list(H) -> + case string_p1(H) of + true -> string_p1(T); + _ -> false + end; +string_p1([]) -> true; +string_p1(_) -> false. + +write_time(Time) -> write_time(Time, "ERROR REPORT"). + +write_time({{Y, Mo, D}, {H, Mi, S}}, Type) -> + io_lib:format("~n=~s==== ~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w ===", + [Type, Y, Mo, D, H, Mi, S]). + +gen_full_log_filename(File, {{Y, Mo, D}, {H, Mi, S}}) -> + io_lib:format("~s_~w.~.2.0w.~.2.0w_~.2.0w-~.2.0w-~.2.0w", [File, Y, Mo, D, H, Mi, S]). + +%% @doc Rename the log file if exists, to "*-old.log". +%% This is needed in systems when the file must be closed before rotation (Windows). +%% On most Unix-like system, the file can be renamed from the command line and +%% the log can directly be reopened. +%% @spec (Filename::string()) -> ok +rotate_log(Filename) -> + case file:read_file_info(Filename) of + {ok, _FileInfo} -> + RotationName = filename:rootname(Filename), + file:rename(Filename, [RotationName, "-old.log"]), + ok; + {error, _Reason} -> + ok + end. diff --git a/src/srvNodeMgr/tools/gameWorld/test/misc/loglevel.erl b/src/srvNodeMgr/tools/gameWorld/test/misc/loglevel.erl new file mode 100644 index 0000000..da3e28b --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/misc/loglevel.erl @@ -0,0 +1,114 @@ +%%%---------------------------------------------------------------------- +%%% File : loglevel.erl +%%% Created : 2010-08-30 +%%%---------------------------------------------------------------------- + +-module(loglevel). + +-export([set/1, get/0]). + +-include("common.hrl"). +-include("debug.hrl"). + +%% Error levels: +-define(LOG_LEVELS, [{0, no_log, "No log"} + , {1, critical, "Critical"} + , {2, error, "Error"} + , {3, warning, "Warning"} + , {4, info, "Info"} + , {5, debug, "Debug"} + , {6, test, "Test"} +]). + +get() -> + Level = logger:get(), + case lists:keysearch(Level, 1, ?LOG_LEVELS) of + {value, Result} -> Result; + _ -> erlang:error({no_such_loglevel, Level}) + end. + +set(LogLevel) when is_atom(LogLevel) -> + set(level_to_integer(LogLevel)); +set(Loglevel) when is_integer(Loglevel) -> + try + io:format("$$$ loglevel:set, level is ~p~n", [Loglevel]), + {Mod, Code} = dynamic_compile:from_string(logger_src(Loglevel)), + code:load_binary(Mod, "logger_h.erl", Code) + catch + Type:Error -> ?CRITICAL_MSG("Error compiling logger (~p): ~p~n", [Type, Error]) + end; +set(_) -> + exit("Loglevel must be an integer"). + +level_to_integer(Level) -> + case lists:keysearch(Level, 2, ?LOG_LEVELS) of + {value, {Int, Level, _Desc}} -> Int; + _ -> erlang:error({no_such_loglevel, Level}) + end. + +%% -------------------------------------------------------------- +%% Code of the mcs logger, dynamically compiled and loaded +%% This allows to dynamically change log level while keeping a +%% very efficient code. +logger_src(Loglevel) -> + L = integer_to_list(Loglevel), + "-module(logger). + + -export([test_msg/4, + debug_msg/4, + info_msg/4, + warning_msg/4, + error_msg/4, + critical_msg/4, + get/0]). + + get() -> " ++ L ++ ". + + %% Helper functions + test_msg(Module, Line, Format, Args) when " ++ L ++ " >= 6 -> + notify(info_msg, + \"T(~p:~p:~p) : \"++Format++\"~n\", + [self(), Module, Line]++Args); + test_msg(_,_,_,_) -> ok. + + debug_msg(Module, Line, Format, Args) when " ++ L ++ " >= 5 -> + notify(info_msg, + \"D(~p:~p:~p) : \"++Format++\"~n\", + [self(), Module, Line]++Args); + debug_msg(_,_,_,_) -> ok. + + info_msg(Module, Line, Format, Args) when " ++ L ++ " >= 4 -> + notify(info_msg, + \"I(~p:~p:~p) : \"++Format++\"~n\", + [self(), Module, Line]++Args); + info_msg(_,_,_,_) -> ok. + + warning_msg(Module, Line, Format, Args) when " ++ L ++ " >= 3 -> + notify(error, + \"W(~p:~p:~p) : \"++Format++\"~n\", + [self(), Module, Line]++Args); + warning_msg(_,_,_,_) -> ok. + + error_msg(Module, Line, Format, Args) when " ++ L ++ " >= 2 -> + case Args of + %% start with : ** Node php + [42,42,32,78,111,100,101,32,112,104,112|_] -> + ok; + _ -> + notify(error, + \"E(~p:~p:~p) : \"++Format++\"~n\", + [self(), Module, Line]++Args) + end; + error_msg(_,_,_,_) -> ok. + + critical_msg(Module, Line, Format, Args) when " ++ L ++ " >= 1 -> + notify(error, + \"C(~p:~p:~p) : \"++Format++\"~n\", + [self(), Module, Line]++Args); + critical_msg(_,_,_,_) -> ok. + + %% Distribute the message to the Erlang error logger + notify(Type, Format, Args) -> + LoggerMsg = {Type, group_leader(), {self(), Format, Args}}, + gen_event:notify(logger_mgr, LoggerMsg). + ". \ No newline at end of file diff --git a/src/srvNodeMgr/tools/gameWorld/test/misc/misc.erl b/src/srvNodeMgr/tools/gameWorld/test/misc/misc.erl new file mode 100644 index 0000000..3137046 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/misc/misc.erl @@ -0,0 +1,562 @@ +%%%---------------------------------------- +%%% @Module : misc +%%% @Author : csj +%%% @Created : 2010.10.05 +%%% @Description: 常用函数 +%%%---------------------------------------- + +-module(misc). + +%% +%% Include files +%% +-include("common.hrl"). +-include("record.hrl"). +-include("goods.hrl"). +%% +%% Exported Functions +%% + +-compile(export_all). + + +%% +%% API Functions +%% +%% get the pid of a registered name +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) -> +%% case global:whereis_name(Name) of +%% Pid0 when is_pid(Pid0) -> +%% exit(Pid0,normal); +%% undefined -> +%% global:re_register_name(Name, Pid) +%% end. + global:re_register_name(Name, Pid); + +register(unique, Name, Pid) -> + global:register_name(Name, Pid). + +unregister({local, Atom}) -> + erlang:unregister(Atom); + +unregister({global, Atom}) -> + global:unregister(Atom). + +is_process_alive(Pid) -> + try + if is_pid(Pid) -> + %%现在都是单节点,无需访问节点来判断 +%% case rpc:call(node(Pid), erlang, is_process_alive, [Pid]) of +%% {badrpc, _Reason} -> false; +%% Res -> Res +%% end; + erlang:is_process_alive(Pid); + true -> false + end + catch + _:_ -> false + end. + +%% time format +one_to_two(One) -> io_lib:format("~2..0B", [One]). + +%% @doc get the time's seconds for integer type +%% @spec get_seconds(Time) -> integer() +get_seconds(Time) -> + {_MegaSecs, Secs, _MicroSecs} = Time, + Secs. + +time_format(Now) -> + {{Y, M, D}, {H, MM, S}} = calendar:now_to_local_time(Now), + lists:concat([Y, "-", one_to_two(M), "-", one_to_two(D), " ", + one_to_two(H), ":", one_to_two(MM), ":", one_to_two(S)]). +date_format(Now) -> + {{Y, M, D}, {_H, _MM, _S}} = calendar:now_to_local_time(Now), + lists:concat([Y, "-", one_to_two(M), "-", one_to_two(D)]). +date_hour_format(Now) -> + {{Y, M, D}, {H, _MM, _S}} = calendar:now_to_local_time(Now), + lists:concat([Y, "-", one_to_two(M), "-", one_to_two(D), " ", one_to_two(H)]). +date_hour_minute_format(Now) -> + {{Y, M, D}, {H, MM, _S}} = calendar:now_to_local_time(Now), + lists:concat([Y, "-", one_to_two(M), "-", one_to_two(D), " ", one_to_two(H), "-", one_to_two(MM)]). +%% split by - +minute_second_format(Now) -> + {{_Y, _M, _D}, {H, MM, _S}} = calendar:now_to_local_time(Now), + lists:concat([one_to_two(H), "-", one_to_two(MM)]). + +hour_minute_second_format(Now) -> + {{_Y, _M, _D}, {H, MM, S}} = calendar:now_to_local_time(Now), + lists:concat([one_to_two(H), ":", one_to_two(MM), ":", one_to_two(S)]). + +rand_to_process(S) -> + Rand = random:uniform(?SEND_MSG), + lists:nth(Rand, S). + + +%% 这里先修改成原子,如果原子不够用,在改回list然后注册成全局的 +player_process_name(PlayerId) when is_integer(PlayerId) or is_atom(PlayerId) -> + tool:to_atom(lists:concat([p_p_, PlayerId])); +player_process_name(PlayerId) when is_list(PlayerId) -> + tool:to_atom(lists:flatten(["p_p_" | PlayerId])); +player_process_name(PlayerId) when is_binary(PlayerId) -> + tool:to_atom(lists:concat([p_p_, tool:md5(PlayerId)])). + +%% 在一账号多个角色的情况下,需要按账号ID判断唯一性 +player_process_accountname(AccountId) when is_integer(AccountId) or is_atom(AccountId) -> + lists:concat([p_a_, AccountId]); +player_process_accountname(AccountId) when is_list(AccountId) -> + lists:flatten(["p_a_" | AccountId]); +player_process_accountname(AccountId) when is_binary(AccountId) -> + lists:concat([p_a_, tool:md5(AccountId)]). + +%% 创建进程名 +create_process_name(Prefix, List) -> + tool:to_atom(lists:concat(lists:flatten([Prefix] ++ lists:map(fun(T) -> ['_', T] end, List)))). + +%% 原子拼装 +create_atom(Prefix, List) -> + tool:to_atom(lists:concat(lists:flatten([Prefix] ++ lists:map(fun(T) -> ['_', T] end, List)))). + +%% 获取来源IP +get_ip(Socket) -> + case inet:peername(Socket) of + {ok, {PeerIP, _Port}} -> + ip_to_binary(PeerIP); + {error, _NetErr} -> + "" + end. + +ip_to_binary(Ip) -> + case Ip of + {A1, A2, A3, A4} -> + [integer_to_list(A1), ".", integer_to_list(A2), ".", integer_to_list(A3), ".", integer_to_list(A4)]; + _ -> + "-" + end. + +ip_list_to_binary(Data) -> + case Data of + [] -> ""; + undefined -> "-"; + {IP, _PORT} -> ip_to_binary(IP); + _ when is_list(Data) -> + [H | T] = Data, + [ip_list_to_binary(H), ",", ip_list_to_binary(T)]; + _ -> "-" + end. + +get_child_count(Atom) -> + case whereis_name({local, Atom}) of + undefined -> + 0; + _ -> + [_, {active, ChildCount}, _, _] = supervisor:count_children(Atom), + ChildCount + end. + +get_child_message_queue_length(Atom) -> + case whereis_name({local, Atom}) of + undefined -> + []; + _ -> + Child_list = supervisor:which_children(Atom), + lists:map( + fun({Name, Pid, _Type, [Class]}) when is_pid(Pid) -> + {message_queue_len, Qlen} = erlang:process_info(Pid, message_queue_len), + {links, Links} = erlang:process_info(Pid, links), + {Name, Pid, Qlen, Class, length(Links)} + end, + Child_list) + end. + +%% desc: 创建玩家的物品ets表编号 +create_goods_ets_name(Num) -> + case lists:member(Num, lists:seq(1, ?MAX_GOODS_ETS_NUM)) of + true -> tool:to_atom(lists:concat([ets_goods_online_, Num])); + false -> none + end. +%% desc: 创建玩家的物品ets表编号 +create_goods_attr_ets_name(Num) -> + case lists:member(Num, lists:seq(1, ?MAX_GOODS_ETS_NUM)) of + true -> tool:to_atom(lists:concat([ets_goods_attribute_, Num])); + false -> none + end. +%% desc: 创建玩家的物品ets表编号 +create_goods_polish_ets_name(Num) -> + case lists:member(Num, lists:seq(1, ?MAX_GOODS_ETS_NUM)) of + true -> tool:to_atom(lists:concat([ets_polish_, Num])); + false -> none + end. + +%% -------------------------------------------------------------------- +%% Func: get pid info/7 +%% Param Process: atom Pid or Pid RegName +%% Top: 0=all result, N=0-N record in the result +%% NeedModule fiter Pid module,[]=all +%% Layer node child layer, 0=all,1=self +%% MinMsgLen message queue length >= MinMsgLen +%% MinMemSize pid memory size >= MinMemSize +%% OrderKey, type atom and the value is: msglen,memory +%% Purpose: get pid info +%% Returns: {ok,Result,Count} Result=[{Pid,RegName,MemSize,MessageLength,Module},...] +%% {error,Reason} +%% -------------------------------------------------------------------- +get_process_info(Process, Top, NeedModule, Layer, MinMsgLen, MinMemSize, OrderKey) -> + RootPid = + if erlang:is_pid(Process) -> + Process; + true -> + case whereis_name({local, Process}) of + undefined -> + error; + ProcessPid -> + ProcessPid + end + end, + case RootPid of + error -> + {error, lists:concat([Process, " is not process reg name in the ", node()])}; + _ -> + AllPidList = get_process_all_pid(RootPid, Layer), + RsList = get_process_info_detail(NeedModule, AllPidList, []), + Len = erlang:length(RsList), + FilterRsList = + case OrderKey of + msglen -> + lists:filter(fun({_, _, _, Qlen, _}) -> Qlen >= MinMsgLen end, RsList); + memory -> + lists:filter(fun({_, _, Qmem, _, _}) -> Qmem >= MinMemSize end, RsList); + _ -> + lists:filter(fun({_, _, _, Qlen, _}) -> Qlen >= MinMsgLen end, RsList) + end, + RsList2 = + case OrderKey of + msglen -> + lists:sort(fun({_, _, _, MsgLen1, _}, {_, _, _, MsgLen2, _}) -> MsgLen1 > MsgLen2 end, FilterRsList); + memory -> + lists:sort(fun({_, _, MemSize1, _, _}, {_, _, MemSize2, _, _}) -> + MemSize1 > MemSize2 end, FilterRsList); + _ -> + lists:sort(fun({_, _, _, MsgLen1, _}, {_, _, _, MsgLen2, _}) -> MsgLen1 > MsgLen2 end, FilterRsList) + end, + NewRsList = + if Top =:= 0 -> + RsList2; + true -> + if erlang:length(RsList2) > Top -> + lists:sublist(RsList2, Top); + true -> + RsList2 + end + end, + {ok, NewRsList, Len} + end. + +%% -------------------------------------------------------------------- +%% Func: get_process_info_detail/3 +%% Purpose: get pid detail info +%% Returns: [{Pid,RegName,MemSize,MessageLength,Module},...] +%% -------------------------------------------------------------------- +get_process_info_detail(_NeedModule, [], Result) -> Result; +get_process_info_detail(NeedModule, [H | T], Result) -> + Mql = get_process_data({message_queue_len, H}), + MemSize = get_process_data({memory, H}), + RegName = get_process_data({registered_name, H}), + case NeedModule of + [] -> + Module = get_process_info_initial_call(H), +%% io:format("~p process RegName:~p,Mql:~p,MemSize:~p,Module:~p\n",[H, RegName, Mql, MemSize, Module]), + get_process_info_detail(NeedModule, T, lists:append(Result, [{H, RegName, MemSize, Mql, Module}])); + _ -> + case get_process_info_check_initial_call(NeedModule, H) of + "" -> + get_process_info_detail(NeedModule, T, Result); + Module -> +%% io:format("~p process RegName:~p,Mql:~p,MemSize:~p\n",[H, RegName, Mql, MemSize]), + get_process_info_detail(NeedModule, T, lists:append(Result, [{H, RegName, MemSize, Mql, Module}])) + + end + end. + +%% -------------------------------------------------------------------- +%% Func: get_process_info_check_initial_call/2 +%% Purpose: check inital call +%% Returns: true or false +%% -------------------------------------------------------------------- +get_process_info_check_initial_call(NeedModule, Pid) -> + DictionaryList = get_process_data({dictionary, Pid}), +%% io:format("Dictionary List:~p\n",[DictionaryList]), + case proplists:lookup('$initial_call', DictionaryList) of + {'$initial_call', {Module, _, _}} -> +%% io:format("~p found initial_call Module=~p\n",[Pid,Module]), + case lists:member(Module, NeedModule) of + true -> + Module; + _ -> + "" + end; + _ -> + "" + end. +%% -------------------------------------------------------------------- +%% Func: get_process_info_initial_call/1 +%% Purpose: get initial call +%% Returns: true or false +%% -------------------------------------------------------------------- +get_process_info_initial_call(Pid) -> + DictionaryList = get_process_data({dictionary, Pid}), +%% io:format("Dictionary List:~p\n",[DictionaryList]), + case proplists:lookup('$initial_call', DictionaryList) of + {'$initial_call', {Module, _, _}} -> + Module; + _ -> + "" + end. +%% -------------------------------------------------------------------- +%% Func: get_process_all_pid/1 +%% Purpose: get pid and child pid, Layer 0 all 1 fisrt +%% Returns: [Pid,...] +%% -------------------------------------------------------------------- +get_process_all_pid(RootPid, Layer) -> + ParentPid = get_process_parent_pid(RootPid), + RootLinkPidList = get_process_data({links, RootPid}), +%% io:format("~p links process links~p,and parent pid is~p\n",[RootPid, RootLinkPidList, ParentPid]), + case RootLinkPidList of + [] -> + [RootPid]; + _ -> + if erlang:length(RootLinkPidList) =:= 1 -> + [RootPid]; + true -> + NewLinkPidList = + if erlang:is_pid(ParentPid) -> + lists:delete(ParentPid, RootLinkPidList); + true -> + RootLinkPidList + end, + LinkPidList = lists:delete(RootPid, NewLinkPidList), + +%% io:format("~p do handle links process links~p\n",[RootPid,LinkPidList]), + if Layer =:= 1 -> + [RootPid]; + true -> + get_process_all_pid(LinkPidList, Layer, [RootPid], 2) + end + end + end. + +get_process_all_pid([], _Layer, ResultList, _Index) -> ResultList; +get_process_all_pid([H | T], Layer, ResultList, Index) -> +%% io:format("get process all pid Index=~p", [Index]), + if erlang:is_pid(H) -> + ParentPid = get_process_parent_pid(H), + RootLinkPidList = get_process_data({links, H}), +%% io:format("~p links process links~p,and parent pid is~p\n",[H, RootLinkPidList, ParentPid]), + case RootLinkPidList of + [] -> + get_process_all_pid(T, Layer, lists:append(ResultList, [H]), Index); + _ -> + if erlang:length(RootLinkPidList) =:= 1 -> + get_process_all_pid(T, Layer, lists:append(ResultList, [H]), Index); + true -> + NewLinkPidList = + if erlang:is_pid(ParentPid) -> + lists:delete(ParentPid, RootLinkPidList); + true -> + RootLinkPidList + end, + LinkPidList = lists:delete(H, NewLinkPidList), + NewIndex = Index + 1, + SubResultList = + if NewIndex > Layer, Layer =/= 0 -> + [H]; + true -> + get_process_all_pid(LinkPidList, Layer, [H], NewIndex) + end, + get_process_all_pid(T, Layer, lists:append(ResultList, SubResultList), Index) + end + end; + true -> + get_process_all_pid(T, Layer, ResultList, Index) + end. + +%% -------------------------------------------------------------------- +%% Func: get_process_parent_pid/1 +%% Purpose: get the pid parent pid +%% Returns: Pid or ignore +%% -------------------------------------------------------------------- +get_process_parent_pid(Pid) -> + DictionaryList = get_process_data({dictionary, Pid}), +%% io:format("Dictionary List:~p\n",[DictionaryList]), + case proplists:lookup('$ancestors', DictionaryList) of + {'$ancestors', [ParentPid | _]} -> +%% io:format("~p found parent pid is ~p\n",[Pid,ParentPid]), + if erlang:is_pid(ParentPid) -> + ParentPid; + true -> + whereis_name({local, ParentPid}) + end; + _ -> + ignore + end. +%% -------------------------------------------------------------------- +%% Func: get_process_data/1 +%% Purpose: get the dictionary info of the process +%% Returns: [] or DictionaryList +%% -------------------------------------------------------------------- +get_process_data({dictionary, Pid}) -> + try erlang:process_info(Pid, dictionary) of + {_, DList} -> DList; + _ -> [] + catch + _:_ -> [] + end; +%% -------------------------------------------------------------------- +%% Func: get_process_data/1 +%% Purpose: get the links info of the process +%% Returns: [] or LinksList +%% -------------------------------------------------------------------- +get_process_data({links, Pid}) -> + try erlang:process_info(Pid, links) of + {_, Links} -> lists:filter(fun(I) -> erlang:is_pid(I) end, Links); + _ -> [] + catch + _:_ -> [] + end; +%% -------------------------------------------------------------------- +%% Func: get_process_data/1 +%% Purpose: get the message queue length info of the process +%% Returns: 0 or Length +%% -------------------------------------------------------------------- +get_process_data({message_queue_len, Pid}) -> + try erlang:process_info(Pid, message_queue_len) of + {message_queue_len, Length} -> Length; + _ -> 0 + catch + _:_ -> 0 + end; +%% -------------------------------------------------------------------- +%% Func: get_process_data/1 +%% Purpose: get the memory size info of the process +%% Returns: 0 or MemorySize +%% -------------------------------------------------------------------- +get_process_data({memory, Pid}) -> + try erlang:process_info(Pid, memory) of + {memory, Size} -> Size; + _ -> 0 + catch + _:_ -> 0 + end; +%% -------------------------------------------------------------------- +%% Func: get_process_data/1 +%% Purpose: get the registered name info of the process +%% Returns: "" or RegisteredName +%% -------------------------------------------------------------------- +get_process_data({registered_name, Pid}) -> + try erlang:process_info(Pid, registered_name) of + {registered_name, RegName} -> RegName; + _ -> "" + catch + _:_ -> "" + end. +%% -------------------------------------------------------------------- +%% Func: get_online_role_count/1 +%% Purpose: get online role count +%% Returns: {0,0} or {SpecsCount,ActiveCount} +%% -------------------------------------------------------------------- +%% get_online_role_count(AccountList) -> +%% case AccountList of +%% AccAtom when is_atom(AccAtom) -> +%% get_online_role_count([AccAtom], 0, 0); +%% AccList when is_list(AccList) -> +%% get_online_role_count(AccList, 0, 0) +%% end. +%% +%% get_online_role_count([], Count, ActiveCount) ->{Count,ActiveCount}; +%% get_online_role_count(Ports, Count, ActiveCount) -> +%% [AccountSupName|T] = Ports, +%% {SNum,ANum} = get_online_role_count_item(AccountSupName), +%% get_online_role_count(T, Count + SNum, ActiveCount + ANum). +%% +%% get_online_role_count_item(SupName) -> +%% try supervisor:count_children(SupName) of +%% [] -> {0,0}; +%% SupList when erlang:is_list(SupList) -> +%% %% io:format("SupList ~p ~n", [SupList]), +%% SCount = +%% case proplists:lookup(specs, SupList) of +%% {specs, SNum} -> SNum; +%% _ -> 0 +%% end, +%% ACount = +%% case proplists:lookup(active, SupList) of +%% {active, ANum} -> ANum; +%% _ -> 0 +%% end, +%% {SCount,ACount}; +%% _ -> {0,0} +%% catch +%% _:_ -> {0,0} +%% end. + +%% -------------------------------------------------------------------- +%% Func: replace_all/4 +%% Purpose: Subject,RE,Replacement,Options +%% Returns: List +%% -------------------------------------------------------------------- +replace_all(Subject, RE, Replacement, Options) -> + ReSubject = re:replace(Subject, RE, Replacement, Options), + case ReSubject =:= Subject of + false -> + replace_all(ReSubject, RE, Replacement, Options); + _ -> + ReSubject + end. + +pg2_get_members(Pg2_name) -> + L = case pg2:get_members(Pg2_name) of + {error, _} -> + timer:sleep(100), + pg2:get_members(Pg2_name); + Other when is_list(Other) -> + Other + end, + if not is_list(L) -> []; + true -> lists:usort(L) + end. + +get_http_content(Url) -> + case httpc:request(Url) of + {ok, {_Status, _Headers, Raw}} -> + Raw; + {error, _Reason} -> + "" + end. + +cancel_timer(Timer) -> + case get(Timer) of + undefined -> skip; + Timer1 -> + erlang:cancel_timer(Timer1) + end. + +write_system_info(Pid, Module, Args) -> + ets:insert(?ETS_SYSTEM_INFO, {Pid, Module, Args}). + +delete_system_info(Pid) -> + ets:delete(?ETS_SYSTEM_INFO, Pid). + +write_monitor_pid(Pid, Module, Args) -> + ets:insert(?ETS_MONITOR_PID, {Pid, Module, Args}). + +delete_monitor_pid(Pid) -> + ets:delete(?ETS_MONITOR_PID, Pid). diff --git a/src/srvNodeMgr/tools/gameWorld/test/misc/misc_admin.erl b/src/srvNodeMgr/tools/gameWorld/test/misc/misc_admin.erl new file mode 100644 index 0000000..5f811be --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/misc/misc_admin.erl @@ -0,0 +1,491 @@ +%%%---------------------------------------- +%%% @Module : misc_admin +%%% @Author : +%%% @Created : +%%% @Description: 系统状态管理和查询 +%%%---------------------------------------- +-module(misc_admin). +%% +%% Include files +%% +-include("common.hrl"). +-include("record.hrl"). +-include("debug.hrl"). +-include("goods.hrl"). + +%%返回码 +-define(PARAM_ERROR_CODE, <<"param_error">>). % 参数错误 +-define(FLAG_ERROR_CODE, <<"flag_error">>). % 验证失败 +-define(FAILED_CODE, <<"failed">>). % 发送消息失败,服务器异常 +-define(SUCCESS_CODE, <<"success">>). % 成功 + +-compile(export_all). + +%% 处理http请求【需加入身份验证或IP验证】 +treat_http_request(Socket, PacketStr) -> + case gen_tcp:recv(Socket, 0, ?RECV_TIMEOUT) of + {ok, Packet} -> + try + P = lists:concat([PacketStr, tool:to_list(Packet)]), + io:format("PacketStr ~p ~n", [http_util:get_cmd_parm(P)]), +%% ?INFO_MSG("Packet:~p ~n", [P]), + {Cmd, KvList, Md5Key} = http_util:get_cmd_parm(P), + Md5Str = string:to_upper(tool:md5(Md5Key)), + io:format("md5 ~p ~n", [Md5Str]), +%% ?INFO_MSG("Cmd:~p ~n ~p ~n ~p ~n ~p ~n ~n", [Cmd, KvList, Md5Key, Md5Str]), + case Md5Str =:= http_util:get_param("flag", KvList) of + false -> do_handle_request(Cmd, KvList, Socket); + true -> gen_tcp:send(Socket, ?FLAG_ERROR_CODE) + end + catch + What:Why -> + ?ERROR_MSG("What ~p, Why ~p, ~p", [What, Why, erlang:get_stacktrace()]), + gen_tcp:send(Socket, ?FAILED_CODE) + end; + {error, Reason} -> + ?ERROR_MSG("http_request error Reason:~p ~n", [Reason]) + end. + +%% 消息广播 +do_handle_request("send_sys_bulletin", KvList, Socket) -> + MsgType = list_to_integer(http_util:get_param("msg_type", KvList)), + Content = http_util:get_param("content", KvList), + cast_to_server(lib_chat, broadcast_sys_msg, [MsgType, Content]), +%% lib_chat:broadcast_sys_msg("test"), + ?INFO_MSG("type:~p content:~ts ~n", [MsgType, Content]), + + gen_tcp:send(Socket, <<"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nhello world!">>); +% gen_tcp:send(Socket, ?SUCCESS_CODE); + +%% 发送邮件 +do_handle_request("send_mail", KvList, Socket) -> + ok; +%% Action = http_util:get_param("action", KvList), +%% UserNames = http_util:get_param("user_names", KvList), +%% UserIds = http_util:get_param("user_ids", KvList), +%% MailTitle = http_util:get_param("mail_title", KvList), +%% MailConten = http_util:get_param("mail_content", KvList), +%% UserNameList = string:tokens(UserNames, ","), +%% UserIdList = string:tokens(UserIds, ","), +%% if +%% Action =:= 1 -> % 只对参数 user_names 和 user_ids 指定的用户发送 +%% cast_to_server(lib_mail, broadcast_sys_msg, [Action, UserNameList, UserIdList, MailTitle, MailConten]); +%% Action =:= 2 -> % 只对符合"条件参数"的所有用户发送,“条件参数”包括下列条件 +%% cast_to_server(lib_mail, broadcast_sys_msg, [MsgType, Content]); +%% Action =:= 3 -> % 只对当前在线玩家发送 +%% cast_to_server(lib_mail, broadcast_sys_msg, [Action, MailTitle, MailConten]); +%% true -> +%% ok +%% end, +%% ?INFO_MSG("Action:~p title:~ts content:~ts ~n", [Action, MailTitle, MailConten]), +%% gen_tcp:send(Socket, ?SUCCESS_CODE); + +%% GM回复玩家接口 +do_handle_request("complain_reply", KvList, Socket) -> + UserName = http_util:get_param("user_name", KvList), + Conten = http_util:get_param("content", KvList), + CompainId = http_util:get_param("compain_id", KvList), + cast_to_server(lib_mail, broadcast_sys_msg, [CompainId, UserName, Conten]), + ?INFO_MSG("CompainId:~p UserName::~ts content:~ts ~n", [CompainId, UserName, Conten]), + gen_tcp:send(Socket, ?SUCCESS_CODE); + +%% 封禁/解封 账号 +do_handle_request("forbid_login", KvList, Socket) -> + UserNames = http_util:get_param("user_names", KvList), + IsForbid = http_util:get_param("is_forbid", KvList), + ForbidTime = + case http_util:get_param("forbid_time", KvList) of + [] -> 0; + ForbidTime1 -> list_to_integer(ForbidTime1) + end, + Reason = http_util:get_param("reason", KvList), + UserNameList = string:tokens(UserNames, ","), + cast_to_server(lib_admin, ban_role, [UserNameList, list_to_integer(IsForbid), ForbidTime, Reason]), + ?INFO_MSG("IsForbid:~p ~n ForbidTime:~p ~n UserName:~ts ~n list:~ts ~n Reason:~ts ~n", + [IsForbid, ForbidTime, UserNames, UserNameList, Reason]), + gen_tcp:send(Socket, ?SUCCESS_CODE); + +%% 封禁/解禁 IP +do_handle_request("ip_ban", KvList, Socket) -> + IP = http_util:get_param("ip", KvList), + IsForbid = http_util:get_param("is_forbid", KvList), + ForbidTime = + case http_util:get_param("forbid_time", KvList) of + [] -> 0; + ForbidTime1 -> list_to_integer(ForbidTime1) + end, + Reason = http_util:get_param("reason", KvList), + IpList = string:tokens(IP, ","), + cast_to_server(lib_admin, ban_ip, [IpList, list_to_integer(IsForbid), ForbidTime, Reason]), + ?INFO_MSG("IsForbid:~p ForbidTime:~p IP:~ts Reason:~ts ~n", [IsForbid, ForbidTime, IP, Reason]), + gen_tcp:send(Socket, ?SUCCESS_CODE); + +%% 踢人接口 +do_handle_request("kick_user", KvList, Socket) -> + UserNames = http_util:get_param("user_names", KvList), + KillFlag = list_to_integer(http_util:get_param("kick_all", KvList)), + Reason = http_util:get_param("reason", KvList), + if + KillFlag =:= 0 -> % 存在多个以逗号分隔 + UserNameList = string:tokens(UserNames, ","), + cast_to_server(lib_admin, kick_user, [UserNameList, Reason]), + gen_tcp:send(Socket, ?SUCCESS_CODE); + KillFlag =:= 1 -> % 踢出所有玩家 + cast_to_server(lib_admin, kick_all_user, []), + gen_tcp:send(Socket, ?SUCCESS_CODE); + true -> + gen_tcp:send(Socket, ?PARAM_ERROR_CODE) + end, + ?INFO_MSG("KillFlag:~p Reason:~ts UserNames:~ts ~n", [KillFlag, Reason]); + +%% 禁言 / 解禁 +do_handle_request("ban_chat", KvList, Socket) -> + UserNames = http_util:get_param("user_names", KvList), + BanFlag = list_to_integer(http_util:get_param("is_ban", KvList)), + BanTime1 = http_util:get_param("ban_date", KvList), % 0=永久禁言,否则以此作为时间戳,代表封号结束时间 + BanTime = + case BanTime1 =:= [] of + true -> 0; + false -> list_to_integer(BanTime1) + end, + Reason = http_util:get_param("reason", KvList), + UserNameList = string:tokens(UserNames, ","), + cast_to_server(lib_admin, donttalk, [UserNameList, BanFlag, BanTime, Reason]), %1=禁言; 0=解禁 + ?INFO_MSG("BanFlag:~p BanTime:~p Reason:~ts UserNames:~ts ~n", [BanFlag, BanTime, Reason, UserNames]), + gen_tcp:send(Socket, ?SUCCESS_CODE); + +%% 新手指导员接口 +do_handle_request("game_instructor_manage", KvList, Socket) -> + UserName = http_util:get_param("user_name", KvList), + Type = http_util:get_param("type", KvList), %1=禁言 0=解禁 + InstructorType = http_util:get_param("instructor_type", KvList), %1.菜鸟指导员 2.指导员达人3.新手导师4.长期指导员5.GM + StartTime = http_util:get_param("start_time", KvList), + EndTime = http_util:get_param("end_time", KvList), +%% cast_to_server(lib_mail, broadcast_sys_msg, [UserName, Type, InstructorType, StartTime, EndTime]), + ?INFO_MSG("Type:~p InstructorType:~p StartTime:~p EndTime:~p UserName:~ts ~n", [Type, InstructorType, StartTime, EndTime, UserName]), + gen_tcp:send(Socket, ?SUCCESS_CODE); + +%% 重置玩家位置 +do_handle_request("reset_user_pos", KvList, Socket) -> + UserName = http_util:get_param("user_name", KvList), +%% cast_to_server(lib_mail, broadcast_sys_msg, UserName), + ?INFO_MSG("content:~ts ~n", [UserName]), + gen_tcp:send(Socket, ?SUCCESS_CODE); + +%% 查询满足条件的玩家数量 +do_handle_request("count_user", KvList, Socket) -> + ok; + +%% 单个玩家详细信息接口 +do_handle_request("user_info_detail", KvList, Socket) -> + UserId = http_util:get_param("user_id", KvList), + UserName = http_util:get_param("user_name", KvList), + Account = http_util:get_param("account", KvList), + % 三个参数并不是互斥的关系,有可以三个都传,有可能只传两个。它们之间在SQL语句里面是and的关系 +%% case call_to_server(lib_admin, get_player_info, [UserId, UserName, Account]) of +%% {badrpc, _} -> +%% gen_tcp:send(Socket, ?FAILED_CODE); +%% Player -> +%% RetMsg = <<>>, +%% gen_tcp:send(Socket, ?SUCCESS_CODE) +%% end, + ?INFO_MSG("UserId:~p UserName:~ts Account:~ts ~n", [UserId, UserName, Account]); + + +% desc {"字段名":"字段中文含义"} 如:{"user_name":"角色名称",user_id":"角色ID"} +% data data 中必须包含以下基本信息: +% 字段 含义 +% account 玩家平台账号 +% user_id 玩家ID +% user_name 玩家角色名 +% reg_time 角色创建时间 +% level 玩家等级 +% last_login_ip 玩家最后登陆IP +% last_login_time 玩家最后登陆时间 +% country 玩家阵营名称(若玩家没有阵营,则返回-1) +% guild 玩家帮派名称(若玩家没有帮派,则返回-1) +% career 玩家职业名称(若玩家没有职业,则返回-1) +% 自定义信息(下面标红部分),游戏方可自行添加。 自定义信息与基本信息返回方式一致: +% +% { +% "account":"gfsyra", +% "user_id":"221", +% "user_name":"高富帅有人爱", +% "reg_time":"1371928400", +% "level":"22", +% "reg_time":"22", +% "last_login_ip":"94.123.22.123", +% "last_login_time":"1388928400", +% "country":"魏国", +% "guild":"超级兵马俑", +% "career":"魏将", +% "is_vip":"y", +% "sex":"1", +% ...... +% + +%% 玩家信息列表 +do_handle_request("user_info_list", KvList, Socket) -> + ok; + +%% 单个帮派详细信息接口 +do_handle_request("guild_info_detail", KvList, Socket) -> + GuildId = http_util:get_param("guild_id", KvList), + GuildName = http_util:get_param("guild_name", KvList), +%% call_to_server(lib_mail, broadcast_sys_msg, [GuildId, GuildName]), + ?INFO_MSG("type:~p content:~ts ~n", [GuildId, GuildName]), + gen_tcp:send(Socket, ?SUCCESS_CODE); + +% data 中必须包含以下基本信息: +% 字段 含义 +% guild_id 帮派ID +% guild_name 帮派名称 +% guild_level 帮派等级 +% guild_ranking 帮派排名 +% leader 帮忙创建者 +% create_time 创建时间 +% member_count 帮派人数 +% member_list 玩家列表,json数组格式 +% 自定义信息(下面标红部分),游戏方可自行添加。 自定义信息与基本信息返回方式一致: +% +% { +% "guild_id":"1234", +% "guild_name":"高富帅帮", +% "guild_level":"2", +% "guild_ranking":"1", +% "member_count":"3", +% "member_list":[ +% "高", "富","帅","高富帅有人爱" +% ], +% "leader":"高富帅有人爱", +% "create_time":"1388928400", +% "is_vip":"y", +% ...... +% } + +%% 玩家帮派信息列表 +do_handle_request("guild_info_list", KvList, Socket) -> + GuildId = http_util:get_param("guild_id", KvList), + GuildName = http_util:get_param("guild_name", KvList), +%% call_to_server(lib_mail, broadcast_sys_msg, [GuildId, GuildName]), + ?INFO_MSG("type:~p content:~ts ~n", [GuildId, GuildName]), + gen_tcp:send(Socket, ?SUCCESS_CODE); + +%% 刷新在线玩家信息 +do_handle_request("freshen_online_user", KvList, Socket) -> +%% cast_to_server(lib_mail, broadcast_sys_msg, []), + ?INFO_MSG("freshen_online_user ~p ~n", []), + gen_tcp:send(Socket, ?SUCCESS_CODE); + +%% 道具、货币发送接口 +do_handle_request("admin_send_gift", KvList, Socket) -> + Action = list_to_integer(http_util:get_param("action", KvList)), + UserNames = http_util:get_param("user_names", KvList), + UserIds = http_util:get_param("user_ids", KvList), + MinLvStr = http_util:get_param("min_lv", KvList), + MinLv = + case MinLvStr =:= [] of + true -> 0; + false -> list_to_integer(MinLvStr) + end, + MaxLvStr = http_util:get_param("max_lv", KvList), + MaxLv = + case MaxLvStr =:= [] of + true -> 0; + false -> list_to_integer(MaxLvStr) + end, +%% min_login_time = http_util:get_param("min_login_time", KvList), +%% max_login_time = http_util:get_param("max_login_time", KvList), +%% min_reg_time = http_util:get_param("min_reg_time", KvList), +%% max_reg_time = http_util:get_param("max_reg_time", KvList), +%% sex = http_util:get_param("sex", KvList), +%% career = http_util:get_param("career", KvList), +%% guild = http_util:get_param("guild", KvList), + MailTitle = http_util:get_param("mail_title", KvList), + MailConten = http_util:get_param("mail_content", KvList), + MoneyAmounts = http_util:get_param("money_amounts", KvList), % 发放货币数量,同时发放多个币种时,使用逗号分隔,与money_type币种对应 + MoneyTypes = http_util:get_param("money_types", KvList), % 1=元宝,2=绑定元宝,3=铜币,4=绑定铜币, 5=礼券 + ItemIds = http_util:get_param("item_ids", KvList), % 发放道具id,同时发放多个道具,使用逗号分隔id + ItemTypes = http_util:get_param("item_types", KvList), % 道具绑定类型:1=绑定, 0= 非绑定, + ItemCounts = http_util:get_param("item_counts", KvList), % 道具数量:发放多个道具时,以逗号分隔,并与item_ids顺序对应 +%% item_levels = http_util:get_param("item_levels", KvList), + + UserNameList = string:tokens(UserNames, ","), + UserIdList = [list_to_integer(Uid) || Uid <- string:tokens(UserIds, ",")], + MoneyAmountList = string:tokens(MoneyAmounts, ","), + MoneyTypeList = string:tokens(MoneyTypes, ","), + ItemIdList = string:tokens(ItemIds, ","), +%% ItemTypeList = string:tokens(ItemTypes, ","), + ItemCountList = string:tokens(ItemCounts, ","), + ?TRACE("MoneyAmountList:~p ~n MoneyTypeList:~p ~n ItemIdList:~p ~n ItemCountList:~p ~n", [MoneyAmountList, MoneyTypeList, ItemIdList, ItemCountList]), + {Res, GoodsList} = get_gooods_list(MoneyAmountList, MoneyTypeList, ItemIdList, ItemCountList), + ?TRACE("Res:~p List:~p ~n", [Res, GoodsList]), + case Res =:= fail of + true -> gen_tcp:send(Socket, ?PARAM_ERROR_CODE); + false -> + if + Action =:= 0 -> % 针对服中全部角色发送 + cast_to_server(lib_mail, send_mail, [all, GoodsList]); + Action =:= 1 -> % 只对参数 user_names 和 user_ids 指定的用户发送 + case length(UserNameList) > 0 of + true -> + cast_to_server(lib_mail, send_goods_money_mail, [UserNameList, GoodsList]); + false -> + skip + end, + case length(UserIdList) > 0 of + true -> + cast_to_server(lib_mail, send_goods_money_mail, [UserIdList, GoodsList]); + false -> + skip + end; + Action =:= 2 -> % 只对符合"条件参数"的所有用户发送,“条件参数”包括下列条件 + cast_to_server(lib_mail, send_mail, [all, MinLv, MaxLv, GoodsList]); + Action =:= 3 -> % 只对当前在线玩家发送 + cast_to_server(lib_mail, send_mail, [online, GoodsList]); + true -> + ok + end, + ?INFO_MSG("Action:~p title:~ts content:~ts ~n", [Action, MailTitle, MailConten]), + gen_tcp:send(Socket, ?SUCCESS_CODE) + end; + +%% 玩家道具查询接口 +do_handle_request("user_props_list", KvList, Socket) -> + UserId = http_util:get_param("user_id", KvList), + UserName = http_util:get_param("user_name", KvList), + Account = http_util:get_param("account", KvList), +%% call_to_server(lib_mail, broadcast_sys_msg, [UserId, UserName, Account]), + ?INFO_MSG("UserId:~p UserName:~ts Account:~ts ~n", [UserId, UserName, Account]), + gen_tcp:send(Socket, ?SUCCESS_CODE); + +%% 玩家坐骑查询接口 +do_handle_request("user_horse_list", KvList, Socket) -> + UserId = http_util:get_param("user_id", KvList), + UserName = http_util:get_param("user_name", KvList), + Account = http_util:get_param("account", KvList), +%% call_to_server(lib_mail, broadcast_sys_msg, [UserId, UserName, Account]), + ?INFO_MSG("UserId:~p UserName:~ts Account:~ts ~n", [UserId, UserName, Account]), + gen_tcp:send(Socket, ?SUCCESS_CODE); + +%% 玩家宠物查询接口 +do_handle_request("user_pet_list", KvList, Socket) -> + UserId = http_util:get_param("user_id", KvList), + UserName = http_util:get_param("user_name", KvList), + Account = http_util:get_param("account", KvList), +%% call_to_server(lib_mail, broadcast_sys_msg, [UserId, UserName, Account]), + ?INFO_MSG("UserId:~p UserName:~ts Account:~ts ~n", [UserId, UserName, Account]), + gen_tcp:send(Socket, ?SUCCESS_CODE); + +%% 玩家技能查询接口 +do_handle_request("user_skill_list", KvList, Socket) -> + UserId = http_util:get_param("user_id", KvList), + UserName = http_util:get_param("user_name", KvList), + Account = http_util:get_param("account", KvList), +%% call_to_server(lib_mail, broadcast_sys_msg, [UserId, UserName, Account]), + ?INFO_MSG("UserId:~p UserName:~ts Account:~ts ~n", [UserId, UserName, Account]), + gen_tcp:send(Socket, ?SUCCESS_CODE); + + +do_handle_request("broad", KvList, Socket) -> + AnnId = http_util:get_param("annid", KvList), + cast_to_server(mod_misc, load_sys_announce, [AnnId, annid]), +%% lib_chat:broadcast_sys_msg("test"), + ?INFO_MSG("type:~p content ~n", [AnnId]), + gen_tcp:send(Socket, ?SUCCESS_CODE); + +%% 充值 +do_handle_request("charge", KvList, Socket) -> + AccountId = list_to_integer(http_util:get_param("account_id", KvList)), + OrderId = list_to_integer(http_util:get_param("order_id", KvList)), + cast_to_server(lib_admin, handle_charge, [AccountId, OrderId]), + ?INFO_MSG("Account:~p OrderId:~p ~n", [AccountId, OrderId]), + gen_tcp:send(Socket, ?SUCCESS_CODE); + +do_handle_request(Other, Kvlist, Socket) -> + ?INFO_MSG("admin unknown cmd ~p, ~p", [Other, Kvlist]), + gen_tcp:send(Socket, ?PARAM_ERROR_CODE). + +%% ===================针对玩家的各类操作===================================== +cast_to_server(Module, Method, Args) -> + GameSvrNode = config:get_server_node(local_gateway), + rpc:cast(GameSvrNode, Module, Method, Args). + +call_to_server(Module, Method, Args) -> + GameSvrNode = config:get_server_node(local_gateway), + rpc:call(GameSvrNode, Module, Method, Args). + +get_gooods_list(MoneyAmountList, MoneyTypeList, ItemIdList, ItemCountList) -> + if + length(MoneyAmountList) /= length(MoneyTypeList) -> + {fail, []}; + length(ItemIdList) /= length(ItemCountList) -> + {fail, []}; + true -> + F = fun(Type, {List, Seq, MList}) -> + MoneyType = list_to_integer(Type), + if + MoneyType =:= 1 -> {[{?MONEY_GOLD_T_ID, list_to_integer(lists:nth(Seq, MList))} | List], Seq + 1, MList}; + MoneyType =:= 2 -> + {[{?MONEY_BGOLD_T_ID, list_to_integer(lists:nth(Seq, MList))} | List], Seq + 1, MList}; + MoneyType =:= 3 -> {[{?MONEY_COIN_T_ID, list_to_integer(lists:nth(Seq, MList))} | List], Seq + 1, MList}; + MoneyType =:= 4 -> + {[{?MONEY_BCOIN_T_ID, list_to_integer(lists:nth(Seq, MList))} | List], Seq + 1, MList}; + true -> {List, Seq, MList} + end + end, + {GoodsList, _Seq, _L} = lists:foldl(F, {[], 1, MoneyAmountList}, MoneyTypeList), + + F1 = fun(Gtid, {List1, Seq1, CountList}) -> + {[{list_to_integer(Gtid), list_to_integer(lists:nth(Seq1, CountList))} | List1], Seq1 + 1, CountList} + end, + {GoodsList1, _Seq1, _L1} = lists:foldl(F1, {[], 1, ItemCountList}, ItemIdList), + {ok, GoodsList ++ GoodsList1} + end. + +%% 安全退出当前游戏服务器(在游戏服务器节点执行) +safe_quit() -> + timer:sleep(10 * 1000), + mod_guild:safe_quit(), + main:server_stop(), + ok. + +stop_server_node([NodeName]) -> + rpc:cast(NodeName, main, server_stop, []). + + +stop_local_gateway_node([NodeName]) -> + rpc:cast(NodeName, main, local_gateway_stop, []). + +stop_game_node([NodeName]) -> + rpc:cast(NodeName, main, gateway_stop, []). + +%% 清数据接口,非请勿用,出问题了别找我 +clear_data() -> + AllTables = db_esql:get_all("show tables"), + Fun = fun(Item) -> + [TName | _] = Item, + TableName = util:bitstring_to_term(TName), + ListTableName = atom_to_list(TableName), + case lists:sublist(ListTableName, 1, 5) =:= "temp_" orelse + lists:sublist(ListTableName, 1, 7) =:= "config_" of + true -> + skip; + false -> + TruncatSql = lists:concat(["truncate table ", TableName]), + db_esql:execute_sql(TruncatSql), + io:format("==truncated table:~p~n", [TableName]) + end + end, + lists:foreach(Fun, AllTables), + timer:sleep(1000), + erlang:halt(). + +%% Sql = lists:concat(["show tables"]), +%% case db_esql:get_row(Sql) of +%% {db_error, _} -> +%% error; +%% [_, A|_]-> +%% CreateTableList = re:split(A,"[\n]",[{return, binary}]), +%% search_auto_increment(CreateTableList) +%% end. diff --git a/src/srvNodeMgr/tools/gameWorld/test/misc/php_parser.erl b/src/srvNodeMgr/tools/gameWorld/test/misc/php_parser.erl new file mode 100644 index 0000000..e9a7c94 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/misc/php_parser.erl @@ -0,0 +1,132 @@ +%% Author: Richard Jones +%% Modified: bisonwu +%% Description: Takes a serialized php object and turns it into an erlang data structure + +-module(php_parser). + +%% +%% Include files +%% + +%% +%% Exported Functions +%% +-export([unserialize/1]). +-export([serialize_map/1, serialize_map/2]). + +%% +%% API Functions +%% + +%% @spec serialize_map/1 +%% @doc serialize for key-value list +serialize_map(KeyValueList) when is_list(KeyValueList) -> + serialize_map(KeyValueList, {"i", "d"}). + +%% @spec serialize_map/2 +%% @doc serialize for key-value list +%% result eg:"a:2:{i:7;d:1277086515;i:8;d:1277086522;}" +serialize_map(KeyValueList, {KeyType, ValType}) when is_list(KeyValueList) -> + %% eg:[{7,1277086515},{8,1277086522}] + + Len = length(KeyValueList), + Items = lists:foldr(fun(X, Items) -> + {Key, Val} = X, + lists:concat([KeyType, ":", Key, ";", ValType, ":", Val, ";", Items]) + end, "", KeyValueList), + lists:concat(["a:", Len, ":{", Items, "}"]). + + +%% @spec unserialize/1 +%% Usage: {Result, Leftover} = php_parser:unserialize(…) +unserialize(S) when is_binary(S) -> unserialize(binary_to_list(S)); +unserialize(S) when is_list(S) -> takeval(S, 1). + +% Internal stuff + +takeval(Str, Num) -> + {Parsed, Remains} = takeval(Str, Num, []), + {lists:reverse(Parsed), Remains}. + +takeval([$} | Leftover], 0, Acc) -> {Acc, Leftover}; +takeval(Str, 0, Acc) -> {Acc, Str}; +takeval([], 0, Acc) -> Acc; + +takeval(Str, Num, Acc) -> + {Val, Rest} = phpval(Str), + %Lots of tracing if you enable this: + %io:format("\nState\n Str: ~s\n Num: ~w\n Acc:~w\n", [Str,Num,Acc]), + %io:format("-Val: ~w\n-Rest: ~s\n\n",[Val, Rest]), + takeval(Rest, Num - 1, [Val | Acc]). + +% +% Parse induvidual php values. +% a "phpval" here is T:val; where T is the type code for int, object, array etc.. +% + +% Simple ones: +phpval([]) -> []; +phpval([$} | Rest]) -> phpval(Rest); % skip } +phpval([$N, $; | Rest]) -> {null, Rest}; % null +phpval([$b, $:, $1, $; | Rest]) -> {true, Rest}; % true +phpval([$b, $:, $0, $; | Rest]) -> {false, Rest}; % false + +% r seems to be a recursive reference to something, represented as an int. +phpval([$r, $: | Rest]) -> + {RefNum, [$; | Rest1]} = string:to_integer(Rest), + {{php_ref, RefNum}, Rest1}; + +% int +phpval([$i, $: | Rest]) -> + {Num, [$; | Rest1]} = string:to_integer(Rest), + {Num, Rest1}; + +% double / float +% NB: php floats can be ints, and string:to_float doesn’t like that. +phpval(_X = [$d, $: | Rest]) -> + {Num, [$; | Rest1]} = case string:to_float(Rest) of + {error, no_float} -> string:to_integer(Rest); + {N, R} -> {N, R} + end, + {Num, Rest1}; + +% string +phpval([$s, $: | Rest]) -> + {Len, [$: | Rest1]} = string:to_integer(Rest), + S = list_to_binary(string:sub_string(Rest1, 2, Len + 1)), + {S, lists:nthtail(Len + 3, Rest1)}; + +% array +phpval([$a, $: | Rest]) -> + {NumEntries, [$:, ${ | Rest1]} = string:to_integer(Rest), + {Array, Rest2} = takeval(Rest1, NumEntries * 2), + {arraytidy(Array), Rest2}; + +% object O:4:\"User\":53:{ +phpval([$O, $: | Rest]) -> + {ClassnameLen, [$: | Rest1]} = string:to_integer(Rest), + % Rest1: "classname":NumEnt:{.. + Classname = string:sub_string(Rest1, 2, ClassnameLen + 1), + Rest1b = lists:nthtail(ClassnameLen + 3, Rest1), + {NumEntries, [$:, ${ | Rest2]} = string:to_integer(Rest1b), + {Classvals, Rest3} = takeval(Rest2, NumEntries * 2), + {{class, Classname, arraytidy(Classvals)}, Rest3}. + +%% +%% Helpers: +%% + +% convert [ k1,v1,k2,v2,k3,v3 ] into [ {k1,v2}, {k2,v2}, {k3,v3} ] +arraytidy(L) -> + lists:reverse(lists:foldl(fun arraytidy/2, [], L)). + +arraytidy(El, [{key___partial, K} | L]) -> [{atomize(K), El} | L]; + +arraytidy(El, L) -> [{key___partial, El} | L]. + +%% Make properties or keys into atoms +atomize(K) when is_binary(K) -> + atomize(binary_to_list(K)); +atomize(K) when is_list(K) -> + list_to_atom(string:to_lower(K)); +atomize(K) -> K. diff --git a/src/srvNodeMgr/tools/gameWorld/test/misc/tool.erl b/src/srvNodeMgr/tools/gameWorld/test/misc/tool.erl new file mode 100644 index 0000000..65e1ae4 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/misc/tool.erl @@ -0,0 +1,651 @@ +-module(tool). + +-compile(export_all). + + + +-define(IOFILE(Str, Args), (fun() -> + Command = io_lib:format(Str, Args), + file:write_file("../logs/data/proto_data.log", Command, [append]) + end)()). +%%导出协议统计结果 +stat_proto_data() -> + List = ets:tab2list(proto_stat), + NewList = lists:map(fun({Cmd, ProtoNum}) -> + {ProtoNum, Cmd} + end, List), + lists:foreach(fun({ProtoNum, Cmd}) -> + ?IOFILE("协议->~p使用次数为->~p ~n", [Cmd, ProtoNum]) + end + , lists:sort(NewList)). +%%打印函数调用测试结果 +trace_profile_result() -> + eprof:stop_profiling(), + eprof:analyze(total). + + +%% 分割列表的函数 +split(N, SrcList) -> + case length(SrcList) > N of + true -> + lists:split(N, SrcList); + false -> + {SrcList, []} + end. + + +%% @doc get IP address string from Socket +ip(Socket) -> + {ok, {IP, _Port}} = inet:peername(Socket), + {Ip0, Ip1, Ip2, Ip3} = IP, + list_to_binary(integer_to_list(Ip0) ++ "." ++ integer_to_list(Ip1) ++ "." ++ integer_to_list(Ip2) ++ "." ++ integer_to_list(Ip3)). + + +%% @doc quick sort +sort([]) -> + []; +sort([H | T]) -> + sort([X || X <- T, X < H]) ++ [H] ++ sort([X || X <- T, X >= H]). + +%% for +for(Max, Max, F) -> [F(Max)]; +for(I, Max, F) -> [F(I) | for(I + 1, Max, F)]. + +%%--------------------------------------------------- +%%给列表中元素加下标 by chenzm +%%@spec for(n,m,fun()) -> [] +%%--------------------------------------------------- +add_index([]) -> + []; +add_index(List) -> + for(1, length(List), fun(I) -> + Elem = lists:nth(I, List), + if + is_tuple(Elem) -> + list_to_tuple([I] ++ tuple_to_list(Elem)); + true -> + {I, Elem} + end + end). +add_index_to_record(List) -> + case List of + [] -> + []; + _ -> + for(1, length(List), fun(I) -> + Elem = lists:nth(I, List), + {I, Elem} + end) + end. + + +%% @doc convert float to string, f2s(1.5678) -> 1.57 +f2s(N) when is_integer(N) -> + integer_to_list(N) ++ ".00"; +f2s(F) when is_float(F) -> + [A] = io_lib:format("~.2f", [F]), + A. + + +%% @doc convert other type to atom +to_atom(Msg) when is_atom(Msg) -> + Msg; +to_atom(Msg) when is_binary(Msg) -> + tool:list_to_atom2(binary_to_list(Msg)); +to_atom(Msg) when is_list(Msg) -> + tool:list_to_atom2(Msg); +to_atom(_) -> + throw(other_value). %%list_to_atom(""). + +%% @doc convert other type to list +to_list(Msg) when is_list(Msg) -> + Msg; +to_list(Msg) when is_atom(Msg) -> + atom_to_list(Msg); +to_list(Msg) when is_binary(Msg) -> + binary_to_list(Msg); +to_list(Msg) when is_integer(Msg) -> + integer_to_list(Msg); +to_list(Msg) when is_float(Msg) -> + f2s(Msg); +to_list(_) -> + throw(other_value). + +%% @doc convert other type to binary +to_binary(Msg) when is_binary(Msg) -> + Msg; +to_binary(Msg) when is_atom(Msg) -> + list_to_binary(atom_to_list(Msg)); +%%atom_to_binary(Msg, utf8); +to_binary(Msg) when is_list(Msg) -> + list_to_binary(Msg); +to_binary(Msg) when is_integer(Msg) -> + list_to_binary(integer_to_list(Msg)); +to_binary(Msg) when is_float(Msg) -> + list_to_binary(f2s(Msg)); +to_binary(_Msg) -> + throw(other_value). + +%% @doc convert other type to float +to_float(Msg) -> + Msg2 = to_list(Msg), + list_to_float(Msg2). + +%% @doc convert other type to integer +%% -spec to_integer(Msg :: any()) -> integer(). %%liujing 2012-8-9 cancel +to_integer(Msg) when is_integer(Msg) -> + Msg; +to_integer(Msg) when is_binary(Msg) -> + Msg2 = binary_to_list(Msg), + list_to_integer(Msg2); +to_integer(Msg) when is_list(Msg) -> + list_to_integer(Msg); +to_integer(Msg) when is_float(Msg) -> + round(Msg); +to_integer(_Msg) -> + throw(other_value). + +to_bool(D) when is_integer(D) -> + D =/= 0; +to_bool(D) when is_list(D) -> + length(D) =/= 0; +to_bool(D) when is_binary(D) -> + to_bool(binary_to_list(D)); +to_bool(D) when is_boolean(D) -> + D; +to_bool(_D) -> + throw(other_value). + +%% @doc convert other type to tuple +to_tuple(T) when is_tuple(T) -> T; +to_tuple(T) -> {T}. + +%% @doc get data type {0=integer,1=list,2=atom,3=binary} +get_type(DataValue, DataType) -> + case DataType of + 0 -> + DataValue2 = binary_to_list(DataValue), + list_to_integer(DataValue2); + 1 -> + binary_to_list(DataValue); + 2 -> + DataValue2 = binary_to_list(DataValue), + list_to_atom(DataValue2); + 3 -> + DataValue + end. + +%% @spec is_string(List)-> yes|no|unicode +is_string([]) -> yes; +is_string(List) -> is_string(List, non_unicode). + +is_string([C | Rest], non_unicode) when C >= 0, C =< 255 -> is_string(Rest, non_unicode); +is_string([C | Rest], _) when C =< 65000 -> is_string(Rest, unicode); +is_string([], non_unicode) -> yes; +is_string([], unicode) -> unicode; +is_string(_, _) -> no. + + +%% @doc get random list +list_random(List) -> + case List of + [] -> + {}; + _ -> + RS = lists:nth(random:uniform(length(List)), List), + ListTail = lists:delete(RS, List), + {RS, ListTail} + end. + +%% @doc get a random integer between Min and Max +random(Min, Max) -> + Min2 = Min - 1, + random:uniform(Max - Min2) + Min2. + +%% @doc 掷骰子 +random_dice(Face, Times) -> + if + Times == 1 -> + random(1, Face); + true -> + lists:sum(for(1, Times, fun(_) -> random(1, Face) end)) + end. + +%% @doc 机率 +odds(Numerator, Denominator) -> + Odds = random:uniform(Denominator), + if + Odds =< Numerator -> + true; + true -> + false + end. + +odds_list(List) -> + Sum = odds_list_sum(List), + odds_list(List, Sum). +odds_list([{Id, Odds} | List], Sum) -> + case odds(Odds, Sum) of + true -> + Id; + false -> + odds_list(List, Sum - Odds) + end. +odds_list_sum(List) -> + {_List1, List2} = lists:unzip(List), + lists:sum(List2). + + +%% @doc 取整 大于X的最小整数 +ceil(X) -> + T = trunc(X), + if + X - T == 0 -> + T; + true -> + if + X > 0 -> + T + 1; + true -> + T + end + end. + + +%% @doc 取整 小于X的最大整数 +floor(X) -> + T = trunc(X), + if + X - T == 0 -> + T; + true -> + if + X > 0 -> + T; + true -> + T - 1 + end + end. +%% 4舍5入 +%% round(X) + +%% subatom +subatom(Atom, Len) -> + list_to_atom(lists:sublist(atom_to_list(Atom), Len)). + +%% @doc 暂停多少毫秒 +sleep(Msec) -> + receive + after Msec -> + true + end. + +md5(S) -> + Md5_bin = erlang:md5(S), + Md5_list = binary_to_list(Md5_bin), + lists:flatten(list_to_hex(Md5_list)). + +list_to_hex(L) -> + lists:map(fun(X) -> int_to_hex(X) end, L). + +int_to_hex(N) when N < 256 -> + [hex(N div 16), hex(N rem 16)]. +hex(N) when N < 10 -> + $0 + N; +hex(N) when N >= 10, N < 16 -> + $a + (N - 10). + +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. + +combine_lists(L1, L2) -> + Rtn = + lists:foldl( + fun(T, Acc) -> + case lists:member(T, Acc) of + true -> + Acc; + false -> + [T | Acc] + end + end, lists:reverse(L1), L2), + lists:reverse(Rtn). + + +get_process_info_and_zero_value(InfoName) -> + PList = erlang:processes(), + ZList = lists:filter( + fun(T) -> + case erlang:process_info(T, InfoName) of + {InfoName, 0} -> false; + _ -> true + end + end, PList), + ZZList = lists:map( + fun(T) -> {T, erlang:process_info(T, InfoName), erlang:process_info(T, registered_name)} + end, ZList), + [length(PList), InfoName, length(ZZList), ZZList]. + +get_process_info_and_large_than_value(InfoName, Value) -> + PList = erlang:processes(), + ZList = lists:filter( + fun(T) -> + case erlang:process_info(T, InfoName) of + {InfoName, VV} -> + if VV > Value -> true; + true -> false + end; + _ -> true + end + end, PList), + ZZList = lists:map( + fun(T) -> {T, erlang:process_info(T, InfoName), erlang:process_info(T, registered_name)} + end, ZList), + [length(PList), InfoName, Value, length(ZZList), ZZList]. + +get_msg_queue() -> + io:fwrite("process count:~p~n~p value is not 0 count:~p~nLists:~p~n", + get_process_info_and_zero_value(message_queue_len)). + +get_memory() -> + io:fwrite("process count:~p~n~p value is large than ~p count:~p~nLists:~p~n", + get_process_info_and_large_than_value(memory, 1048576)). + +get_memory(Value) -> + io:fwrite("process count:~p~n~p value is large than ~p count:~p~nLists:~p~n", + get_process_info_and_large_than_value(memory, Value)). + +get_heap() -> + io:fwrite("process count:~p~n~p value is large than ~p count:~p~nLists:~p~n", + get_process_info_and_large_than_value(heap_size, 1048576)). + +get_heap(Value) -> + io:fwrite("process count:~p~n~p value is large than ~p count:~p~nLists:~p~n", + get_process_info_and_large_than_value(heap_size, Value)). + +get_processes() -> + io:fwrite("process count:~p~n~p value is large than ~p count:~p~nLists:~p~n", + get_process_info_and_large_than_value(memory, 0)). + + +list_to_term(String) -> + {ok, T, _} = erl_scan:string(String ++ "."), + case erl_parse:parse_term(T) of + {ok, Term} -> + Term; + {error, Error} -> + Error + end. + + +substr_utf8(Utf8EncodedString, Length) -> + substr_utf8(Utf8EncodedString, 1, Length). +substr_utf8(Utf8EncodedString, Start, Length) -> + ByteLength = 2 * Length, + Ucs = xmerl_ucs:from_utf8(Utf8EncodedString), + Utf16Bytes = xmerl_ucs:to_utf16be(Ucs), + SubStringUtf16 = lists:sublist(Utf16Bytes, Start, ByteLength), + Ucs1 = xmerl_ucs:from_utf16be(SubStringUtf16), + xmerl_ucs:to_utf8(Ucs1). + +ip_str(IP) -> + case IP of + {A, B, C, D} -> + lists:concat([A, ".", B, ".", C, ".", D]); + {A, B, C, D, E, F, G, H} -> + lists:concat([A, ":", B, ":", C, ":", D, ":", E, ":", F, ":", G, ":", H]); + Str when is_list(Str) -> + Str; + _ -> + [] + end. + +%%对正负进行调整:负数变为0,正数保持不变 +int_format(Num) -> + if Num >= 0 -> + Num; + true -> + 0 + end. + +%%去掉字符串空格 +remove_string_black(L) -> + F = fun(S) -> + if S == 32 -> []; + true -> S + end + end, + Result = [F(lists:nth(I, L)) || I <- lists:seq(1, length(L))], + lists:filter(fun(T) -> T =/= [] end, Result). + + +%%获取协议操作的时间戳,true->允许;false -> 直接丢弃该条数据 +%%spec is_operate_ok/1 param: Type -> 添加的协议类型(atom); return: true->允许;false -> 直接丢弃该条数据 +is_operate_ok(Type, TimeStamp) -> + NowTime = util:longunixtime(), + case get(Type) of + undefined -> + put(Type, NowTime), + true; + Value -> + case (NowTime - Value) >= TimeStamp of + true -> + put(Type, NowTime), + true; + false -> + false + end + end. + +%%打包字符串数据 +pack_string(Str) -> + StrBin = tool:to_binary(Str), + Len = byte_size(StrBin), + {Len, StrBin}. + +%%对[{GetName, Rate},..]结构类型的单项随机获取的通用处理,空列表返回undefined +get_rand_single(RateList) -> + Fun = fun({_Tmp, R}, RNum) -> + RNum + R + end, + AllR = lists:foldl(Fun, 0, RateList), + GetRNum = util:rand(1, AllR), + Fun1 = fun({Atom, Rat}, [BGet, Ra, FirstNum, GetAtom1]) -> + EndNum = FirstNum + Rat, + if BGet =:= 0 andalso Ra =< EndNum -> + [1, Ra, EndNum, Atom]; + true -> + [BGet, Ra, EndNum, GetAtom1] + end + end, + [_NewBGet, _NewRa, _FirstNum, GetAtom] = lists:foldl(Fun1, [0, GetRNum, 0, undefined], RateList), + GetAtom. + +%%对[{GetName, Rate, MinNum, MaxNum},..]结构类型的单项随机获取的通用处理,空列表返回undefined +get_rand_single2(RateList) -> + Fun = fun({_Tmp, R, _Mn, _Mx}, RNum) -> + RNum + R + end, + AllR = lists:foldl(Fun, 0, RateList), + GetRNum = util:rand(1, AllR), + Fun1 = fun({Atom, Rat, MinNum, MaxNum}, [BGet, Ra, FirstNum, GetAtom1]) -> + EndNum = FirstNum + Rat, + if BGet =:= 0 andalso Ra =< EndNum -> + [1, Ra, EndNum, {Atom, MinNum, MaxNum}]; + true -> + [BGet, Ra, EndNum, GetAtom1] + end + end, + [_NewBGet, _NewRa, _FirstNum, GetAtom] = lists:foldl(Fun1, [0, GetRNum, 0, undefined], RateList), + GetAtom. + + +%%对单个玩家数据回档的操作函数======2012-9-17 from liujing====== + +%%获取需要处理的表名(返回字符串结构的表名) +get_handle_table_name() -> + TableList = lib_player_rw:get_all_tables(), + F = fun(TableName, GetList) -> + TableName1 = util:term_to_string(TableName), + case TableName1 =/= "cards" andalso TableName1 =/= "sys_acm" andalso string:str(TableName1, "admin") =/= 1 + andalso TableName1 =/= "auto_ids" andalso TableName1 =/= "shop" andalso string:str(TableName1, "base") =/= 1 + andalso TableName1 =/= "slaves" andalso TableName1 =/= "battle_cache_data" andalso string:str(TableName1, "arena") =/= 1 + andalso string:str(TableName1, "log_") =/= 1 andalso string:str(TableName1, "th") =/= 1 + andalso TableName1 =/= "rela" of + false -> GetList; + true -> + GetList ++ [util:string_to_term(tool:to_list(TableName1))] + end + end, + lists:foldl(F, [], TableList). + +ini_faraway_mongo_db(PoolId, Num) -> + Host = + case Num of + 1 -> + "113.105.250.125"; + 2 -> + "113.105.251.123"; + 4 -> + "183.61.130.69"; + _ -> + %%连接内部192.168.51.174服务器 + "192.168.51.174" + end, +%% Host = io:get_line("remote database ip:") , + Port = 27017, + case Num of + 0 -> + DB = "csj_dev_S1"; + _ -> + DB = lists:concat(["csj_dev_S", Num]) + end, + io:format("====dest db:~p~n", [[Host, DB]]), + + EmongoSize = 1, + emongo_sup:start_link(), + emongo_app:initialize_pools([PoolId, Host, Port, DB, EmongoSize]). + +ini_local_mongo_db(PoolId, Num) -> + Host = "192.168.51.174", +%% Host = io:get_line("sorce database ip:") , + Port = 27017, + case Num of + 0 -> + DB = "csj_dev_src_S1"; + _ -> + DB = lists:concat(["csj_dev_src_S", Num]) + end, + io:format("====src db:~p~n", [DB]), + EmongoSize = 1, + emongo_sup:start_link(), + emongo_app:initialize_pools([PoolId, Host, Port, DB, EmongoSize]). + +get_mongo_to_mysql(UidList, ServerNum) -> +%% CONFIG_FILE = "../config/gateway.config", + FarPoolId = lists:concat(["master_mongo_tmp", ServerNum]), + LocalPoolId = "master_mongo_l", + TableList = get_handle_table_name(), + ini_faraway_mongo_db(FarPoolId, ServerNum), + ini_local_mongo_db(LocalPoolId, ServerNum), + Fun1 = fun(TableName, GetUid) -> + case TableName of + player -> + io:format("========_1_~n"), + [WhereOpertion, FieldOpertion] = db_mongoutil:make_select_opertion(db_mongo:transfer_fields(TableName, "*"), [{id, GetUid}], [], []), + L = emongo:find_all(LocalPoolId, tool:to_list(TableName), WhereOpertion, FieldOpertion), + io:format("========_1_~p~n", [L]), + RList = db_mongo:handle_all_result(TableName, db_mongo:transfer_fields(TableName, "*"), L), + [DelOpertion] = db_mongoutil:make_delete_opertion([{id, GetUid}]), + emongo:delete(FarPoolId, tool:to_list(TableName), DelOpertion), + case RList of + [] -> + GetUid; + _ -> + FieldList = db_mongo:get_all_fields(TableName), + Fun2 = fun(RL) -> + FullKeyValuelist = db_mongo:fullKeyValue(TableName, lists:zip(FieldList, RL)), + FullKeyValuelist1 = checkF(FullKeyValuelist), + Opertion = db_mongoutil:make_insert_opertion(FullKeyValuelist1), + emongo:insert(FarPoolId, tool:to_list(TableName), Opertion) + end, + lists:foreach(Fun2, RList), + GetUid + end; + _ -> + io:format("========_3_[~p]~n", [TableName]), + FieldList = db_mongo:get_all_fields(TableName), + case FieldList of + undefined -> + GetUid; + [] -> + GetUid; + _ -> + [WhereOpertion, FieldOpertion] = db_mongoutil:make_select_opertion(db_mongo:transfer_fields(TableName, "*"), [{uid, GetUid}], [], []), + L = emongo:find_all(LocalPoolId, tool:to_list(TableName), WhereOpertion, FieldOpertion), + + RList = db_mongo:handle_all_result(TableName, db_mongo:transfer_fields(TableName, "*"), L), + [DelOpertion] = db_mongoutil:make_delete_opertion([{uid, GetUid}]), + emongo:delete(FarPoolId, tool:to_list(TableName), DelOpertion), + case RList of + [] -> + GetUid; + _ -> + Fun2 = fun(RL) -> + FullKeyValuelist = db_mongo:fullKeyValue(TableName, lists:zip(FieldList, RL)), + FullKeyValuelist1 = checkF(FullKeyValuelist), + Opertion = db_mongoutil:make_insert_opertion(FullKeyValuelist1), + emongo:insert(FarPoolId, tool:to_list(TableName), Opertion) + end, + lists:foreach(Fun2, RList), + GetUid + end + end + end + end, + Fun = fun(Uid) -> + lists:foldl(Fun1, Uid, TableList) + end, + lists:foreach(Fun, UidList). + +checkF(KeyVList) -> + Fun = fun({Key, Val}) -> + case Val of + [] -> + {Key, <<"[]">>}; + undefined -> + {Key, <<"[]">>}; + _ -> + {Key, Val} + end + end, + + lists:map(Fun, KeyVList). + +%%打印结构体,哥只能做到这样的 +%%dynamic_compile死活编译不过 +recinfo(FieldList, Rec) -> + RecordName = lists:nth(1, tuple_to_list(Rec)), + ValueList = lists:nthtail(1, tuple_to_list(Rec)), + Length = min(length(FieldList), length(ValueList)), + OutStr = "#" ++ tool:to_list(RecordName) ++ "{\r\n", + OutStr1 = lists:foldl(fun(Idx, Str) -> + Str ++ io_lib:format(" ~p = ~p,~n", [lists:nth(Idx, FieldList), lists:nth(Idx, ValueList)]) + end, OutStr, lists:seq(1, Length - 1)), + OutStr1 ++ io_lib:format(" ~p = ~p}~n", [lists:nth(Length, FieldList), lists:nth(Length, ValueList)]). + +gcd(A, 0) -> + A; +gcd(A, B) when A >= B -> + gcd(A rem B, B); +gcd(A, B) -> + gcd(B, A). +%%求最小公约数 +hcd([H | T]) -> + gcd(H, hcd(T)); +hcd([]) -> + 0. + diff --git a/src/srvNodeMgr/tools/gameWorld/test/misc/util.erl b/src/srvNodeMgr/tools/gameWorld/test/misc/util.erl new file mode 100644 index 0000000..5ad560b --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/misc/util.erl @@ -0,0 +1,563 @@ +%%%----------------------------------- +%%% @Module : util +%%% @Author : csj +%%% @Created : 2010.10.05 +%%% @Description: 公共函数 +%%%----------------------------------- +-module(util). +-include("common.hrl"). +-include("record.hrl"). +-compile(export_all). + +%% 在List中的每两个元素之间插入一个分隔符 +implode(_S, []) -> + [<<>>]; +implode(S, L) when is_list(L) -> + implode(S, L, []). +implode(_S, [H], NList) -> + lists:reverse([thing_to_list(H) | NList]); +implode(S, [H | T], NList) -> + L = [thing_to_list(H) | NList], + implode(S, T, [S | L]). + +%%返回X在[Min, Max]区间内的值 +minmax(X, Min, Max) -> + min(max(X, Min), Max). + +%% 字符->列 +explode(S, B) -> + re:split(B, S, [{return, list}]). +explode(S, B, int) -> + [list_to_integer(Str) || Str <- explode(S, B), length(Str) > 0]. + +thing_to_list(X) when is_integer(X) -> integer_to_list(X); +thing_to_list(X) when is_float(X) -> float_to_list(X); +thing_to_list(X) when is_atom(X) -> atom_to_list(X); +thing_to_list(X) when is_binary(X) -> binary_to_list(X); +thing_to_list(X) when is_list(X) -> X. + +%% 日志记录函数 +log(T, F, A, Mod, Line) -> + {ok, Fl} = file:open("logs/error_log.txt", [write, append]), + Format = list_to_binary("#" ++ T ++ " ~s[~w:~w] " ++ F ++ "\r\n~n"), + {{Y, M, D}, {H, I, S}} = erlang:localtime(), + Date = list_to_binary([integer_to_list(Y), "-", integer_to_list(M), "-", integer_to_list(D), " ", integer_to_list(H), ":", integer_to_list(I), ":", integer_to_list(S)]), + file:close(Fl). + +%% 取得当前的unix时间戳 +unixtime() -> + {M, S, _} = game_timer:now(),%%os:timestamp() + M * 1000000 + S. + +longunixtime() -> + {M, S, Ms} = game_timer:now(), + (M * 1000000000000 + S * 1000000 + Ms) div 1000. + +%% 转换成HEX格式的md5 +md5(S) -> + lists:flatten([io_lib:format("~2.16.0b", [N]) || N <- binary_to_list(erlang:md5(S))]). + +%% 产生一个介于Min到Max之间的随机整数 +rand(Same, Same) -> Same; +rand(Min, Max) -> + M = Min - 1, + if + Max - M =< 0 -> + 0; + true -> + %% 如果没有种子,将从核心服务器中去获取一个种子,以保证不同进程都可取得不同的种子 + case get("rand_seed") of + undefined -> + RandSeed = mod_rand:get_seed(), + random:seed(RandSeed), + put("rand_seed", RandSeed); + _ -> skip + end, + RanNum = random:uniform(Max - M) + M, + RanNum + end. + +%%随机从集合中选出指定个数的元素length(List) >= Num +%%[1,2,3,4,5,6,7,8,9]中选出三个不同的数字[1,2,4] +get_random_list(List, Num) -> + ListSize = length(List), + F = fun(N, List1) -> + Random = rand(1, (ListSize - N + 1)), + Elem = lists:nth(Random, List1), + List2 = lists:delete(Elem, List1), + List2 + end, + Result = lists:foldl(F, List, lists:seq(1, Num)), + List -- Result. + + +%%从列表[{a,7},{b,8},{c,90},{d,100}],a为7%概率,中根据概率选出指定数目不重复的元素列表 +%%注意Num不能大于lenth(List) +get_random_list_probability(List, Num) -> + F = fun(Elem, ProbabilityValue) -> + lists:duplicate(ProbabilityValue, Elem) + end, + Result1 = lists:flatten([F(Elem, ProbabilityValue) || {Elem, ProbabilityValue} <- List]), + ProbabilityList = lists:reverse(lists:sort([ProbabilityValue || {_Elem, ProbabilityValue} <- List])), + MaxProbabilityValue = lists:sum([lists:nth(N, ProbabilityList) || N <- lists:seq(1, Num)]), + Result2 = get_random_list(Result1, Num + MaxProbabilityValue - length(List)), + Result3 = lists:usort(Result2), + Result4 = get_random_list(Result3, Num), + Result4. + +get_randomId_by_value([], _) -> + fail; +get_randomId_by_value(Data, Random) -> + case lists:nth(1, Data) of + {Id, Weight} -> + case Weight >= Random of + true -> + Id; + false -> + get_randomId_by_value(Data -- [{Id, Weight}], Random - Weight) + end; + _ -> + io:format("get_randomId_by_value no match") + end. + +%%从列表[{a,7},{b,8},{c,9},{d,10}],a为7权重,b为8权重 +get_random_by_weight(List) -> + F = fun({Id, Weight}, Sum) -> + Sum + Weight + end, + Sum2 = lists:foldl(F, 0, List), + RandomVa = random:uniform(Sum2), + RandomId = get_randomId_by_value(List, RandomVa). + + +%%向上取整 +ceil(N) -> + T = trunc(N), + case N == T of + true -> T; + false -> 1 + T + end. + +%%向下取整 +floor(X) -> + T = trunc(X), + case (X < T) of + true -> T - 1; + _ -> T + end. + +sleep(T) -> + receive + after T -> ok + end. + +sleep(T, F) -> + receive + after T -> F() + end. + +get_list([], _) -> + []; +get_list(X, F) -> + F(X). + +%% for循环 +for(Max, Max, F) -> + F(Max); +for(I, Max, F) -> + F(I), + for(I + 1, Max, F). + +%% 带返回状态的for循环 +%% @return {ok, State} +for(Max, Min, _F, State) when Min < Max -> + {ok, State}; +for(Max, Max, F, State) -> F(Max, State); +for(I, Max, F, State) -> {ok, NewState} = F(I, State), for(I + 1, Max, F, NewState). + + +for_new(Min, Max, _F, State) when (Min > Max) -> + {ok, State}; +for_new(Min, Max, F, State) -> + {ok, NewState} = F(Min, State), + for_new(Min + 1, Max, F, NewState). + + +%% term序列化,term转换为string格式,e.g., [{a},1] => "[{a},1]" +term_to_string(Term) -> + binary_to_list(list_to_binary(io_lib:format("~w", [Term]))). + +%% term序列化,term转换为bitstring格式,e.g., [{a},1] => <<"[{a},1]">> +term_to_bitstring(Term) -> + erlang:list_to_bitstring(io_lib:format("~w", [Term])). + +%% term反序列化,string转换为term,e.g., "[{a},1]" => [{a},1] +string_to_term(String) -> + case String of + [] -> []; + _ -> + case erl_scan:string(String ++ ".") of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, Term} -> Term; + _Err -> undefined + end; + _Error -> + undefined + end + end. + +%%将列表转换为string [a,b,c] -> "a,b,c" +list_to_string(List) -> + case List == [] orelse List == "" of + true -> ""; + false -> + F = fun(E) -> + tool:to_list(E) ++ "," + end, + L1 = [F(E) || E <- List], + L2 = lists:concat(L1), + string:substr(L2, 1, length(L2) - 1) + end. + +%% term反序列化,bitstring转换为term,e.g., <<"[{a},1]">> => [{a},1] +bitstring_to_term(undefined) -> undefined; +bitstring_to_term(BitString) -> + string_to_term(tool:to_list(BitString)). + + +%% 时间函数 +%% ----------------------------------------------------------------- +%% 根据1970年以来的秒数获得日期 +%% ----------------------------------------------------------------- +seconds_to_localtime(Seconds) -> + DateTime = calendar:gregorian_seconds_to_datetime(Seconds + ?DIFF_SECONDS_0000_1900), + calendar:universal_time_to_local_time(DateTime). + +%% ----------------------------------------------------------------- +%% 判断是否同一天 +%% ----------------------------------------------------------------- +is_same_date(Seconds1, Seconds2) -> + NDay = (Seconds1 + 8 * 3600) div 86400, + ODay = (Seconds2 + 8 * 3600) div 86400, + NDay =:= ODay. + +%% ----------------------------------------------------------------- +%% 判断是否同一星期 +%% ----------------------------------------------------------------- +is_same_week(Seconds1, Seconds2) -> + {{Year1, Month1, Day1}, Time1} = seconds_to_localtime(Seconds1), + % 星期几 + Week1 = calendar:day_of_the_week(Year1, Month1, Day1), + % 从午夜到现在的秒数 + Diff1 = calendar:time_to_seconds(Time1), + Monday = Seconds1 - Diff1 - (Week1 - 1) * ?ONE_DAY_SECONDS, + Sunday = Seconds1 + (?ONE_DAY_SECONDS - Diff1) + (7 - Week1) * ?ONE_DAY_SECONDS, + if ((Seconds2 >= Monday) and (Seconds2 < Sunday)) -> true; + true -> false + end. + +%% ----------------------------------------------------------------- +%% 获取当天0点和第二天0点 +%% ----------------------------------------------------------------- +get_midnight_seconds(Seconds) -> + {{_Year, _Month, _Day}, Time} = seconds_to_localtime(Seconds), + % 从午夜到现在的秒数 + Diff = calendar:time_to_seconds(Time), + % 获取当天0点 + Today = Seconds - Diff, + % 获取第二天0点 + NextDay = Seconds + (?ONE_DAY_SECONDS - Diff), + {Today, NextDay}. + +%% 获取下一天开始的时间 +get_next_day_seconds(Now) -> + {{_Year, _Month, _Day}, Time} = util:seconds_to_localtime(Now), + % 从午夜到现在的秒数 + Diff = calendar:time_to_seconds(Time), + Now + (?ONE_DAY_SECONDS - Diff). + +%% ----------------------------------------------------------------- +%% 计算相差的天数 +%% ----------------------------------------------------------------- +get_diff_days(Seconds1, Seconds2) -> + {{Year1, Month1, Day1}, _} = seconds_to_localtime(Seconds1), + {{Year2, Month2, Day2}, _} = seconds_to_localtime(Seconds2), + Days1 = calendar:date_to_gregorian_days(Year1, Month1, Day1), + Days2 = calendar:date_to_gregorian_days(Year2, Month2, Day2), + DiffDays = abs(Days2 - Days1), + DiffDays + 1. + +%% 获取从午夜到现在的秒数 +get_today_current_second() -> + {_, Time} = calendar:now_to_local_time(game_timer:now()), + NowSec = calendar:time_to_seconds(Time), + NowSec. + +%%判断今天星期几 +get_date() -> + calendar:day_of_the_week(date()). + +%%获取上一周的开始时间和结束时间 +get_pre_week_duringtime() -> + OrealTime = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}), + {Year, Month, Day} = date(), + CurrentTime = calendar:datetime_to_gregorian_seconds({{Year, Month, Day}, {0, 0, 0}}) - OrealTime - 8 * 60 * 60,%%从1970开始时间值 + WeekDay = calendar:day_of_the_week(Year, Month, Day), + Day1 = + case WeekDay of %%上周的时间 + 1 -> 7; + 2 -> 7 + 1; + 3 -> 7 + 2; + 4 -> 7 + 3; + 5 -> 7 + 4; + 6 -> 7 + 5; + 7 -> 7 + 6 + end, + StartTime = CurrentTime - Day1 * 24 * 60 * 60, + EndTime = StartTime + 7 * 24 * 60 * 60, + {StartTime, EndTime}. + +%%获取本周的开始时间和结束时间 +get_this_week_duringtime() -> + OrealTime = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}), + {Year, Month, Day} = date(), + CurrentTime = calendar:datetime_to_gregorian_seconds({{Year, Month, Day}, {0, 0, 0}}) - OrealTime - 8 * 60 * 60,%%从1970开始时间值 + WeekDay = calendar:day_of_the_week(Year, Month, Day), + Day1 = + case WeekDay of %%上周的时间 + 1 -> 0; + 2 -> 1; + 3 -> 2; + 4 -> 3; + 5 -> 4; + 6 -> 5; + 7 -> 6 + end, + StartTime = CurrentTime - Day1 * 24 * 60 * 60, + EndTime = StartTime + 7 * 24 * 60 * 60, + {StartTime, EndTime}. + + +%%以e=2.718281828459L为底的对数 +lnx(X) -> + math:log10(X) / math:log10(?E). + +check_same_day(Timestamp) -> + NDay = (util:unixtime() + 8 * 3600) div 86400, + ODay = (Timestamp + 8 * 3600) div 86400, + NDay =:= ODay. + +%% desc: 计算距离下一个整点的时间(秒为单位) +get_now_to_next_hour() -> + 3600 - (1 + (calendar:time_to_seconds(time()) rem 3600)). + +%% desc: 1970到今天凌晨0点0分0秒的秒数 +calc_today_0_sec() -> + unixtime() - calendar:time_to_seconds(time()). + +%% 获取1970到昨天和今天凌晨0点0分0秒的秒数 +get_seconds_yesterday() -> + LastDayEnd = calc_today_0_sec(), + LastDayBegin = LastDayEnd - 3600 * 24, + {LastDayBegin, LastDayEnd}. + +%%对list进行去重,排序 +%%Replicat 0不去重,1去重 +%%Sort 0不排序,1排序 +filter_list(List, Replicat, Sort) -> + if Replicat == 0 andalso Sort == 0 -> + List; + true -> + if Replicat == 1 andalso Sort == 1 -> + lists:usort(List); + true -> + if Sort == 1 -> + lists:sort(List); + true -> + lists:reverse(filter_replicat(List, [])) + end + end + end. + +%%list去重 +filter_replicat([], List) -> + List; +filter_replicat([H | Rest], List) -> + Bool = lists:member(H, List), + List1 = + if Bool == true -> + [[] | List]; + true -> + [H | List] + end, + List2 = lists:filter(fun(T) -> T =/= [] end, List1), + filter_replicat(Rest, List2). + + +%%在两个数之间 +between(Item, Min, Max) -> + case Max > Min of + true -> + Item >= Min andalso Item =< Max; + false -> + Item >= Max andalso Item =< Min + end. + + +%%--------------------------同屏处理函数--------------------------- +%% @spec 相同区域算法,玩家在屏幕的正中央,因此只需要算出该玩家边框的4个坐标点 +get_screen(X, Y, SolutX, SolutY) -> + {HalfScreenWidth, HalfScreenHeight} = + if + SolutX =/= 0 andalso SolutY =/= 0 -> + {util:ceil(SolutX / 2), util:ceil(SolutY / 2)}; + true -> + {util:ceil(?SOLUT_X / 2), util:ceil(?SOLUT_Y / 2)} + end, + {X - HalfScreenWidth, Y - HalfScreenHeight, X + HalfScreenWidth, Y + HalfScreenHeight}. + +%% 判断X,Y是否在所在的框里面。 +is_same_screen(X, Y, {X1, Y1, X2, Y2}) -> + X1 =< X andalso X2 >= X andalso Y1 =< Y andalso Y2 >= Y. + +%%判断两个坐标是否在同一个屏幕里面 +is_same_screen([X1, Y1, X2, Y2], [SolutX, SolutY]) -> + {ScreenWidth, ScreenHeight} = + if + SolutX =/= 0 andalso SolutY =/= 0 -> + {SolutX, SolutY}; + true -> + {?SOLUT_X, ?SOLUT_Y} + end, +%% io:format("=====~p:~p~n",[ScreenWidth,ScreenHeight]) , + SX1 = X1 div ScreenWidth, + SY1 = Y1 div ScreenHeight, + SX2 = X2 div ScreenWidth, + SY2 = Y2 div ScreenHeight, + + SX1 == SX2 andalso SY1 == SY2. + +%% 获取九宫格(?SLICEWIDTH*?SLICEHEIGHT)的边界坐标 +%% 九宫格的边界各自为屏幕长宽的1/2 +get_matrix(X, Y) -> + X1 = X div ?SLICEWIDTH * ?SLICEWIDTH, + Y1 = Y div ?SLICEHEIGHT * ?SLICEHEIGHT, + {X1 - ?SLICEWIDTH, Y1 - ?SLICEHEIGHT, X1 + ?SLICEWIDTH * 2, Y1 + ?SLICEHEIGHT * 2}. +get_matrix(X, Y, SolutX, SolutY) -> + {SliceWidth, SliceHeight} = get_slice_area(SolutX, SolutY), + X1 = X div SliceWidth * SliceWidth, + Y1 = Y div SliceHeight * SliceHeight, + {X1 - SliceWidth, Y1 - SliceHeight, X1 + SliceWidth * 2, Y1 + SliceHeight * 2}. + +%% 获取九宫格小格子的长宽 +get_slice_area(SolutX, SolutY) -> + case SolutX =:= 0 andalso SolutY =:= 0 of + true -> + {?SLICEWIDTH, ?SLICEWIDTH}; + false -> + {round(SolutX / 2), round(SolutY / 2)} + end. + +%% 获取九宫格小格子的长宽 +get_slice(X, Y) -> + {SliceWidth, SliceHeight} = get_slice_area(0, 0), + X1 = SliceWidth div 2, + Y1 = SliceHeight div 2, + {X - X1, Y - Y1, X + X1, Y + Y1}. + +%% 获取九宫格小格子的长宽 +get_slice(X, Y, SolutX, SolutY) -> + {SliceWidth, SliceHeight} = get_slice_area(SolutX, SolutY), + X1 = SliceWidth div 2, + Y1 = SliceHeight div 2, + {X - X1, Y - Y1, X + X1, Y + Y1}. + +%% 判断X,Y是否在所在的九宫格边界内。 +is_in_matrix(X, Y, {X1, Y1, X2, Y2}) -> + if + X1 =< X andalso X2 >= X andalso Y1 =< Y andalso Y2 >= Y -> + true; + true -> + false + end. + +%是否在同一九宫格子小格子里面 +is_same_slice(X1, Y1, X2, Y2) -> + SX1 = X1 div ?SLICEWIDTH, + SY1 = Y1 div ?SLICEHEIGHT, + SX2 = X2 div ?SLICEWIDTH, + SY2 = Y2 div ?SLICEHEIGHT, + if + SX1 == SX2 andalso SY1 == SY2 -> + true; + true -> + false + end. +is_same_slice(X1, Y1, X2, Y2, SolutX, SolutY) -> + {SliceWidth, SliceHeight} = get_slice_area(SolutX, SolutY), + SX1 = X1 div SliceWidth, + SY1 = Y1 div SliceHeight, + SX2 = X2 div SliceWidth, + SY2 = Y2 div SliceHeight, + if + SX1 == SX2 andalso SY1 == SY2 -> + true; + true -> + false + end. +get_xy_slice(X1, Y1) -> + SX1 = X1 div ?SLICEWIDTH, + SY1 = Y1 div ?SLICEHEIGHT, + {SX1, SY1}. + +is_in_range(X1, Y1, X2, Y2, Range) -> + X = abs(X1 - X2), + Y = abs(Y1 - Y2), + X =< Range andalso Y =< Range. + +%%@spec 计算两点间的直线距离 +distance({X1, Y1}, {X2, Y2}) -> + round(math:sqrt((X2 - X1) * (X2 - X1) + (Y2 - Y1) * (Y2 - Y1))). +%%用于lists:flodl +make_list(SrcList, Item) when is_integer(SrcList) -> + [Item]; +make_list(SrcList, Item) -> + SrcList ++ [Item]. +%%将格式为{H,M,S}的时间格式数据装换为数字 +conver_time(Time) -> + case Time of + {H, M, S} -> + H * 10000 + M * 100 + S; + _ -> + ?ERROR_MSG("data parse in conver_time data is ~p ~n", [Time]), + 0 + end. +check_list(Src) -> + case Src of + 0 -> []; + List -> List + end. + +getDataFirstTime() -> + NowTime = unixtime(), + {{Year, Month, Day}, {Hour, Min, Second}} = calendar:local_time(), + DataFirstTime = NowTime - (Hour * 3600 + Min * 60 + Second), + DataFirstTime. + + +%%十进制转到N进制 ex: v10toVn(999999,36,[]) +v10toVn(I0, Base, R0) -> + D = I0 rem Base, + I1 = I0 div Base, + R1 = if D >= 10 -> + [D - 10 + $A | R0]; + true -> + [D + $0 | R0] + end, + if I1 =:= 0 -> + R1; + true -> + v10toVn(I1, Base, R1) + end. + diff --git a/src/srvNodeMgr/tools/gameWorld/test/mysql_test.erl b/src/srvNodeMgr/tools/gameWorld/test/mysql_test.erl new file mode 100644 index 0000000..f70a512 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/mysql_test.erl @@ -0,0 +1,51 @@ +%%%--------------------------------------------- +%%% @Module : mysql_test +%%% @Author : csj +%%% @Created : 2010.10.05 +%%% @Description: mysql测试 +%%%--------------------------------------------- +-module(mysql_test). +-compile(export_all). +-define(DB, mysql_conn_poll). +-define(DB_HOST, "localhost"). +-define(DB_PORT, 3386). +-define(DB_USER, "root"). +-define(DB_PASS, "root"). +-define(DB_NAME, "csj"). +-define(DB_ENCODE, utf8). + + +conn() -> + mysql:start_link(?DB, ?DB_HOST, ?DB_PORT, ?DB_USER, ?DB_PASS, ?DB_NAME, fun(_, _, _, _) -> ok end, ?DB_ENCODE), + mysql:connect(?DB, ?DB_HOST, ?DB_PORT, ?DB_USER, ?DB_PASS, ?DB_NAME, ?DB_ENCODE, true), +% mysql:fetch(?DB, <<"drop table if exists test">>), +% mysql:fetch(?DB, <<"create table test (id int not null auto_increment,row varchar(50) not null,r int not null, primary key (id)) engine = myisam">>), + mysql:fetch(?DB, <<"truncate table test">>), + ok. + +test() -> + mysql:fetch(?DB, <<"truncate table test">>), + mysql:fetch(?DB, <<"begin">>), + F = fun() -> + db_sql:execute(io_lib:format(<<"insert into `test` (`row`,`r`) values ('~s',~p)">>, ["我是来测试性能的", 123])), + db_sql:execute(io_lib:format(<<"update `test` set `row` = '~s' where id = ~p">>, ["我是来测试性能的", 1])) +% mysql:fetch(?DB, io_lib:format(<<"insert into `test` (`row`,`r`) values ('~s',~p)">>,["我是来测试性能的",123])) + end, + prof:run(F, 10000), + mysql:fetch(?DB, <<"commit">>), + +% mysql:fetch(?DB, <<"begin">>), +% +% F1 = fun() -> +% mysql:fetch(?DB, io_lib:format(<<"update `test` set `row` = '~s' where id = ~p">>,["我是来测试性能的",123])) +% end, +% prof:run(F1, 10000), +% mysql:fetch(?DB, <<"commit">>), +% +% F2 = fun() -> +% mysql:fetch(?DB, <<"select * from `test` where id = 1">>) +% end, +% prof:run(F2, 10000), + + ok. + diff --git a/src/srvNodeMgr/tools/gameWorld/test/mysql_to_emongo.erl b/src/srvNodeMgr/tools/gameWorld/test/mysql_to_emongo.erl new file mode 100644 index 0000000..d70d5f3 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/mysql_to_emongo.erl @@ -0,0 +1,402 @@ +%%%-------------------------------------- +%%% @Module : mysql_to_emongo +%%% @Author : csj +%%% @Created : 2010.10.20 +%%% @Description: mysql->emongo数据库转换处理模块 +%%%-------------------------------------- +-module(mysql_to_emongo). +-compile([export_all]). +-include("common.hrl"). + +-define(CONFIG_FILE, "../config/gateway.config"). + +-define(PoolId, mysql_conn_for_mongodb). + +-define(AUTO_IDS, "auto_ids"). + +%% 启动转换程序,只转换基础数据,不会对其它非基础数据产生影响 +start_base() -> + start("base_"), + ok. + +%% 启动转换程序注意,会将所有的表数据删除再转换数据 ,在正式的数据中不要调用此方法 +start_all() -> + start(""), + ok. + +start_single([Atom]) -> + Prefix = util:term_to_string(Atom), + start(Prefix), + ok. + +start_single2([Atom]) -> + Prefix = util:term_to_string(Atom), + start2(Prefix), + ok. + +%%清档,删除原有的非基础数据和管理员数据 +start_clear() -> + TableList = lib_player_rw:get_all_tables(), + F = fun(TableName) -> + TableName1 = util:term_to_string(TableName), + case TableName1 =/= "cards" andalso TableName1 =/= "sys_acm" andalso string:str(TableName1, "admin") =/= 1 + andalso TableName1 =/= "auto_ids" andalso TableName1 =/= "shop" andalso string:str(TableName1, "base") =/= 1 of + false -> skip; + true -> + emongo:delete(tool:to_list(?MASTER_POOLID), TableName1, []) + end + end, + [F(TableName) || TableName <- TableList], + ok. + +%% 启动转换程序 +start(Prefix) -> + case get_mysql_config(?CONFIG_FILE) of + [Host, Port, User, Password, DB_name, Encode] -> + mysql:start_link(?PoolId, Host, Port, User, Password, DB_name, fun(_, _, _, _) -> ok end, Encode), + mysql:connect(?PoolId, Host, Port, User, Password, DB_name, Encode, true), + case get_mongo_config(?CONFIG_FILE) of + [MongoPoolId, EmongoHost, EmongoPort, EmongoDatabase, EmongoSize] -> + init_mongo([MongoPoolId, EmongoHost, EmongoPort, EmongoDatabase, EmongoSize]), + io:format("Mysql~p ==>Mongo ~p~n", [[Host, Port, User, Password, DB_name], [EmongoHost, EmongoPort, EmongoDatabase]]), + io:format("Prefix ==>Mongo ~p~n", [Prefix]), + read_write_tables(DB_name, Prefix), + ok; + _ -> emongo_config_fail + end, + ok; + _ -> mysql_config_fail + end, + halt(), + ok. + +%% 启动转换程序 +start2(Prefix) -> + case get_mysql_config(?CONFIG_FILE) of + [Host, Port, User, Password, DB_name, Encode] -> + mysql:start_link(?PoolId, Host, Port, User, Password, DB_name, fun(_, _, _, _) -> ok end, Encode), + mysql:connect(?PoolId, Host, Port, User, Password, DB_name, Encode, true), + case get_mongo_config(?CONFIG_FILE) of + [MongoPoolId, EmongoHost, EmongoPort, EmongoDatabase, EmongoSize] -> + init_mongo([MongoPoolId, EmongoHost, EmongoPort, EmongoDatabase, EmongoSize]), + io:format("Mysql~p ==>Mongo ~p~n", [[Host, Port, User, Password, DB_name], [EmongoHost, EmongoPort, EmongoDatabase]]), + io:format("Prefix ==>Mongo ~p~n", [Prefix]), + read_write_tables(DB_name, Prefix), + ok; + _ -> emongo_config_fail + end, + ok; + _ -> mysql_config_fail + end, + ok. + +get_mysql_config(Config_file) -> + try + {ok, [L]} = file:consult(Config_file), + {_, C} = lists:keyfind(gateway, 1, L), + {_, Mysql_config} = lists:keyfind(mysql_config, 1, C), + {_, Host} = lists:keyfind(host, 1, Mysql_config), + {_, Port} = lists:keyfind(port, 1, Mysql_config), + {_, User} = lists:keyfind(user, 1, Mysql_config), + {_, Password} = lists:keyfind(password, 1, Mysql_config), + {_, DB} = lists:keyfind(db, 1, Mysql_config), + {_, Encode} = lists:keyfind(encode, 1, Mysql_config), + [Host, Port, User, Password, DB, Encode] + catch + _:_ -> no_config + end. + +get_mongo_config(Config_file) -> + try + {ok, [L]} = file:consult(Config_file), + {_, C} = lists:keyfind(gateway, 1, L), + {_, Emongo_config} = lists:keyfind(emongo_config, 1, C), + {_, MongoPoolId} = lists:keyfind(poolId, 1, Emongo_config), + {_, EmongoHost} = lists:keyfind(emongoHost, 1, Emongo_config), + {_, EmongoPort} = lists:keyfind(emongoPort, 1, Emongo_config), + {_, EmongoDatabase} = lists:keyfind(emongoDatabase, 1, Emongo_config), + {_, EmongoSize} = lists:keyfind(emongoSize, 1, Emongo_config), + [MongoPoolId, EmongoHost, EmongoPort, EmongoDatabase, EmongoSize] + catch + _:_ -> no_config + end. + +%%初始化emongoDB链接 +init_mongo([MongoPoolId, EmongoHost, EmongoPort, EmongoDatabase, EmongoSize]) -> + emongo_sup:start_link(), + emongo_app:initialize_pools([MongoPoolId, EmongoHost, EmongoPort, EmongoDatabase, EmongoSize]), +%% emongo:insert(tool:to_list(?MASTER_POOLID),"b",[{id, 111},{name,"111ls"},{age,130}]), +%% Bin1 = emongo:find_one(tool:to_list(?MASTER_POOLID), "player", [{"id", 1424}]), + ok. + +%%读写操作,将mysql数据转换为emongo文档对象 +%% SELECT column_name,data_type, column_key, extra FROM information_schema.columns WHERE table_schema='csj_dev' and table_name='adminkind' +read_write_tables(DB_name, Prefix) -> + timer:sleep(5 * 1000), + if Prefix =:= "" -> + emongo:delete(tool:to_list(?MASTER_POOLID), ?AUTO_IDS, []); + true -> + no_action + end, + Sql = "SELECT table_name FROM information_schema.tables WHERE table_schema='" ++ DB_name ++ "' and table_type ='BASE TABLE'", + emongo:ensure_index(tool:to_list(?MASTER_POOLID), "auto_ids", [{<<"id">>, 1}]), + emongo:ensure_index(tool:to_list(?MASTER_POOLID), "auto_ids", [{<<"name">>, 1}]), + Data = mysql:fetch(?PoolId, list_to_binary(Sql)), + R = handleResult(Data),%%R is [[<<"adminchange">>],[<<"admingroup">>],[<<"adminkind">>]] + F = fun(D) -> + [R1] = D, %%R1 is <<"adminchange">> + Index = string:str(binary_to_list(R1), Prefix), + + if Prefix =:= "" orelse Index =:= 1 orelse + ((Prefix =:= "" orelse Prefix =:= "base_") andalso R1 =:= <<"shop">>) + orelse R1 =:= <<"adminkind">> -> + Sql1 = "SELECT column_name,data_type,column_key,extra FROM information_schema.columns WHERE table_schema= '" ++ DB_name ++ "' AND table_name= '" ++ binary_to_list(R1) ++ "'", + Sql2 = "SELECT * FROM " ++ binary_to_list(R1), + Result1 = mysql:fetch(?PoolId, list_to_binary(Sql1)), + Result2 = mysql:fetch(?PoolId, list_to_binary(Sql2)), + ColumnAndType = handleResult(Result1),%%ColumnAndType is [[<<"id">>,<<"int">>],[<<"name">>,<<"varchar">>],[<<"pid">>,<<"varchar">>],[<<"url">>,<<"varchar">>]], + TableRecord = handleResult(Result2), + records_to_documents(DB_name, binary_to_list(R1), ColumnAndType, TableRecord), + if R1 =:= <<"adminkind">> -> + %% 转换后,设置“资源管理”为不显示 + emongo:update(tool:to_list(?MASTER_POOLID), <<"adminkind">>, + [{<<"name">>, <<"资源管理">>}], [{"$set", [{<<"show">>, <<"NO">>}]}]), + ok; + true -> no_action + end; + true -> skip + end + end, + [F(D) || D <- R], + + %%更新base数据时同步更新其它非base表的索引 + if Prefix =:= "base_" -> + add_other_table_index(DB_name, R); + true -> + skip + end, + ok. + +%%当调用start_base时将其它非base表的索引也同步过来,base表已在前面同步过 +%%R is [[<<"adminchange">>],[<<"admingroup">>],[<<"adminkind">>]] +add_other_table_index(DB_name, R) -> + F = fun(D) -> + [R1] = D, %%R1 is <<"adminchange">> + binary_to_list(R1)%%R1 is "adminchange" + end, + TableList = [F(D) || D <- R], + OtherTables = [T || T <- TableList, string:str(T, "base") =/= 1],%%除掉所有base开头的表 + F1 = fun(TableName) -> + Sql1 = "SELECT column_name,data_type,column_key,extra FROM information_schema.columns WHERE table_schema= '" ++ DB_name ++ "' AND table_name= '" ++ TableName ++ "'", + Result1 = mysql:fetch(?PoolId, list_to_binary(Sql1)), + ColumnAndType = handleResult(Result1),%%ColumnAndType is [[<<"id">>,<<"int">>],[<<"name">>,<<"varchar">>],[<<"pid">>,<<"varchar">>],[<<"url">>,<<"varchar">>]], + KeyList = [Key || [_Name, _Type, Key, _Extra] <- ColumnAndType, Key =:= <<"UNI">> orelse Key =:= <<"MUL">> orelse Key =:= <<"PRI">>], + case length(KeyList) of + 0 -> + io:format("~s Warning...No Key Table: [~p]\n", [misc:time_format(now()), TableName]); + _ -> + skip + end, + %%添加主键索引和唯一索引 + lists:foreach(fun(FieldAndType) -> + [Name, _Type, Key, Extra] = FieldAndType, + if Key =:= <<"PRI">>, Extra =:= <<"auto_increment">> -> + emongo:ensure_index(tool:to_list(?MASTER_POOLID), TableName, [{Name, 1}]); + Key =:= <<"UNI">> orelse Key =:= <<"MUL">> orelse Key =:= <<"PRI">> -> + emongo:ensure_index(tool:to_list(?MASTER_POOLID), TableName, [{Name, 1}]); + true -> + ok + end + end, + ColumnAndType), + + %%添加处理联合索引 + IndexSql = "SELECT column_name FROM information_schema.columns WHERE table_schema= '" ++ DB_name ++ "' AND table_name= '" ++ (TableName) ++ "' AND column_key ='MUL'", + IndexResult = handleResult(mysql:fetch(?PoolId, list_to_binary(IndexSql))), + case IndexResult of + [] -> skip; + _ -> + IndexResultSize = length(IndexResult), + F3 = fun(II) -> + [RR] = lists:nth(II, IndexResult), + {binary_to_list(RR), 1} + end, + LL = [F3(II) || II <- lists:seq(1, IndexResultSize)], + emongo:ensure_index(tool:to_list(?MASTER_POOLID), TableName, LL) + end + end, + [F1(Table) || Table <- OtherTables], + ok. + +%%["a"] -> "a" +term_to_string(Term) -> + binary_to_list(list_to_binary(io_lib:format("~p", [Term]))). + +string_to_term(String) -> + case erl_scan:string(String ++ ".") of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, Term} -> Term; + _Err -> undefined + end; + _Error -> + undefined + end. + + +%%将mysql记录转换为emongo的document +%%TableName like "test" +%%ColumnAndType like [[<<"id">>,<<"int">>], +%% [<<"row">>,<<"varchar">>], +%% [<<"r">>,<<"int">>]] +%%TableRecord like [[1,111111,<<"111111">>], +%% [2,9898,<<"9898bf">>]] +records_to_documents(DB_name, TableName, ColumnAndType, TableRecord) -> + ErrList = [Head || [Head | Tail] <- TableRecord, len_chk([Head | Tail]) =/= true], + if + length(ErrList) > 0 andalso TableName =/= "base_talk" -> + io:format("Waring for length!!!!! Table Name ~p.....Head:~p....... ~n", [TableName, ErrList]); + true -> + skip + end, + case length(TableRecord) of + 0 -> + H0 = lists:map(fun(FieldAndType) -> + [Name, _, _, _] = FieldAndType, + {tool:to_atom(Name), 0} + end, + ColumnAndType), + EmptyCollection = true, + H = [H0]; + _ -> + EmptyCollection = false, + F = fun(R) -> + CtList = mergeList(ColumnAndType), + ColumnSize = length(CtList), + [{lists:nth(I, CtList), lists:nth(I, R)} || I <- lists:seq(1, ColumnSize)] + end, + H = [F(Record) || Record <- TableRecord] %% H like [[{<<"id">>,1},{<<"name">>,<<"zs">>}],[[{<<"id">>,2},{<<"name">>,<<"ls">>}]] + end, + %%不删除cards表及管理员表 + case TableName =/= "cards" + andalso TableName =/= "base_com_gift" %%补偿奖励配置表不清除 +%% start_all 删除sys_acm表 +%% andalso TableName =/= "sys_acm" + andalso string:str(TableName, "admin") =/= 1 of + false -> skip; + true -> + emongo:delete(tool:to_list(?MASTER_POOLID), TableName, []), + Mysql_count = length(TableRecord), + io:format("handle: ~p ...", [TableName]), + insert_to_emongo(TableName, H), + case EmptyCollection of + true -> emongo:delete(tool:to_list(?MASTER_POOLID), TableName, []); + false -> no_action + end, + KeyList = [Key || [_Name, _Type, Key, _Extra] <- ColumnAndType, Key =:= <<"UNI">> orelse Key =:= <<"MUL">> orelse Key =:= <<"PRI">>], + case length(KeyList) of + 0 -> + io:format("\n ############## Warning...No Key Table: [~p] #################\n", [TableName]); + _ -> + skip + end, + %%添加主键索引和唯一索引 + lists:foreach(fun(FieldAndType) -> + [Name, _Type, Key, Extra] = FieldAndType, + if Key =:= <<"PRI">>, Extra =:= <<"auto_increment">> -> + emongo:ensure_index(tool:to_list(?MASTER_POOLID), TableName, [{Name, 1}]), + create_max_id(TableName, Name); + Key =:= <<"UNI">> orelse Key =:= <<"MUL">> orelse Key =:= <<"PRI">> -> + emongo:ensure_index(tool:to_list(?MASTER_POOLID), TableName, [{Name, 1}]); + true -> + ok + end + end, + ColumnAndType), + %%添加处理联合索引 + IndexSql = "SELECT column_name FROM information_schema.columns WHERE table_schema= '" ++ DB_name ++ "' AND table_name= '" ++ (TableName) ++ "' AND column_key ='MUL'", + IndexResult = handleResult(mysql:fetch(?PoolId, list_to_binary(IndexSql))), + case IndexResult of + [] -> skip; + _ -> + IndexResultSize = length(IndexResult), + F3 = fun(II) -> + [RR] = lists:nth(II, IndexResult), + {binary_to_list(RR), 1} + end, + LL = [F3(II) || II <- lists:seq(1, IndexResultSize)], + emongo:ensure_index(tool:to_list(?MASTER_POOLID), TableName, LL) + end, + Mongo_count = + case emongo:count(tool:to_list(?MASTER_POOLID), tool:to_list(TableName), []) of + undefined -> 0; + Val -> Val + end, + if Mysql_count =:= Mongo_count -> + io:format(" [~p]==>[~p] finished! ~n", [Mysql_count, Mongo_count]); + true -> + io:format(" [~p]==>[~p] ERROR!!!!!!!!!!!!!!!!!!!!!!!!!!!! ~n", [Mysql_count, Mongo_count]) + end + end, + ok. + +%%将mysql:fetch(?DB,Bin)查询结果转换为[[A]]形式, +handleResult(Data) -> + {_, Bin} = Data, + {_, _, R, _, _} = Bin, %%R is [[<<"adminchange">>],[<<"admingroup">>],[<<"adminkind">>]] + R. + +%%将列表转换形式[[<<"id">>,<<"int">>],[[<<"name">>,<<"varchar">>],<<"age">>,<<"int">>]] -> [<<"id">>,<<"name">>,<<"age">>] +mergeList(List) -> + F = fun(L1) -> + [Name, _Type, _Key, _Extra] = L1, + Name + end, + [F(L) || L <- List]. + +%%插入数据 +insert_to_emongo(TableName, H) -> +%% emongo:insert(tool:to_list(?MASTER_POOLID),"b",[{id, 111},{name,"111ls"},{age,130}]), + F = fun(R) -> +%% io:format("R is ~p~n",[R]), + emongo:insert(tool:to_list(?MASTER_POOLID), TableName, R) + end, + [F(R) || R <- H], + ok. + +%% 创建最大自增id +create_max_id(TableName, Name) -> + try +%% io:format("create_max_id_0_~p~n",[[TableName, Name]]), + Sql = "select max(" ++ tool:to_list(Name) ++ ") from " ++ tool:to_list(TableName), +%% io:format("create_max_id_1_~p~n",[Sql]), + Result = mysql:fetch(?PoolId, list_to_binary(Sql)), + [[MaxId]] = handleResult(Result), +%% io:format("create_max_id_1_~p~n",[MaxId]), + MaxId_1 = + case MaxId of + null -> 0; + undefined -> 0; + _ -> MaxId + end, +%% io:format("create_max_id_2_~p~n",[[TableName, Name, MaxId]]), + emongo:delete(tool:to_list(?MASTER_POOLID), ?AUTO_IDS, [{name, TableName}]), + emongo:insert(tool:to_list(?MASTER_POOLID), ?AUTO_IDS, [{name, TableName}, {Name, MaxId_1}]) + catch + _:_ -> error + end, + ok. + +%%mysql字段长度检测 +len_chk([]) -> + true; +len_chk(ChkList) -> + [H | T] = ChkList, + Length = length(tool:to_list(H)), + if + Length > 250 -> +%% io:format("len_chk_2_~p~n",[Length]), + false; + true -> + len_chk(T) + end. \ No newline at end of file diff --git a/src/srvNodeMgr/tools/gameWorld/test/performance/cast_and_call.erl b/src/srvNodeMgr/tools/gameWorld/test/performance/cast_and_call.erl new file mode 100644 index 0000000..f3ab4a0 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/performance/cast_and_call.erl @@ -0,0 +1,54 @@ +%%%--------------------------------------------- +%%% @module : cast_and_call +%%% @Author : csj +%%% @Created : 2010.10.05 +%%% @description: gen_server cast and call 测试 +%%% @result:cast 要比call 快0.006毫秒 +%%%--------------------------------------------- +-module(cast_and_call). +-behaviour(gen_server). + +%%Interface functions. +-export([start/0, test/0]). + +%%gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). + +start() -> + gen_server:start(?MODULE, [], []). + +init([]) -> + {ok, 1}. + +handle_cast('TEST', Status) -> + %%io:format("This is cast. ~n"), + {noreply, Status}. + +handle_call('TEST', _FROM, Status) -> + %%io:format("This is call. ~n"), + {reply, ok, Status}. + +handle_info(_Info, Status) -> + {noreply, Status}. + +terminate(normal, Status) -> + {ok, Status}. + +code_change(_OldVsn, Status, _Extra) -> + {ok, Status}. + +test() -> + {ok, Pid} = start(), + fprof:apply(gen_server, call, [Pid, 'TEST']), + fprof:profile(), + fprof:analyse({dest, "call.analysis"}), + fprof:apply(gen_server, cast, [Pid, 'TEST']), + fprof:profile(), + fprof:analyse({dest, "cast.analysis"}), + F1 = fun() -> gen_server:call(Pid, 'TEST') end, + F2 = fun() -> gen_server:cast(Pid, 'TEST') end, + prof:run(F1, 100000), + prof:run(F2, 100000). + + + diff --git a/src/srvNodeMgr/tools/gameWorld/test/performance/loop.erl b/src/srvNodeMgr/tools/gameWorld/test/performance/loop.erl new file mode 100644 index 0000000..8d9afa3 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/performance/loop.erl @@ -0,0 +1,34 @@ +%%%--------------------------------------------- +%%% @Module : loop +%%% @Author : csj +%%% @Created : 2010.10.05 +%%% @Description: 测试循环性能 +%%% @Resule : F2 比 F1 快20毫秒 不到10000基本可以忽略不计 +%%%--------------------------------------------- +-module(loop). +-compile(export_all). + + + + +do_loop([], _Data) -> ok; +do_loop([[_S] | T], Data) -> + do_loop(T, Data). + +do_loop2([], _Data, _X) -> ok; +do_loop2([[S] | T], Data, X) when S == X -> + do_loop2(T, Data, X); +do_loop2([[_S] | T], Data, X) -> + do_loop2(T, Data, X). + +test() -> + F1 = fun() -> + L = [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]], + do_loop2(L, <<16:16>>, [8]) + end, + F2 = fun() -> + L = [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]], + do_loop(lists:delete([8], L), <<16:16>>) + end, + prof:run(F1, 100000), + prof:run(F2, 100000). diff --git a/src/srvNodeMgr/tools/gameWorld/test/performance/prof.erl b/src/srvNodeMgr/tools/gameWorld/test/performance/prof.erl new file mode 100644 index 0000000..60475eb --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/performance/prof.erl @@ -0,0 +1,25 @@ +%%%--------------------------------------------- +%%% @Module : prof +%%% @Author : csj +%%% @Created : 2010.10.05 +%%% @Description: 性能测试工具 +%%%--------------------------------------------- +-module(prof). +-compile(export_all). + +%%性能测试 +%%Fun:函数 +%%Loop:执行次数 +run(Fun, Loop) -> + statistics(wall_clock), + for(1, Loop, Fun), + {_, T1} = statistics(wall_clock), + io:format("~p loops, using time: ~pms~n", [Loop, T1]), + ok. + +for(Max, Max, Fun) -> + Fun(); +for(I, Max, Fun) -> + Fun(), for(I + 1, Max, Fun). + + diff --git a/src/srvNodeMgr/tools/gameWorld/test/random_test.erl b/src/srvNodeMgr/tools/gameWorld/test/random_test.erl new file mode 100644 index 0000000..aa14e04 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/random_test.erl @@ -0,0 +1,160 @@ +%% Author: Administrator +%% Created: 2011-11-1 +%% Description: TODO: Add description to random_test +-module(random_test). + +%% +%% Include files +%% +-include("common.hrl"). +-include("record.hrl"). +%% +%% Exported Functions +%% +-compile(export_all). + +%% +%% API Functions +%% +random_test() -> + ?DEBUG("Start ~p", [[0]]), + random_test_loop(1000000), + ?DEBUG("Finish ~p", [[0]]). + +random_test_loop(T) -> + case T < 1 of + true -> + []; + _ -> + R = util:rand(1, 10000), + io:format("~p\n", [R]), + random_test_loop(T - 1) + end. + + +%% acid_to_player() -> +%% PlayerDB = ?DB_MODULE:select_all(player, "*", []), +%% F = fun(CD) -> +%% Cin = list_to_tuple([player | CD]), +%% case Cin#player.acid of +%% 0 -> +%% case ?DB_MODULE:select_row(user,"*",[{acnm,Cin#player.acnm}]) of +%% [] -> +%% ACId = ?DB_MODULE:insert(user, [acid,acnm,state,idcrs,sn], [0,Cin#player.acnm,0,0,0]), +%% ?DB_MODULE:update(user, [{acid,ACId}], [{id,ACId}]); +%% D -> +%% [ACId,_acid,_acnm,_st,_idc,_sn] = D +%% end, +%% ?DB_MODULE:update(player,[{acid, ACId}],[{id, Cin#player.id}]); +%% _ -> +%% [] +%% end +%% end, +%% lists:foreach(F, PlayerDB). + + + +online_clear() -> + PlayerDB = ?DB_MODULE:select_all(player, "*", [{olflg, 1}]), +%% Ftime = util:unixtime(), +%% ?DEBUG("Start ~p",[Ftime]), +%% PlayerDB = ?DB_MODULE:select_all(player, "*", []), + F = fun(CD) -> + Cin = list_to_tuple([player | CD]), + case lib_player:is_online(Cin#player.id) of + true -> + []; + _ -> + ?DB_MODULE:update(player, [{olflg, 0}], [{id, Cin#player.id}]) + end + end, + lists:foreach(F, PlayerDB). +%% Etime = util:unixtime(), +%% ?DEBUG("End ~p",[Etime]). + + +idcrs_clear() -> + UsrDB = ?DB_MODULE:select_all(user, "*", []), + F = fun(CD) -> + [Id, _ACId, _Acnm, _St, _Id, _sn] = CD, + ?DB_MODULE:update(user, [{idcrs, 0}], [{id, Id}]) + end, + lists:foreach(F, UsrDB). + + +%% relationship2relaTrans() -> +%% Relationship = ?DB_MODULE:select_all(relationship, "*", [{rela,0}]), +%% F = fun(CD) -> +%% [Id,IDA,IDB,Re,Al,Bsex,Bnick,Bcar,Bg,Bj,Asex,Anick,Acar,Ag,Aj] = CD, +%% RD = db_agent_rela:insert_rela(IDA,0,Anick,Asex,Acar,0,"",1), +%% Cin = list_to_tuple([ets_rela | RD]), +%% NFR = {IDB,Bnick,Bsex,Bcar,0,[]}, +%% NFRALL = [NFR|Cin#ets_rela.frid], +%% NCin = Cin#ets_rela{frid = NFRALL,fn = Cin#ets_rela.fn+1}, +%% db_agent_rela:update_rela_by_uid(NCin) +%% end, +%% lists:foreach(F, Relationship). + + + +fore_test() -> + lists:foreach(fun(D) -> + io:format(" ~p ", [D]) + end, [1, 2, 3, 4]). + + + +log_player() -> + D = ?DB_MODULE:select_all(player, "id,acid,acnm,nick,sex,crr", []), +%% ?DEBUG("~p",[D]), + F = fun(CD) -> + [Uid, Acid, Acnm, Nick, Sex, Crr] = CD, + ?DB_LOG_MODULE:insert(log_player, [uid, acid, acnm, nick, sex, crr], [Uid, Acid, Acnm, Nick, Sex, Crr]) + end, + lists:foreach(F, D). + + +lists_find() -> + Lis = data_name_list:get_list(), + Start = util:unixtime(), + io:format("Start Time ~p~n", [Start]), + find_test(Lis, 100000), + End = util:unixtime(), + io:format("End Time ~p~n", [End]). + +find_test(Lis, Num) -> + if Num < 1 -> + ok; + true -> + FindNum = 100001 + random:uniform(1000), + lists:keyfind(FindNum, 1, Lis), + find_test(Lis, Num - 1) + end. + + + +get_player(Lv) -> + D = ?DB_MODULE:select_all(player, "acnm,lv", [{lv, ">=", Lv}]), +%% ?DEBUG("~p",[D]). + F = fun(CD) -> + [Acnm, NLv] = CD, + Acm = util:string_to_term(tool:to_list(Acnm)), + {Acm, NLv} +%% ?DB_LOG_MODULE:insert(log_player,[uid,acid,acnm,nick], [Uid,Acid,Acnm,Nick]) + end, + Res = lists:map(F, D), + Start = util:unixtime(), + FileName = io_lib:format("./~p.txt", [Start]), + {ok, FileIo} = file:open(FileName, [write]), + File = io_lib:format("~p", [Res]), + file:write(FileIo, File), + file:close(FileIo). + + + + + + + + + diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/new_robot/chat_script.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/new_robot/chat_script.erl new file mode 100644 index 0000000..0b10b95 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/new_robot/chat_script.erl @@ -0,0 +1,49 @@ +%% @author Administrator +%% @doc @todo Add description to chat_script. + + +-module(chat_script). + +-define(HEADER_LENGTH, 4). % +%% ==================================================================== +%% API functions +%% ==================================================================== +-compile(export_all). + +call(11010, {Content}, Sokcet) -> + Len1 = byte_size(Content), + Data = <>, + gen_tcp:send(Sokcet, pack(11010, Data)); +call(11070, {Id, Content}, Sokcet) -> + Len1 = byte_size(Content), + Data = <>, + gen_tcp:send(Sokcet, pack(11070, Data)). +%% <> +handle_socket(11010, BinData) -> + io:format("rec package ~p~n", [11010]), + <> = BinData, + <> = Rest1, + <> = Rest2, + Nick1 = binary_to_list(Nick), + Content1 = binary_to_list(Content), + io:format(" nick name ~p , content ~p~n", [Nick1, Content1]), + BinData; +handle_socket(11070, BinData) -> + io:format("rec package ~p~n", [11070]), + <> = BinData, + <> = Rest1, + <> = Rest2, + Nick1 = binary_to_list(Nick), + Content1 = binary_to_list(Content), + io:format(" nick name ~p , content ~p~n", [Nick1, Content1]), + BinData; +handle_socket(_, _) -> + void. +%% ==================================================================== +%% Internal functions +%% ==================================================================== + +%%打包数据 +pack(Cmd, Data) -> + L = byte_size(Data) + ?HEADER_LENGTH, + <>. diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/new_robot/mail_script.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/new_robot/mail_script.erl new file mode 100644 index 0000000..27079e5 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/new_robot/mail_script.erl @@ -0,0 +1,84 @@ +%% @author Johanthe_Yip +%% @doc 旧项目邮件模块接口调试 ,由于本项目之前用于两个项目中,故包换两种邮件协议,该脚本 +%% 对两项目协议进行调试 + +-module(mail_script). + +%% ==================================================================== +%% API functions +%% ==================================================================== +-compile(export_all). +-define(HEADER_LENGTH, 4). % + +%%-----------旧版邮件协议请求服务器 1900x c_2_s ------------ + +%%==========获取信件列表及内容============= +call(19004, {Mail_type, Mail_page}, Sokcet) -> + gen_tcp:send(Sokcet, pack(19004, <>)); +%%==========提取附件========== +call(19006, {Mail_id}, Sokcet) -> + gen_tcp:send(Sokcet, pack(19006, <>)); + +%%-----------新版邮件协议请求服务器 190x c_2_s--------------- + +%%==========获取信件列表及内容========= +call(19051, {Page_index}, Sokcet) -> + gen_tcp:send(Sokcet, pack(19051, <>)). + +%%-----------旧版邮件协议接收信息 1900x s_2_c ------------ + +%%==========获取信件列表及内容============= +handle_socket(19004, BinData) -> + <> = BinData, + if Result =/= 0 -> + <> = Rest, + io:format("rec package ~p~n", [19004]), + {Maillist, _} = get_list([], BinList, MailNum), + io:format("package info: ~p~n", [Maillist]); + true -> + io:format("rec package but no data ~p ~n", [19004]) end, + BinData; +%%==========提取附件========== +handle_socket(19006, BinData) -> + io:format("rec package ~p~n", [19006]), + <> = BinData, + io:format("package info Result:~p MailId:~p~n", [Result, MailId]); + +%%-----------新版邮件协议请求服务器 190x s_2_c--------------- + +%%==========获取信件列表及内容========= +handle_socket(19051, BinData) -> + io:format("rec package ~p~n", [19051]), + BinData; +handle_socket(_, _) -> + void. + + +%% 读取列表,列表每项:[Id, Type,State, Timestamp, SName, Uid,Title, Content,Goods_list,Coin,Gold] +%% 对应:<> +%% AccList 列表累加器,使用时初始为[] +get_list(AccList, Bin, N) when N > 0 -> + case Bin of + <> -> + <> = Rest, + <> = Rest2, + <> = Rest3, + <> = Rest4, + SName1 = binary_to_list(SName), + Title1 = binary_to_list(Title), + Content1 = binary_to_list(Content), + GoodsBin1 = binary_to_list(GoodsBin), + Item = [Id, Type, State, Timestamp, SName, Title, Content, GoodsBin, Coin, Gold], + %io:format("Item: ~p~n", [Item]), + NewList = [Item | AccList], + get_list(NewList, Rest5, N - 1); + _R1 -> + error + end; +get_list(AccList, Bin, _) -> + {lists:reverse(AccList), Bin}. + +%%打包数据 +pack(Cmd, Data) -> + L = byte_size(Data) + ?HEADER_LENGTH, + <>. \ No newline at end of file diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/new_robot/new_robot.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/new_robot/new_robot.erl new file mode 100644 index 0000000..6544618 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/new_robot/new_robot.erl @@ -0,0 +1,850 @@ +%%% ------------------------------------------------------------------- +%%% @Author : Johanathe_Yip +%%% @Created : 2013.01.13 +%%% ------------------------------------------------------------------- +-module(new_robot). + +-behaviour(gen_server). +-compile(export_all). +-include("common.hrl"). +-include("record.hrl"). +-include_lib("stdlib/include/ms_transform.hrl"). + +-define(CONFIG_FILE, "../config/gateway.config"). + +%% 连接网关端口,不读取gateway配置 +-define(GATEWAY_ADD, "127.0.0.1"). +%% -define(GATEWAY_ADD,"192.168.51.131"). +-define(GATEWAY_PORT, 7777). + +-define(ACTION_SPEED_CONTROL, 10). +-define(ACTION_INTERVAL, ?ACTION_SPEED_CONTROL * 1000). % 自动行为最大时间间隔 +-define(ACTION_MIN, 3000). % 自动行为最小时间间隔 + +-define(TCP_OPTS, [ + binary, + {packet, 0}, % no packaging + {reuseaddr, true}, % allow rebind without waiting + {nodelay, false}, + {delay_send, true}, + {active, false}, + {exit_on_close, false} +]). + +-define(ETS_ROBOT, ets_robot). + +-define(CHECK_ROBOT_STATUS, 1 * 60 * 1000). + +%% -define(debug,1). +%% 断言以及打印调试信息宏 +-ifdef(debug). +-define(TRACE(Str), io:format(Str)). +-define(TRACE(Str, Args), io:format(Str, Args)). +% unicode版 +-define(TRACE_W(Str), io:format("~ts", [list_to_binary(io_lib:format(Str, []))])). +-define(TRACE_W(Str, Args), io:format("~ts", [list_to_binary(io_lib:format(Str, Args))])). +-else. +-define(TRACE(Str), void). +-define(TRACE(Str, Args), void). + +-define(TRACE_W(Str), void). +-define(TRACE_W(Str, Args), void). +-endif. + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-record(robot, { + orig_n, + login, + acid, %%account id + socket, %%socket + socket2, + socket3, + pid, %%process id + x, %%x坐标 + y, %%y坐标 + scene, + tox, + toy, + hp, + id, %% id + act, %% 动作 + status, %% 当前状态 + dstScene, + step, + frda, %% 好友信息 + bkda, %% 黑名单信息, + sgda %% 陌生人信息 +}). +%%% +%%% API + +start() -> + start(30000, 1), + ok. + + +%%StartId 起始AccountID +%%Num int 数量 +%%Mod 跑步模式 1 ,2 +start(StartId, Num) -> + sleep(100), + F = fun(N) -> + ?TRACE("start robot-~p~n", [N]), + sleep(100), + ?MODULE:start_link(StartId + N) + end, + for(0, Num, F), + ok. + +%%创建 一个ROBOT 进程 +start_link(N) -> + case gen_server:start(?MODULE, [N], []) of + {ok, _Pid} -> + ?TRACE("--robot~p start finish!-~n", [N]); + %gen_server:cast(Pid, {start_action}); + _ -> + fail + end. + +%% -------------------------------------------------------------------- +%% Function: init/1 +%% Description: Initiates the server +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% -------------------------------------------------------------------- +%%初始化玩家数据 +init([N]) -> + process_flag(trap_exit, true), + Pid = self(), + case login(N, Pid) of + {ok, Socket} -> + Scene = 10101, + Robot = #robot{socket = Socket, + login = 0, + acid = N, + id = 0, + pid = Pid, + act = none, + status = none, + scene = Scene, + dstScene = Scene, + tox = rand(1, 40), + toy = rand(1, 20), + orig_n = N, + step = 0, + frda = [], %% 好友信息 + bkda = [], %% 黑名单信息, + sgda = []%% 陌生人信息 + }, + %%登陆成功后开始动作 +%% %%旧协议收邮件 +%% mail_script:call(19004,{1,1},Socket), + %%旧协议收附件 +%% mail_script:call(19006,{1},Socket), +%% chat_script:call(11010,{<<"11">>},Socket), +%% chat_script:call(11070,{1,<<"11">>},Socket), +%% task_script:call(30003,1,Socket), +%% task_script:call(30003,2,Socket), + {ok, Robot}; + _Error -> + ?TRACE("init: error, reason: ~p~n", [_Error]), + {stop, normal, {}} + end. + + +%% -------------------------------------------------------------------- +%% Function: handle_call/3 +%% Description: Handling call messages +%% Returns: {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | (terminate/2 is called) +%% {stop, Reason, State} (terminate/2 is called) +%% -------------------------------------------------------------------- +handle_call({act}, _From, State) -> + %%act有跑步run或者静止undefined + handle(State#robot.act, a, State#robot.socket), + {reply, ok, State}; + +handle_call({get_state}, _From, State) -> + {reply, State, State}; + +handle_call({get_socket}, _From, State) -> + Reply = State#robot.socket, + {reply, Reply, State}; + +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. + +%% -------------------------------------------------------------------- +%% Function: handle_cast/2 +%% Description: Handling cast messages +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%% -------------------------------------------------------------------- +handle_cast({login_ok, _Code}, State) -> + ?TRACE("login successful~n"), + NewState = State#robot{login = 1}, + {noreply, NewState}; + +handle_cast(login_failed, State) -> + ?TRACE("login failed~n"), + {stop, normal, State}; + +handle_cast({playerid, Id}, State) -> + NewState = State#robot{id = Id}, + {noreply, NewState}; + +handle_cast(enter_ok, State) -> + NewState = State#robot{act = run, status = standing}, + gen_server:cast(self(), {start_action}), + {noreply, NewState}; + +handle_cast({start_action}, State) -> + if is_port(State#robot.socket) -> + %%心跳进程 + spawn_link(fun() -> handle(heart, a, State#robot.socket) end), + %%Pid= self(), + %%获取个人信息 + handle(get_self_info, 0, State#robot.socket), + %handle(chat,"-加经验 1000000",State#robot.socket), + %handle(chat,"-全功能",State#robot.socket), + {noreply, State}; + true -> + ?TRACE("start_action stop_1: /~p/,~n", [State]), + {stop, normal, State} + end; + +handle_cast({add_child_socket, N, Socket}, State) -> + NewState = + if + is_pid(State#robot.pid) andalso is_port(Socket) -> + case N of + 2 -> State#robot{socket2 = Socket}; + 3 -> State#robot{socket3 = Socket}; + _ -> State + end; + true -> + ?TRACE(" start_child_socket err : /~p/,~n", [State]), + State + end, + {noreply, NewState}; + +handle_cast({upgrade_state, NewState}, _State) -> + {noreply, NewState}; + +handle_cast({get_state_13001}, State) -> + handle(get_self_info, a, State#robot.socket), + {noreply, State}; + +handle_cast({upgrade_state_13001, [Scene, X, Y, Hp]}, State) -> + NewState = State#robot{x = X, y = Y, hp = Hp, scene = Scene}, + {noreply, NewState}; + +handle_cast({upgrade_state_13099, [IdLists]}, State) -> + IdLists1 = [[State#robot.id] | IdLists], + NewState = State#robot{frda = IdLists1}, + {noreply, NewState}; + +handle_cast({run}, State) -> + State2 = State#robot{act = run}, + {noreply, State2}; + +handle_cast({stop}, State) -> + State2 = State#robot{act = undefined}, + {noreply, State2}; + +handle_cast({stop, Reason}, State) -> + ?TRACE("~s_quit_2: /~p/~p/~p/,~n", [misc:time_format(now()), State#robot.acid, State#robot.id, Reason]), + {stop, normal, State}; + +handle_cast(_Msg, State) -> + {noreply, State}. + +%% -------------------------------------------------------------------- +%% Function: handle_info/2 +%% Description: Handling all non call/cast messages +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%% -------------------------------------------------------------------- +handle_info({stop, Reason}, State) -> + ?TRACE("~s ------ robot stop: /~p/~p/~p/,~n", [misc:time_format(now()), State#robot.acid, State#robot.id, Reason]), + {stop, normal, State}; + +handle_info({event, action_random, PlayerId, Socket}, State) -> + Random_interval = random:uniform(?ACTION_INTERVAL * 2) + ?ACTION_MIN * 6, +%% ?TRACE("~s_action_random: ~p~n", [misc:time_format(now()), Random_interval]), + handle_action_random(PlayerId, Socket), + %% 好友机器人测试 + NewState = handle_action_friend(State), + erlang:send_after(Random_interval, self(), {event, action_random, PlayerId, Socket}), + {noreply, NewState}; + +handle_info(close, State) -> + gen_tcp:close(State#robot.socket), + {noreply, State}; + +handle_info(_Info, State) -> + {noreply, State}. + +%% -------------------------------------------------------------------- +%% Function: terminate/2 +%% Description: Shutdown the server +%% Returns: any (ignored by gen_server) +%% -------------------------------------------------------------------- +terminate(Reason, State) -> + ?TRACE(" ----------terminate-----------~s_quit_4: /~p/~p/~p/,~n", [misc:time_format(now()), State#robot.acid, State#robot.id, Reason]), + if is_port(State#robot.socket) -> + gen_tcp:close(State#robot.socket); + true -> no_socket + end, + ok. + +%% -------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Convert process state when code is changed +%% Returns: {ok, NewState} +%% -------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%========================================================================= +%% 业务处理函数 +%%========================================================================= +%%登录游戏服务器 +login(N, Pid) -> + case get_game_server() of + {Ip, Port} -> + case connect_server(Ip, Port) of + {ok, Socket} -> + ?TRACE("~s ---connect to IP:~p Port: ~p ok...~n", [misc:time_format(now()), Ip, Port]), + Accid = N, + AccName = "Guest" ++ integer_to_list(Accid), + handle(login, {Accid, AccName}, Socket), + spawn_link(fun() -> do_parse_packet(Socket, Pid) end), + {ok, Socket}; + _Reason2 -> + ?TRACE("Connect to server failed: ~p~n", [_Reason2]), + error + end; + _Reason1 -> + ?TRACE("get server failed: ~p~n", [_Reason1]), + error_110 + end. + +%% 获取网关服务器参数 +get_gateway_config(Config_file) -> + try + {ok, [L]} = file:consult(Config_file), + {_, C} = lists:keyfind(gateway, 1, L), + {_, Mysql_config} = lists:keyfind(tcp_listener, 1, C), + {_, Ip} = lists:keyfind(ip, 1, Mysql_config), + {_, Port} = lists:keyfind(port, 1, Mysql_config), + [Ip, Port] + catch + _:_ -> [?GATEWAY_ADD, ?GATEWAY_PORT] + end. + +%%连接网关服务器 +get_game_server() -> + [Gateway_Ip, Gateway_Port] = [?GATEWAY_ADD, ?GATEWAY_PORT], + case gen_tcp:connect(Gateway_Ip, Gateway_Port, ?TCP_OPTS, 10000) of + {ok, Socket} -> + ?TRACE("get_game_server connected to gateway: Ip:~p, Port: ~p ~n", [Gateway_Ip, Gateway_Port]), + Data = pack(60000, <<>>), + gen_tcp:send(Socket, Data), + try + case gen_tcp:recv(Socket, ?HEADER_LENGTH) of + {ok, <>} -> + BodyLen = Len - ?HEADER_LENGTH, + case gen_tcp:recv(Socket, BodyLen, 3000) of + {ok, <>} -> + <> = Bin, + case Rlen of + 1 -> + <> = RB, + {IP, Bin2} = pt:read_string(Bin1), + <> = Bin2, + ?TRACE("get_game_server IP, Port: /~p/~p/~n", [IP, Port]), + {IP, Port}; + _Len -> + ?TRACE("recv 60000: Unknown Len: ~p~n", [_Len]), + no_gameserver + end; + _Reason1 -> + gen_tcp:close(Socket), + ?TRACE("error when recv 60000: Reason:~p ~n", [_Reason1]), + error_10 + end; + {error, _Reason2} -> + ?TRACE("get_game_server error:~p/~n", [_Reason2]), + gen_tcp:close(Socket), + error_20 + end + catch + _:_ -> gen_tcp:close(Socket), + error_30 + end; + {error, _Reason3} -> + ?TRACE("get_game_server--------------error:~p/~n", [_Reason3]), + error_40 + end. + +%%连接服务端 +connect_server(Ip, Port) -> + gen_tcp:connect(Ip, Port, ?TCP_OPTS, 10000). + +%% 接受信息 +async_recv(Sock, Length, Timeout) when is_port(Sock) -> + case prim_inet:async_recv(Sock, Length, Timeout) of + {error, Reason} -> throw({Reason}); + {ok, Res} -> Res; + Res -> Res + end. + +%%接收来自服务器的数据 - 登陆后进入游戏逻辑 +%%Socket:socket id +%%Client: client记录 +do_parse_packet(Socket, Pid) -> + Ref = async_recv(Socket, ?HEADER_LENGTH, ?HEART_TIMEOUT), + receive + {inet_async, Socket, Ref, {ok, <>}} -> + ?TRACE("receive command: ~p, length: ~p", [Cmd, Len]), + BodyLen = Len - ?HEADER_LENGTH, + RecvData = + case BodyLen > 0 of + true -> + Ref1 = async_recv(Socket, BodyLen, ?TCP_TIMEOUT), + receive + {inet_async, Socket, Ref1, {ok, Binary}} -> + ?TRACE("Data: ~p~n", [Binary]), + {ok, Binary}; + Other -> + ?TRACE("Data recv Error: ~p~n", [Other]), + {fail, Other} + end; + false -> + {ok, <<>>} + end, + case RecvData of + {ok, BinData} -> + case Cmd of + 10000 -> + <> = BinData, + case Code of + 0 -> + gen_server:cast(Pid, {login_ok, 0}), + <<_:32, PlayerId:64, _Bin2/binary>> = _Bin1, + handle(enter_player, {PlayerId}, Socket), + ok; + 1 -> + <> = _Bin1, + gen_server:cast(Pid, {login_ok, 1}), + handle(select_role, Accid, Socket), + ok; + _ -> + gen_server:cast(Pid, login_failed), + ?TRACE("login failed: Code: ~p~n", [Code]), + failed + end; + 10003 -> + <> = BinData, + ?TRACE("10003: Code: ~p PlayerId~p~n", [Code, PlayerId]), + if Code =:= 1 -> + handle(enter_player, {PlayerId}, Socket), + gen_server:cast(Pid, {playerid, PlayerId}); + true -> + gen_server:cast(Pid, {stop}) + end; + 10004 -> + <> = BinData, + ?TRACE("10004: Code: ~p ~n", [Code]), + if Code =/= 0 -> + gen_server:cast(Pid, enter_ok); + true -> + gen_server:cast(Pid, {stop}) + end; + 13001 -> + <> = BinData, + ?TRACE("13001: Uid:~p, Gender ~p, Level ~p, Speed ~p, Scene ~p, X ~p, Y ~p, Hp ~p~n", [Uid, Gender, Level, Speed, Scene, X, Y, Hp]), + %%更新信息 + gen_server:cast(Pid, {upgrade_state_13001, [Scene, X, Y, Hp]}), + {ok, Data} = ptr_30:write(30003, [4]), + gen_tcp:send(Socket, Data), + ok; + 10007 -> + <<_Code:16>> = BinData, + ok; + 30501 -> + Result = ptr_30:read(30501, BinData), + ?TRACE("~p : ~p", [30501, Result]), + {ok, List} = Result, + lists:foreach(fun(Tid) -> + {ok, Data} = ptr_30:write(30004, [Tid]), + gen_tcp:send(Socket, Data) + end, + List); + O -> + Result = ptr_30:read(O, BinData), + ?TRACE("~p : ~p", [O, Result]) + end, + do_parse_packet(Socket, Pid); + {fail, _} -> + ?TRACE("do_parse_packet recv data failed:/~p/~p/~n~p~n", [Socket, Pid, RecvData]), + gen_tcp:close(Socket), + gen_server:cast(Pid, {stop, socket_error_1}) + end; + %%超时处理 + {inet_async, Socket, Ref, {error, timeout}} -> + ?TRACE("do_parse_packet timeout:/~p/~p/~n", [Socket, Pid]), + do_parse_packet(Socket, Pid); + %%用户断开连接或出错 + Reason -> + ?TRACE("do_parse_packet: Error Reason:/~p/~p/~n", [Socket, Reason]), + gen_tcp:close(Socket), + gen_server:cast(Pid, {stop, socket_error_3}) + end. + +%% 随机事件处理 +handle_action_random(PlayerId, Socket) -> + Actions = [chat], + Action = lists:nth(random:uniform(length(Actions)), Actions), + Module = list_to_atom(lists:concat(["robot_", Action])), + catch Module:handle(PlayerId, Socket), + ok. + +handle_action_friend(State) -> + Socket = State#robot.socket, + Friend = State#robot.frda, + + case Friend of + [] -> + gen_tcp:send(Socket, pack(13099, <<40:8, 200:8>>)), + State; + _ -> + Index = random:uniform(length(Friend)), + PlayerId = lists:nth(Index, Friend), + Fri = lists:delete(PlayerId, Friend), + Actions = [friend], + Action = lists:nth(random:uniform(length(Actions)), Actions), + Module = list_to_atom(lists:concat(["robot_", Action])), + catch Module:handle(PlayerId, Socket), + State#robot{frda = Fri} + end. + +%%游戏相关操作%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%心跳包 +handle(heart, _, Socket) -> + case gen_tcp:send(Socket, pack(10006, <<>>)) of + ok -> + sleep(24 * 1000), + handle(heart, a, Socket); + _ -> + error + end; + +%%子socket链接 +handle(start_child_socket, {State, N}, _) -> + sleep(5000), + case get_game_server() of + {Ip, Port} -> + case connect_server(Ip, Port - N * 100) of + {ok, Socket} -> + Accid = State#robot.acid, + Pid = State#robot.pid, + Data = pack(10008, <<9999:16, Accid:32, N:8>>), + gen_tcp:send(Socket, Data), + try + Ref = async_recv(Socket, ?HEADER_LENGTH, ?TCP_TIMEOUT), + receive + {inet_async, Socket, Ref, {ok, <>}} -> + %%?TRACE("--------------------------cmd:~p~n",[Cmd]), + BodyLen = Len - ?HEADER_LENGTH, + case BodyLen > 0 of + true -> + Ref1 = async_recv(Socket, BodyLen, ?TCP_TIMEOUT), + receive + {inet_async, Socket, Ref1, {ok, Binary}} when Cmd =:= 10008 -> + %%?TRACE("----------------------rev--10008~n",[]), + <> = Binary, + %%?TRACE("----------------------rev--10008:~p~n",[Code]), + if + Code == 1 -> + %%spawn_link(fun()->do_parse_packet(Socket, Pid) end), + gen_server:cast(Pid, {add_child_socket, N, Socket}), + {ok, N}; + true -> + error_50 + end; + Other -> + ?TRACE("---------------child-----------cmd--other:~p~n", [Other]), + gen_tcp:close(Socket), + error_60 + end; + false -> + error_70 + end; + %%用户断开连接或出错 + Other -> + ?TRACE("---------------------child------------other-----err---------~p~n", [Other]), + gen_tcp:close(Socket), + error_80 + end + catch + _:_ -> gen_tcp:close(Socket), + error_90 + end; + _ -> + error_100 + end; + _ -> error_110 + end; + +%%登陆 +handle(login, {Accid, AccName}, Socket) -> + ?TRACE("sending login request entry socket: ~p ~p ~p~n", [Accid, AccName, Socket]), + AccStamp = 1273027133, + Tick = integer_to_list(Accid) ++ AccName ++ integer_to_list(AccStamp) ++ ?TICKET, + TickMd5 = util:md5(Tick), + TickMd5Bin = list_to_binary(TickMd5), + TLen = byte_size(TickMd5Bin), + AccNameLen = byte_size(list_to_binary(AccName)), + AccNameBin = list_to_binary(AccName), + Data = <<9999:16, Accid:32, AccStamp:32, AccNameLen:16, AccNameBin/binary, TLen:16, TickMd5Bin/binary>>, + ?TRACE("sending login request: ~p ~p~n", [Accid, AccName]), + gen_tcp:send(Socket, pack(10000, Data)), + ok; + +%%玩家列表 +handle(list_player, _, Socket) -> + gen_tcp:send(Socket, pack(10002, <<1:16>>)), + ok; + +%%选择角色进入 +handle(select_role, Accid, Socket) -> + NickName = "GUEST-" ++ integer_to_list(Accid), + NameBin = list_to_binary(NickName), + TLen = byte_size(NameBin), + gen_tcp:send(Socket, pack(10003, <<9999:16, 1:8, 1:8, TLen:16, NameBin/binary>>)), + ok; + +%%选择角色进入 +handle(enter_player, {PlayerId}, Socket) -> + gen_tcp:send(Socket, pack(10004, <<9999:16, PlayerId:64, 30:8, 20:8>>)), + ok; + +%%跑步 +handle(run, a, Socket) -> + X = util:rand(15, 45), + Y = util:rand(15, 45), + gen_tcp:send(Socket, pack(12001, <>)); + +%%ai模式跑步 +handle(run, {X, Y, SX, SY}, Socket) -> + ?TRACE("----running:[~p][~p]~n", [X, Y]), + gen_tcp:send(Socket, pack(12001, <>)); + +%%进入场景 +handle(enter_scene, Sid, Socket) -> + gen_tcp:send(Socket, pack(12005, <>)), + gen_tcp:send(Socket, pack(12002, <<>>)); %%换场景还要发送12002加载场景, 不然看不到角色的。 + +%% 聊天模块 +handle(chat1, PlayerId, Socket) -> + Actions = [chat], + Action = lists:nth(random:uniform(length(Actions)), Actions), + Module = list_to_atom(lists:concat(["robot_", Action])), + catch Module:handle(PlayerId, Socket), + ok; + +%%聊天 +handle(chat, Data, Socket) -> + Bin = list_to_binary(Data), + L = byte_size(Bin), + gen_tcp:send(Socket, pack(11010, <>)); + +%%静止 +handle(undefined, a, _Socket) -> + ok; +%%获取其他玩家信息 +handle(get_player_info, Id, Socket) -> + gen_tcp:send(Socket, pack(13004, <>)); + +%%获取自己信息 +handle(get_self_info, _, Socket) -> + ?TRACE("get_self_info: sending 13001~n"), + gen_tcp:send(Socket, pack(13001, <<>>)); + +%%复活 +handle(revive, _, Socket) -> + gen_tcp:send(Socket, pack(20004, <<3:8>>)), + Action = tool:to_binary("-加血 100000"), + ActionLen = byte_size(Action), + Data = <>, + Packet = pack(11020, Data), + gen_tcp:send(Socket, Packet); + +handle(Handle, Data, Socket) -> + ?TRACE("handle error: /~p/~p/~n", [Handle, Data]), + {reply, handle_no_match, Socket}. + +%%玩家列表 +read(<>) -> + ?TRACE("client read: ~p ~p ~p~n", [L, 10002, Num]), + F = fun(Bin1) -> + <> = Bin1, + {Name, Rest} = read_string(Bin2), + ?TRACE("player list: Id=~p Status=~p Pro=~p Sex=~p Lv=~p Name=~p~n", [Id, S, C, Sex, Lv, Name]), + Rest + end, + for(0, Num, F, Bin), + ?TRACE("player list end.~n"); + +read(<>) -> + ?TRACE("client read: ~p ~p~n", [L, Cmd]); +read(<>) -> + ?TRACE("client read: ~p ~p ~p~n", [L, Cmd, Status]); +read(<>) -> + ?TRACE("client read: ~p ~p ~p~n", [L, Cmd, Bin]); +read(Bin) -> + ?TRACE("client rec: ~p~n", [Bin]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%辅助函数 +%%读取字符串 +read_string(Bin) -> + case Bin of + <> -> + case Bin1 of + <> -> + {binary_to_list(Str), Rest}; + _R1 -> + {[], <<>>} + end; + _R1 -> + {[], <<>>} + end. + +random_sleep(T) -> + N = random:uniform(T), + timer:sleep(N * 100). + + +sleep(T) -> + receive + after T -> ok + end. + +for(Max, Max, _F) -> + []; +for(Min, Max, F) -> + [F(Min) | for(Min + 1, Max, F)]. + +for(Max, Max, _F, X) -> + X; +for(Min, Max, F, X) -> + F(X), + for(Min + 1, Max, F, X). + +sleep_send({T, S}) -> + receive + after T -> handle(run, a, S) + end. + +get_pid(Name) -> + case whereis(Name) of + undefined -> + err; + Pid -> Pid + end. + +ping(Node) -> + case net_adm:ping(Node) of + pang -> + ?TRACE("ping ~p error.~n", [Node]); + pong -> + ?TRACE("ping ~p success.~n", [Node]); + Error -> + ?TRACE("error: ~p ~n", [Error]) + end. + +do_act(Pid) -> + State = gen_server:call(Pid, {get_state}), + handle(State#robot.act, a, State#robot.socket), + sleep(2000), + do_act(Pid). + +%%根据机器人状态进行动作 +%%根据机器人状态进行动作 +ai(Pid) -> + %%?TRACE("start ai ~p.~n",[Pid]), + %%更新信息 + gen_server:cast(Pid, {get_state_13001}), + Random_interval = random:uniform(6000) + 3000, + sleep(Random_interval), + State = gen_server:call(Pid, {get_state}), + case State#robot.act of + run -> + case State#robot.hp > 0 of + true -> + case State#robot.status of + standing -> + + if State#robot.step == 0 -> + + Tox = rand(5, 27), + Toy = rand(30, 45), + New_step = 1; + true -> + + Tox = rand(5, 27),%%State#robot.tox, + Toy = rand(30, 45),%%State#robot.toy, + New_step = 0 + end, + State2 = State#robot{tox = Tox, toy = Toy, step = New_step, status = running}, + gen_server:cast(State#robot.pid, {upgrade_state, State2}); + running -> + + if State#robot.x =/= State#robot.tox orelse State#robot.y =/= State#robot.toy -> %%当前坐标不等于目的坐标 + handle(run, {State#robot.x, State#robot.y, State#robot.tox, State#robot.toy}, State#robot.socket), + Random_interval2 = round(abs(State#robot.tox - State#robot.x) / 4) * 1000, + sleep(Random_interval2), + handle(run, {State#robot.tox, State#robot.toy, State#robot.tox, State#robot.toy}, State#robot.socket); + true -> + State2 = State#robot{status = standing}, %%到达目的地, 换个状态为站 + gen_server:cast(State#robot.pid, {upgrade_state, State2}) %%更新机器人状态 + + end; + _ -> + ?TRACE("robot status error!~n") + end; + false -> + ok%handle(revive,a,State#robot.socket) + end; + undefined -> + ok + end, + ai(Pid). + +pack(Cmd, Data) -> + L = byte_size(Data) + ?HEADER_LENGTH, + <>. + + +rand(Same, Same) -> Same; +rand(Min, Max) -> + M = Min - 1, + if + Max - M =< 0 -> + 0; + true -> + random:uniform(Max - M) + M + end. diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/new_robot/ptr_30.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/new_robot/ptr_30.erl new file mode 100644 index 0000000..b8422d1 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/new_robot/ptr_30.erl @@ -0,0 +1,218 @@ +%%-------------------------------------- +%% @Module: ptr_30 +%% Author: Auto Generated +%% Created: Thu Feb 28 15:17:18 2013 +%% Description: +%%-------------------------------------- +-module(ptr_30). + +%%-------------------------------------- +%% Include files +%%-------------------------------------- +-include("common.hrl"). +-include("record.hrl"). + +%%-------------------------------------- +%% Exported Functions +%%-------------------------------------- +-compile(export_all). + + +%%-------------------------------------- +%%Protocol:30003 接受任务 +%%-------------------------------------- +read(30003, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol 30004 完成任务并挑选奖励 +%%-------------------------------------- +read(30004, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol:30005 查询任务npc状态 +%%-------------------------------------- +read(30005, <>) -> + <> = BinData, + Fun_NpcList = fun(_Idx, {RestBin, ResultList}) -> + <> = RestBin, + {_NpcList_RestBin, [[NpcId, NpcState] | ResultList]} + end, + {_NpcList_DoneBin, NpcList} = lists:foldl(Fun_NpcList, {NpcListBin, []}, lists:seq(1, NpcListLen)), + {ok, [lists:reverse(NpcList)]}; + +%%-------------------------------------- +%%Protocol:30006 获取指定长度任务列表 +%%-------------------------------------- +read(30006, <>) -> + <> = BinData, + Fun_TaskList = fun(_Idx, {RestBin, ResultList}) -> + <> = RestBin, + {_TaskList_RestBin, [[TaskId, TaskState, TaskProcess] | ResultList]} + end, + {_TaskList_DoneBin, TaskList} = lists:foldl(Fun_TaskList, {TaskListBin, []}, lists:seq(1, TaskListLen)), + {ok, lists:reverse(TaskList)}; + +%%-------------------------------------- +%%Protocol:30007 消耗元宝自动完成任务 +%%-------------------------------------- +read(30007, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 30501 服务端通知客户端任务的完成条件已满足 +%%-------------------------------------- +read(30501, <>) -> + <> = BinData, + Fun_TaskList = fun(_Idx, {RestBin, ResultList}) -> + <> = RestBin, + {_TaskList_RestBin, [TaskId | ResultList]} + end, + {_TaskList_DoneBin, TaskList} = lists:foldl(Fun_TaskList, {TaskListBin, []}, lists:seq(1, TaskListLen)), + {ok, lists:reverse(TaskList)}; + +%%-------------------------------------- +%%Protocol: 30502 服务器向客户端发送新的任务进度 +%%-------------------------------------- +read(30502, <>) -> + <> = BinData, + Fun_TaskList = fun(_Idx, {RestBin, ResultList}) -> + <> = RestBin, + {_TaskList_RestBin, [[TaskId, FinNum, NowNum] | ResultList]} + end, + {_TaskList_DoneBin, TaskList} = lists:foldl(Fun_TaskList, {TaskListBin, []}, lists:seq(1, TaskListLen)), + {ok, lists:reverse(TaskList)}; + +%%-------------------------------------- +%%Protocol:30503 通知客户端服务器为玩家触发了自动触发任务(列表) +%%-------------------------------------- +read(30503, <>) -> + <> = BinData, + Fun_TaskList = fun(_Idx, {RestBin, ResultList}) -> + <> = RestBin, + {_TaskList_RestBin, [TaskId | ResultList]} + end, + {_TaskList_DoneBin, TaskList} = lists:foldl(Fun_TaskList, {TaskListBin, []}, lists:seq(1, TaskListLen)), + {ok, [lists:reverse(TaskList)]}; + +%%-------------------------------------- +%%Protocol:30505 通知客户端服务器为玩家自动触发了某个任务 +%%-------------------------------------- +read(30505, <>) -> + {ok, [TaskId]}; + +%%-------------------------------------- +%%Protocol:30506 通知客户端服务器为玩家自动完成了某个任务 +%%-------------------------------------- +read(30506, <>) -> + {ok, [TaskId]}; + +%%-------------------------------------- +%%Protocol:30507 通知客户端日常任务重置 +%%-------------------------------------- +read(30507, <>) -> + <> = BinData, + Fun_TaskList = fun(_Idx, {RestBin, ResultList}) -> + <> = RestBin, + {_TaskList_RestBin, [Type | ResultList]} + end, + {_TaskList_DoneBin, TaskList} = lists:foldl(Fun_TaskList, {TaskListBin, []}, lists:seq(1, TaskListLen)), + {ok, [lists:reverse(TaskList)]}; + +%%-------------------------------------- +%% undefined command +%%-------------------------------------- +read(_Cmd, _R) -> + {error, no_match}. + +%%-------------------------------------- +%%Protocol:30003 接受任务 +%%-------------------------------------- +write(30003, [TaskId]) -> + {ok, pt:pack(30003, <>)}; + +%%-------------------------------------- +%%Protocol:协议号:30004 完成任务并挑选奖励 +%%-------------------------------------- +write(30004, [TaskId]) -> + {ok, pt:pack(30004, <>)}; + +%%-------------------------------------- +%%Protocol:30005 查询任务npc状态 +%%-------------------------------------- +write(30005, [NpcList]) -> + Fun_NpcList = fun([NpcId]) -> + <> + end, + NpcList_Len = length(NpcList), + NpcList_ABin = any_to_binary(lists:map(Fun_NpcList, NpcList)), + NpcList_ABinData = <>, + {ok, pt:pack(30005, <>)}; + +%%-------------------------------------- +%%Protocol:30006 获取指定长度任务列表 +%%-------------------------------------- +write(30006, [Len]) -> + {ok, pt:pack(30006, <>)}; + +%%-------------------------------------- +%%Protocol:30007 消耗元宝自动完成任务 +%%-------------------------------------- +write(30007, [TaskId]) -> + {ok, pt:pack(30007, <>)}; + +%%-------------------------------------- +%%Protocol: 30501 服务端通知客户端任务的完成条件已满足 +%%-------------------------------------- +write(30501, _) -> + {ok, pt:pack(30501, <<>>)}; + +%%-------------------------------------- +%%Protocol: 30502 服务器向客户端发送新的任务进度 +%%-------------------------------------- +write(30502, _) -> + {ok, pt:pack(30502, <<>>)}; + +%%-------------------------------------- +%%Protocol:30503 通知客户端服务器为玩家触发了自动触发任务(列表) +%%-------------------------------------- +write(30503, _) -> + {ok, pt:pack(30503, <<>>)}; + +%%-------------------------------------- +%%Protocol:30505 通知客户端服务器为玩家自动触发了某个任务 +%%-------------------------------------- +write(30505, _) -> + {ok, pt:pack(30505, <<>>)}; + +%%-------------------------------------- +%%Protocol:30506 通知客户端服务器为玩家自动完成了某个任务 +%%-------------------------------------- +write(30506, _) -> + {ok, pt:pack(30506, <<>>)}; + +%%-------------------------------------- +%%Protocol:30507 通知客户端日常任务重置 +%%-------------------------------------- +write(30507, _) -> + {ok, pt:pack(30507, <<>>)}; + +%%-------------------------------------- +%% undefined command +%%-------------------------------------- +write(Cmd, _R) -> + ?ERROR_MSG("~s_errorcmd_[~p] ", [misc:time_format(game_timer:now()), Cmd]), + {ok, pt:pack(0, <<>>)}. + +%%------------------------------------ +%% internal function +%%------------------------------------ +pack_string(Str) -> + BinData = tool:to_binary(Str), + Len = byte_size(BinData), + <>. + +any_to_binary(Any) -> + tool:to_binary(Any). + diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/new_robot/task_script.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/new_robot/task_script.erl new file mode 100644 index 0000000..c86a64d --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/new_robot/task_script.erl @@ -0,0 +1,30 @@ +%% @author Administrator +%% @doc @todo Add description to task_script. + + +-module(task_script). +-define(HEADER_LENGTH, 4). % +%% ==================================================================== +%% API functions +%% ==================================================================== +-export([call/3]). + +%% ==================================================================== +%% Internal functions +%% ==================================================================== + +call(30003, TaskId, Socket) -> + gen_tcp:send(Socket, pack(30003, <>)); +call(30004, TaskId, Socket) -> + gen_tcp:send(Socket, pack(30004, <>)); +call(30007, TaskId, Socket) -> + gen_tcp:send(Socket, pack(30007, <>)); +call(30005, NpcList, Socket) -> + gen_tcp:send(Socket, pack(30005, NpcList)); +call(30006, Size, Socket) -> + gen_tcp:send(Socket, pack(30006, <>)). + +%%打包数据 +pack(Cmd, Data) -> + L = byte_size(Data) + ?HEADER_LENGTH, + <>. diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/proto/ptr_11.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/proto/ptr_11.erl new file mode 100644 index 0000000..b1bc105 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/proto/ptr_11.erl @@ -0,0 +1,144 @@ +%%-------------------------------------- +%% @Module: ptr_11 +%% Author: Auto Generated +%% Created: Fri Mar 01 19:14:40 2013 +%% Description: +%%-------------------------------------- +-module(ptr_11). + +%%-------------------------------------- +%% Include files +%%-------------------------------------- +-include("common.hrl"). +-include("record.hrl"). + +%%-------------------------------------- +%% Exported Functions +%%-------------------------------------- +-compile(export_all). + + +%%-------------------------------------- +%%Protocol: 11000 聊天信息 +%%-------------------------------------- +read(11000, <>) -> + {Name, _Name_DoneBin} = pt:read_string(BinData), + <> = _Name_DoneBin, + {Content, _Content_DoneBin} = pt:read_string(_Type_DoneBin), + {ok, [Uid, Name, Type, Content]}; + +%%-------------------------------------- +%%Protocol: 11001 发送世界信息 +%%-------------------------------------- +read(11001, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 11002 发送场景信息 +%%-------------------------------------- +read(11002, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 11003 发送帮派信息 +%%-------------------------------------- +read(11003, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 11004 发送私聊信息 +%%-------------------------------------- +read(11004, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 11005 GM指令 +%%-------------------------------------- +read(11005, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 11010 系统信息/广播 +%%-------------------------------------- +read(11010, <>) -> + {Content, _Content_DoneBin} = pt:read_string(BinData), + {ok, [Type, Content]}; + +%%-------------------------------------- +%%Protocol: 11099 调试信息 +%%-------------------------------------- + +%%-------------------------------------- +%% undefined command +%%-------------------------------------- +read(_Cmd, _R) -> + {error, no_match}. + +%%-------------------------------------- +%%Protocol: 11000 聊天信息 +%%-------------------------------------- + +%%-------------------------------------- +%%Protocol: 11001 发送世界信息 +%%-------------------------------------- +write(11001, [ShowState, Content]) -> + Content_StrBin = pack_string(Content), + {ok, pt:pack(11001, <>)}; + +%%-------------------------------------- +%%Protocol: 11002 发送场景信息 +%%-------------------------------------- +write(11002, [Content]) -> + Content_StrBin = pack_string(Content), + {ok, pt:pack(11002, <>)}; + +%%-------------------------------------- +%%Protocol: 11003 发送帮派信息 +%%-------------------------------------- +write(11003, [Content]) -> + Content_StrBin = pack_string(Content), + {ok, pt:pack(11003, <>)}; + +%%-------------------------------------- +%%Protocol: 11004 发送私聊信息 +%%-------------------------------------- +write(11004, [PeerId, Content]) -> + Content_StrBin = pack_string(Content), + {ok, pt:pack(11004, <>)}; + +%%-------------------------------------- +%%Protocol: 11005 GM指令 +%%-------------------------------------- +write(11005, [Type, Content]) -> + Content_StrBin = pack_string(Content), + {ok, pt:pack(11005, <>)}; + +%%-------------------------------------- +%%Protocol: 11010 系统信息/广播 +%%-------------------------------------- + +%%-------------------------------------- +%%Protocol: 11099 调试信息 +%%-------------------------------------- +write(11099, [Content]) -> + Content_StrBin = pack_string(Content), + {ok, pt:pack(11099, <>)}; + +%%-------------------------------------- +%% undefined command +%%-------------------------------------- +write(Cmd, _R) -> + ?ERROR_MSG("~s_errorcmd_[~p] ", [misc:time_format(game_timer:now()), Cmd]), + {ok, pt:pack(0, <<>>)}. + +%%------------------------------------ +%% internal function +%%------------------------------------ +pack_string(Str) -> + BinData = tool:to_binary(Str), + Len = byte_size(BinData), + <>. + +any_to_binary(Any) -> + tool:to_binary(Any). + diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/proto/ptr_19.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/proto/ptr_19.erl new file mode 100644 index 0000000..a22cda3 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/proto/ptr_19.erl @@ -0,0 +1,198 @@ +%%-------------------------------------- +%% @Module: ptr_19 +%% Author: Auto Generated +%% Created: Tue Mar 05 09:35:33 2013 +%% Description: +%%-------------------------------------- +-module(ptr_19). + +%%-------------------------------------- +%% Include files +%%-------------------------------------- +-include("common.hrl"). +-include("record.hrl"). + +%%-------------------------------------- +%% Exported Functions +%%-------------------------------------- +-compile(export_all). + + +%%-------------------------------------- +%%Protocol: 19001 玩家反馈到GM +%%-------------------------------------- +read(19001, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 19002 获取GM反馈 +%%-------------------------------------- +read(19002, <>) -> + <> = BinData, + Fun_FbList = fun(_Idx, {RestBin, ResultList}) -> + <> = RestBin, + <> = _ContentList_RestBin, + Fun_ContentList = fun(_Idx, {RestBin, ResultList}) -> + {Name, _Name_DoneBin} = pt:read_string(RestBin), + {Content, _Content_DoneBin} = pt:read_string(_Name_DoneBin), + <> = _Content_DoneBin, + {_ContentList_RestBin, [[Name, Content, Date] | ResultList]} + end, + {_ContentList_DoneBin, ContentList} = lists:foldl(Fun_ContentList, {ContentListBin, []}, lists:seq(1, ContentListLen)), + {_ContentList_DoneBin, [[FbId, Type, State, lists:reverse(ContentList)] | ResultList]} + end, + {_FbList_DoneBin, FbList} = lists:foldl(Fun_FbList, {FbListBin, []}, lists:seq(1, FbListLen)), + {ok, [lists:reverse(FbList)]}; + +%%-------------------------------------- +%%Protocol: 19010 是否有未读邮件 +%%-------------------------------------- +read(19010, <>) -> + {ok, [Num]}; + +%%-------------------------------------- +%%Protocol: 19011 邮件列表 +%%-------------------------------------- +read(19011, <>) -> + <> = BinData, + Fun_MailList = fun(_Idx, {RestBin, ResultList}) -> + <> = RestBin, + {SName, _SName_DoneBin} = pt:read_string(_SName_RestBin), + {Title, _Title_DoneBin} = pt:read_string(_SName_DoneBin), + {_Title_DoneBin, [[MailId, Type, State, Date, SName, Title] | ResultList]} + end, + {_MailList_DoneBin, MailList} = lists:foldl(Fun_MailList, {MailListBin, []}, lists:seq(1, MailListLen)), + {ok, [lists:reverse(MailList)]}; + +%%-------------------------------------- +%%Protocol: 19012 邮件具体内容 +%%-------------------------------------- +read(19012, <>) -> + {Content, _Content_DoneBin} = pt:read_string(BinData), + <> = _Content_DoneBin, + Fun_GoodList = fun(_Idx, {RestBin, ResultList}) -> + <> = RestBin, + {_GoodList_RestBin, [[GoodTypeId, GoodsNum, Exist] | ResultList]} + end, + {_GoodList_DoneBin, GoodList} = lists:foldl(Fun_GoodList, {GoodListBin, []}, lists:seq(1, GoodListLen)), + {ok, [StCode, MailId, Content, lists:reverse(GoodList)]}; + +%%-------------------------------------- +%%Protocol: 19013 回复邮件 +%%-------------------------------------- +read(19013, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 19014 收取附件 +%%-------------------------------------- +read(19014, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 19015 删除邮件 +%%-------------------------------------- +read(19015, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 19016 发送邮件 +%%-------------------------------------- +read(19016, <>) -> + <> = BinData, + Fun_ErrRecvList = fun(_Idx, {RestBin, ResultList}) -> + {ErrName, _ErrName_DoneBin} = pt:read_string(RestBin), + {_ErrName_DoneBin, [ErrName | ResultList]} + end, + {_ErrRecvList_DoneBin, ErrRecvList} = lists:foldl(Fun_ErrRecvList, {ErrRecvListBin, []}, lists:seq(1, ErrRecvListLen)), + {ok, [Result, lists:reverse(ErrRecvList)]}; + +%%-------------------------------------- +%% undefined command +%%-------------------------------------- +read(_Cmd, _R) -> + {error, no_match}. + +%%-------------------------------------- +%%Protocol: 19001 玩家反馈到GM +%%-------------------------------------- +write(19001, [Type, Content]) -> + Content_StrBin = pack_string(Content), + {ok, pt:pack(19001, <>)}; + +%%-------------------------------------- +%%Protocol: 19002 获取GM反馈 +%%-------------------------------------- +write(19002, _) -> + {ok, pt:pack(19002, <<>>)}; + +%%-------------------------------------- +%%Protocol: 19010 是否有未读邮件 +%%-------------------------------------- +write(19010, _) -> + {ok, pt:pack(19010, <<>>)}; + +%%-------------------------------------- +%%Protocol: 19011 邮件列表 +%%-------------------------------------- +write(19011, _) -> + {ok, pt:pack(19011, <<>>)}; + +%%-------------------------------------- +%%Protocol: 19012 邮件具体内容 +%%-------------------------------------- +write(19012, [MailId]) -> + {ok, pt:pack(19012, <>)}; + +%%-------------------------------------- +%%Protocol: 19013 回复邮件 +%%-------------------------------------- +write(19013, [MailId, Content]) -> + Content_StrBin = pack_string(Content), + {ok, pt:pack(19013, <>)}; + +%%-------------------------------------- +%%Protocol: 19014 收取附件 +%%-------------------------------------- +write(19014, [MailId]) -> + {ok, pt:pack(19014, <>)}; + +%%-------------------------------------- +%%Protocol: 19015 删除邮件 +%%-------------------------------------- +write(19015, [MailId]) -> + {ok, pt:pack(19015, <>)}; + +%%-------------------------------------- +%%Protocol: 19016 发送邮件 +%%-------------------------------------- +write(19016, [Title, Content, RecvList]) -> + Title_StrBin = pack_string(Title), + Content_StrBin = pack_string(Content), + Fun_RecvList = fun([Name]) -> + Name_StrBin = pack_string(Name), + <> + end, + RecvList_Len = length(RecvList), + RecvList_ABin = any_to_binary(lists:map(Fun_RecvList, RecvList)), + RecvList_ABinData = <>, + {ok, pt:pack(19016, <>)}; + +%%-------------------------------------- +%% undefined command +%%-------------------------------------- +write(Cmd, _R) -> + ?ERROR_MSG("~s_errorcmd_[~p] ", [misc:time_format(game_timer:now()), Cmd]), + {ok, pt:pack(0, <<>>)}. + +%%------------------------------------ +%% internal function +%%------------------------------------ +pack_string(Str) -> + BinData = tool:to_binary(Str), + Len = byte_size(BinData), + <>. + +any_to_binary(Any) -> + tool:to_binary(Any). + diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/proto/ptr_40.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/proto/ptr_40.erl new file mode 100644 index 0000000..219276d --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/proto/ptr_40.erl @@ -0,0 +1,427 @@ +%%-------------------------------------- +%% @Module: ptr_40 +%% Author: Auto Generated +%% Created: Wed Mar 06 20:35:00 2013 +%% Description: +%%-------------------------------------- +-module(ptr_40). + +%%-------------------------------------- +%% Include files +%%-------------------------------------- +-include("common.hrl"). +-include("record.hrl"). +-include("debug.hrl"). +%%-------------------------------------- +%% Exported Functions +%%-------------------------------------- +-compile(export_all). + + +%%-------------------------------------- +%%Protocol: 40001 查询帮派(分页待定) +%%-------------------------------------- +%read(40001,<>) -> +read(40001, Data) -> + NewData = zlib:uncompress(Data), + <> = NewData, + %?TRACE("read 40001 CurPageNo= ~p ,TotalPage=~p ~n", [CurPageNo,TotalPage]), + <> = BinData, + %?TRACE("read 40001 GuildListLen: ~p ~n", [GuildListLen]), + Fun_GuildList = fun(_Idx, {RestBin, ResultList}) -> + <> = RestBin, + %?TRACE("read 40001 GuildId: ~p ~n", [GuildId]), + {GuildName, _GuildName_DoneBin} = pt:read_string(_GuildName_RestBin), + <> = _GuildName_DoneBin, + {Name, _Name_DoneBin} = pt:read_string(_Name_RestBin), + {Announce, _Announce_DoneBin} = pt:read_string(_Name_DoneBin), + {_Announce_DoneBin, [[GuildId, GuildName, CurNum, MaxNum, Level, Uid, Name, Announce] | ResultList]} + end, + {_GuildList_DoneBin, GuildList} = lists:foldl(Fun_GuildList, {GuildListBin, []}, lists:seq(1, GuildListLen)), + %?TRACE("read 40001 GuildList: ~p", [GuildList]), + {ok, [CurPageNo, TotalPage, lists:reverse(GuildList)]}; + +%%-------------------------------------- +%%Protocol: 40002 创建帮派 +%%-------------------------------------- +read(40002, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 40003 加入帮派 +%%-------------------------------------- +read(40003, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 40004 退出所在帮派 +%%-------------------------------------- +read(40004, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 40005 查询帮派成员 +%%-------------------------------------- +read(40005, <>) -> + {ok, [StCode]}; +read(40005, <>) -> + <> = BinData, + Fun_MemList = fun(_Idx, {RestBin, ResultList}) -> + <> = RestBin, + {Name, _Name_DoneBin} = pt:read_string(_Name_RestBin), + <> = _Name_DoneBin, + {_MemList_RestBin, [[Uid, Name, Level, Career, Gender, Position, Contrib, LastLoginTime, Online] | ResultList]} + end, + {_MemList_DoneBin, MemList} = lists:foldl(Fun_MemList, {MemListBin, []}, lists:seq(1, MemListLen)), + {ok, [StCode, lists:reverse(MemList)]}; + +%%-------------------------------------- +%%Protocol: 40006 发起弹劾 +%%-------------------------------------- +read(40006, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 40007 弹劾操作 +%%-------------------------------------- +read(40007, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 40008 获取弹劾信息 +%%-------------------------------------- +read(40008, <>) -> + {ok, [StCode]}; +read(40008, <>) -> + <> = BinData, + Fun_RejectList = fun(_Idx, {RestBin, ResultList}) -> + <> = RestBin, + {_RejectList_RestBin, [[Uid, Pos, State, AgreeNum, DisagreeNum, RemainTime] | ResultList]} + end, + {_RejectList_DoneBin, RejectList} = lists:foldl(Fun_RejectList, {RejectListBin, []}, lists:seq(1, RejectListLen)), + {ok, [StCode, lists:reverse(RejectList)]}; + +%%-------------------------------------- +%%Protocol: 40009 帮派日志 +%%-------------------------------------- +read(40009, <>) -> + <> = BinData, + Fun_LogList = fun(_Idx, {RestBin, ResultList}) -> + <> = RestBin, + {Name, _Name_DoneBin} = pt:read_string(_Name_RestBin), + <> = _Name_DoneBin, + {Content, _Content_DoneBin} = pt:read_string(_Content_RestBin), + {_Content_DoneBin, [[Uid, Name, TimeStamp, Content] | ResultList]} + end, + {_LogList_DoneBin, LogList} = lists:foldl(Fun_LogList, {LogListBin, []}, lists:seq(1, LogListLen)), + {ok, [lists:reverse(LogList)]}; + +%%-------------------------------------- +%%Protocol: 40030 邀请玩家加入帮派(帮主/副帮主/长老) +%%-------------------------------------- +read(40030, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 40031 帮派申请列表(帮主/副帮主) +%%-------------------------------------- +read(40031, <>) -> + <> = BinData, + Fun_ApplyList = fun(_Idx, {RestBin, ResultList}) -> + <> = RestBin, + {Name, _Name_DoneBin} = pt:read_string(_Name_RestBin), + <> = _Name_DoneBin, + {_ApplyList_RestBin, [[Uid, Name, Level, Career, Gender, Force, TimeStamp] | ResultList]} + end, + {_ApplyList_DoneBin, ApplyList} = lists:foldl(Fun_ApplyList, {ApplyListBin, []}, lists:seq(1, ApplyListLen)), + {ok, [lists:reverse(ApplyList)]}; + +%%-------------------------------------- +%%Protocol: 40032 通过或拒绝加入申请(帮主/副帮主) +%%-------------------------------------- +read(40032, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 40033 提升职务(帮主) +%%-------------------------------------- +read(40033, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 40034 解散帮派(帮主) +%%-------------------------------------- +read(40034, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 40035 踢出成员(帮主/副帮主) +%%-------------------------------------- +read(40035, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 40036 帮派升级(帮主/副帮主/长老) +%%-------------------------------------- +read(40036, <>) -> + {ok, [Result, UplevelCd]}; + +%%-------------------------------------- +%%Protocol: 40037 帮主让位 +%%-------------------------------------- +read(40037, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 40039 帮派公告设置 +%%-------------------------------------- +read(40039, <>) -> + {ok, [Result]}; + +%%-------------------------------------- +%%Protocol: 40070 帮派新增成员信息(广播) +%%-------------------------------------- +read(40070, <>) -> + {Name, _Name_DoneBin} = pt:read_string(BinData), + <> = _Name_DoneBin, + <> = _Level_DoneBin, + <> = _Career_DoneBin, + {ok, [Uid, Name, Level, Career, Gender]}; + +%%-------------------------------------- +%%Protocol: 40071 被踢通知(接收玩家) +%%-------------------------------------- +read(40071, <>) -> + {GuildName, _GuildName_DoneBin} = pt:read_string(BinData), + {ok, [GuildId, GuildName]}; + +%%-------------------------------------- +%%Protocol: 40072 帮派邀请 +%%-------------------------------------- +read(40072, <>) -> + {Name, _Name_DoneBin} = pt:read_string(BinData), + <> = _Name_DoneBin, + <> = _GuildId_DoneBin, + <> = _MemNum_DoneBin, + {GuildName, _GuildName_DoneBin} = pt:read_string(_Level_DoneBin), + <> = _GuildName_DoneBin, + {Name, _Name_DoneBin} = pt:read_string(_Uid_DoneBin), + {ok, [Uid, Name, GuildId, MemNum, Level, GuildName, Uid, Name]}; + +%%-------------------------------------- +%%Protocol: 40073 职位变化通告(广播) +%%-------------------------------------- +read(40073, <>) -> + {Name, _Name_DoneBin} = pt:read_string(BinData), + <> = _Name_DoneBin, + <> = _OldPos_DoneBin, + {ok, [Uid, Name, OldPos, NewPos]}; + +%%-------------------------------------- +%%Protocol: 40074 帮主让位通知(广播) +%%-------------------------------------- +read(40074, <>) -> + {OldName, _OldName_DoneBin} = pt:read_string(BinData), + <> = _OldName_DoneBin, + {NewName, _NewName_DoneBin} = pt:read_string(_NewUid_DoneBin), + {ok, [OldUid, OldName, NewUid, NewName]}; + +%%-------------------------------------- +%%Protocol: 40075 帮派升级通知(广播) +%%-------------------------------------- +read(40075, <>) -> + {ok, [OldLevel, NewLevel]}; + +%%-------------------------------------- +%%Protocol: 40076 拒绝申请通知(仅玩家) +%%-------------------------------------- +read(40076, <>) -> + {GuildName, _GuildName_DoneBin} = pt:read_string(BinData), + {ok, [GuildId, GuildName]}; + +%%-------------------------------------- +%%Protocol: 40077 新帮派公告 +%%-------------------------------------- +read(40077, <>) -> + {Content, _Content_DoneBin} = pt:read_string(BinData), + {ok, [Content]}; + +%%-------------------------------------- +%%Protocol: 40078 申请加入批准通知(仅玩家) +%%-------------------------------------- +read(40078, <>) -> + {GuildName, _GuildName_DoneBin} = pt:read_string(BinData), + {ok, [GuildId, GuildName]}; + +%%-------------------------------------- +%% undefined command +%%-------------------------------------- +read(_Cmd, _R) -> + {error, no_match}. + +%%-------------------------------------- +%%Protocol: 40001 查询帮派(分页待定) +%%-------------------------------------- +write(40001, [PageNo, IsNotFull, IsSameGroup]) -> + {ok, pt:pack(40001, <>)}; + +%%-------------------------------------- +%%Protocol: 40002 创建帮派 +%%-------------------------------------- +write(40002, [Name, Announce]) -> + Name_StrBin = pack_string(Name), + Announce_StrBin = pack_string(Announce), + {ok, pt:pack(40002, <>)}; + +%%-------------------------------------- +%%Protocol: 40003 加入帮派 +%%-------------------------------------- +write(40003, [GuildId]) -> + {ok, pt:pack(40003, <>)}; + +%%-------------------------------------- +%%Protocol: 40004 退出所在帮派 +%%-------------------------------------- +write(40004, _) -> + {ok, pt:pack(40004, <<>>)}; + +%%-------------------------------------- +%%Protocol: 40005 查询帮派成员 +%%<> +%%-------------------------------------- +write(40005, [GuildId, IsOnline]) -> + {ok, pt:pack(40005, <>)}; + +%%-------------------------------------- +%%Protocol: 40006 发起弹劾 +%%-------------------------------------- +write(40006, _) -> + {ok, pt:pack(40006, <<>>)}; + +%%-------------------------------------- +%%Protocol: 40007 弹劾操作 +%%-------------------------------------- +write(40007, [Ops]) -> + {ok, pt:pack(40007, <>)}; + +%%-------------------------------------- +%%Protocol: 40008 获取弹劾信息 +%%-------------------------------------- +write(40008, _) -> + {ok, pt:pack(40008, <<>>)}; + +%%-------------------------------------- +%%Protocol: 40009 帮派日志 +%%-------------------------------------- +write(40009, _) -> + {ok, pt:pack(40009, <<>>)}; + +%%-------------------------------------- +%%Protocol: 40030 邀请玩家加入帮派(帮主/副帮主/长老) +%%-------------------------------------- +write(40030, [PlayerId]) -> + {ok, pt:pack(40030, <>)}; + +%%-------------------------------------- +%%Protocol: 40031 帮派申请列表(帮主/副帮主) +%%-------------------------------------- +write(40031, _) -> + {ok, pt:pack(40031, <<>>)}; + +%%-------------------------------------- +%%Protocol: 40032 通过或拒绝加入申请(帮主/副帮主) +%%-------------------------------------- +write(40032, [Uid, Ops]) -> + {ok, pt:pack(40032, <>)}; + +%%-------------------------------------- +%%Protocol: 40033 提升职务(帮主) +%%-------------------------------------- +write(40033, [Uid]) -> + {ok, pt:pack(40033, <>)}; + +%%-------------------------------------- +%%Protocol: 40034 解散帮派(帮主) +%%-------------------------------------- +write(40034, _) -> + {ok, pt:pack(40034, <<>>)}; + +%%-------------------------------------- +%%Protocol: 40035 踢出成员(帮主/副帮主) +%%-------------------------------------- +write(40035, [PlayerId]) -> + {ok, pt:pack(40035, <>)}; + +%%-------------------------------------- +%%Protocol: 40036 帮派升级(帮主/副帮主/长老) +%%-------------------------------------- +write(40036, _) -> + {ok, pt:pack(40036, <<>>)}; + +%%-------------------------------------- +%%Protocol: 40037 帮主让位 +%%-------------------------------------- +write(40037, [Uid]) -> + {ok, pt:pack(40037, <>)}; + +%%-------------------------------------- +%%Protocol: 40039 帮派公告设置 +%%-------------------------------------- +write(40039, [Content]) -> + Content_StrBin = pack_string(Content), + {ok, pt:pack(40039, <>)}; + +%%-------------------------------------- +%%Protocol: 40070 帮派新增成员信息(广播) +%%-------------------------------------- + +%%-------------------------------------- +%%Protocol: 40071 被踢通知(接收玩家) +%%-------------------------------------- + +%%-------------------------------------- +%%Protocol: 40072 帮派邀请 +%%-------------------------------------- + +%%-------------------------------------- +%%Protocol: 40073 职位变化通告(广播) +%%-------------------------------------- + +%%-------------------------------------- +%%Protocol: 40074 帮主让位通知(广播) +%%-------------------------------------- + +%%-------------------------------------- +%%Protocol: 40075 帮派升级通知(广播) +%%-------------------------------------- + +%%-------------------------------------- +%%Protocol: 40076 拒绝申请通知(仅玩家) +%%-------------------------------------- + +%%-------------------------------------- +%%Protocol: 40077 新帮派公告 +%%-------------------------------------- + +%%-------------------------------------- +%%Protocol: 40078 申请加入批准通知(仅玩家) +%%-------------------------------------- + +%%-------------------------------------- +%% undefined command +%%-------------------------------------- +write(Cmd, _R) -> + ?ERROR_MSG("~s_errorcmd_[~p] ", [misc:time_format(game_timer:now()), Cmd]), + {ok, pt:pack(0, <<>>)}. + +%%------------------------------------ +%% internal function +%%------------------------------------ +pack_string(Str) -> + BinData = tool:to_binary(Str), + Len = byte_size(BinData), + <>. + +any_to_binary(Any) -> + tool:to_binary(Any). + diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/proto/ptr_44.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/proto/ptr_44.erl new file mode 100644 index 0000000..c675d4f --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/proto/ptr_44.erl @@ -0,0 +1,54 @@ +%%-------------------------------------- +%% @Module: ptr_11 +%% Author: Auto Generated +%% Created: Fri Mar 01 19:14:40 2013 +%% Description: +%%-------------------------------------- +-module(ptr_44). + +%%-------------------------------------- +%% Include files +%%-------------------------------------- +-include("common.hrl"). +-include("record.hrl"). + +%%-------------------------------------- +%% Exported Functions +%%-------------------------------------- +-compile(export_all). +%%-------------------------------------- +%%Protocol: 44001 升级技能 +%%-------------------------------------- +write(44001, [UpgradeType]) -> + {ok, pt:pack(44001, <>)}; +%%-------------------------------------- +%%Protocol: 44006 升星 +%%-------------------------------------- +write(44006, [AutoBuy, BatchUpgrade]) -> + {ok, pt:pack(44006, <>)}; + +%%-------------------------------------- +%%Protocol: 44007 升阶 +%%-------------------------------------- +write(44007, [AutoBuy]) -> + {ok, pt:pack(44007, <>)}; + + +%%-------------------------------------- +%% undefined command +%%-------------------------------------- +write(Cmd, _R) -> + ?ERROR_MSG("~s_errorcmd_[~p] ", [misc:time_format(game_timer:now()), Cmd]), + {ok, pt:pack(0, <<>>)}. + +%%------------------------------------ +%% internal function +%%------------------------------------ +pack_string(Str) -> + BinData = tool:to_binary(Str), + Len = byte_size(BinData), + <>. + +any_to_binary(Any) -> + tool:to_binary(Any). + diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/robot.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/robot.erl new file mode 100644 index 0000000..ff768c7 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/robot.erl @@ -0,0 +1,921 @@ +%%% ------------------------------------------------------------------- +%%% Author: SMXX +%%% Description : 机器人 +%%% Created : +%%% ------------------------------------------------------------------- +-module(robot). +-behaviour(gen_server). +-include("robot.hrl"). + +-compile(export_all). +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). + +-define(START_ROBOT_GOODS, false). + +%%% +%%% API + +start() -> + start([1, 1500, ?SERVER_PORT, ?SERVER_ADD]), % 默认端口 IP + ok. + +%%StartId 起始AccountID +%%Num int 数量 +%%Mod 跑步模式 1 ,2 指定端口和ip +start([StartId0, Num0, Port0, IP0]) -> + + io:format("--Start"), + StartId = list_to_integer(atom_to_list(StartId0)), + Num = list_to_integer(atom_to_list(Num0)), + Port = list_to_integer(atom_to_list(Port0)), + IP = atom_to_list(IP0), + %io:format("--StartId:~p, Num:~p, Port:~p, IP:~p ~n",[StartId, Num, Port, IP]), + ets:new(?ETS_ZIP_PROTO, [named_table, public, set, {read_concurrency, true}]), %%压缩协议ets表 + ets:new(player_mon_info, [named_table, public, set, {read_concurrency, true}]), %%压缩协议ets表 + ets:new(off_line_static, [named_table, public, set, {read_concurrency, true}]), + ets:insert(off_line_static, {1, 0}), + ets:insert(off_line_static, {2, 0}), + io:format("--StartId:~p, Num:~p, Port:~p, IP:~p ~n", [StartId, Num, Port, IP]), + sleep(500), + F = fun(N) -> + io:format("start robot-~p~n", [N]), + sleep(150), + robot:start_link(N, Port, IP) + %io:format("----start robot-~p end ~n",[N]) + end, + MaxNum = Num + StartId, + for(StartId, MaxNum, F), + [{_, NUM}] = ets:lookup(off_line_static, 2), + io:format("start finish total attr ~p ~n", [NUM]), + keep_alive(). + +keep_alive() -> + sleep(100000), + keep_alive(). + +%%创建 一个ROBOT 进程 +start_link(N, Port, IP) -> + io:format("--N:~p, Port:~p, IP:~p ~n", [N, Port, IP]), + case gen_server:start(?MODULE, [N, Port, IP], []) of + {ok, Pid} -> + io:format("--robot~p start finish!-~n", [N]), + case ?START_ROBOT_GOODS of + true -> + gen_server:cast(Pid, {startGoodsTest}); + _ -> + ok + end, + %gen_server:cast(Pid, {start_action}); + Pid, + ok; + _ -> + io:format("--robot error!-~n"), + fail + end. + +%% -------------------------------------------------------------------- +%% Function: init/1 +%% Description: Initiates the server +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% -------------------------------------------------------------------- +%%初始化玩家数据 +init([N, Port, IP]) -> + io:format("**********200130916 001 robot init N=~p,Port=~p,IP=~p ~n", [N, Port, IP]), + process_flag(trap_exit, true), + Pid = self(), + case login(N, Pid, Port, IP) of + {ok, Socket} -> + random:seed(erlang:now()), + Flag = random:uniform(2), + io:format("--robot init ~p start finish! runing ~p -~n", [N, Flag > 1]), + if Flag > 1 -> + [{_, NUM}] = ets:lookup(off_line_static, 2), + ets:insert(off_line_static, {2, NUM + 1}), + Act = run; + true -> + Act = other + end, + Scene = 10101, + Robot = #robot{socket = Socket, + login = 0, + acid = N, + id = 0, + pid = Pid, + act = chat,%%任务压测 + status = none, + scene = Scene, + dstScene = Scene, + tox = rand(1, 40), + toy = rand(1, 20), + orig_n = N, + step = 0, + guild = 0, + guild_post = 0, + frda = [], %% 好友信息 + bkda = [], %% 黑名单信息, + sgda = [] %% 陌生人信息 + }, + %%登陆成功后开始动作 + io:format("**********200130916 002 robot init finish ~n"), + case ?START_ROBOT_GOODS of + true -> + NewRobot = Robot#robot{act = test_goods}; + _ -> + NewRobot = Robot + end, + {ok, NewRobot}; + _Error -> + ?TRACE("init: error, reason: ~p~n", [_Error]), + {stop, normal, {}} + end. + +%% -------------------------------------------------------------------- +%% Function: handle_call/3 +%% Description: Handling call messages +%% Returns: {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | (terminate/2 is called) +%% {stop, Reason, State} (terminate/2 is called) +%% -------------------------------------------------------------------- +handle_call({get_state}, _From, State) -> + {reply, State, State}; + +%%更新玩家的任务列表信息(模拟前端) +handle_call({upgrade_state_30006, TaskList}, _From, State) -> + io:format("VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV:::~p~n", [TaskList]), + NewState = State#robot{task_list = TaskList}, + {reply, State, NewState}; + +%%处理模块发到某个模块的消息 +handle_call({Mod, Msg}, _From, State) -> + case lists:member(Mod, ?RANDOM_MODULE) of + true -> + Module = list_to_atom(lists:concat(["robot_", Mod])), + case catch Module:handle_call(State, Msg) of + {reply, Reply, NewState} when is_record(NewState, robot) -> + {reply, Reply, NewState}; + _ -> + {reply, noreply, State} + end; + false -> + ?TRACE("Error cast call: Mod:~p Msg: ~p~n", [Mod, Msg]), + {reply, error, State} + end; + +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. + +%% -------------------------------------------------------------------- +%% Function: handle_cast/2 +%% Description: Handling cast messages +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%% -------------------------------------------------------------------- +handle_cast({login_ok, _Code}, State) -> + ?TRACE("login successful~n"), + NewState = State#robot{login = 1}, + {noreply, NewState}; + +handle_cast(login_failed, State) -> + ?TRACE("login failed~n"), + {stop, normal, State}; + +handle_cast(startGoodsTest, State) -> + io:format("******20130916 robot handle_cast startGoodsTest socket = ~p~n ~n", [State#robot.socket]), + Pid = self(), + spawn_link(fun() -> ai(Pid) end), + {noreply, State}; + +handle_cast(enter_ok, State) -> + io:format("========enter_ok:~p ~p~n", [State#robot.act, State#robot.status]), + robot_task:handle(get_task, {}, State#robot.socket),%%获取任务列表 + gen_tcp:send(State#robot.socket, pack(21000, <<>>)), + gen_server:cast(self(), {start_action}), + {noreply, State}; + +handle_cast({playerid, Id}, State) -> + NewState = State#robot{id = Id}, + {noreply, NewState}; + +handle_cast({after_fight, Len, TargetBin}, State) -> + DataList = get_robot_status(Len, TargetBin, []), + case lists:keyfind(State#robot.id, 1, DataList) of + {_, CurHp} -> + case CurHp > 0 of + true -> + NewState = State; + false -> + NewState = State#robot{status = dead} + end; + _ -> + NewState = State + end, + {noreply, NewState}; + +handle_cast({start_action}, State) -> + if is_port(State#robot.socket) -> + %%心跳进程 + spawn_link(fun() -> handle(heart, a, State#robot.socket) end), + Pid = self(), + spawn_link(fun() -> ai(Pid) end), + if ?INITIAL_GM >= 1 -> + spawn(fun() -> robot_gm:handle(State) end); + true -> skip end, + erlang:send_after(1000, Pid, {random}), + {noreply, State}; + true -> + ?TRACE("start_action stop_1: /~p/,~n", [State]), + {stop, normal, State} + end; + +handle_cast({upgrade_state, NewState}, _State) -> + %%io:format("====upgrade_state ~p~n",[NewState#robot.status]) , + {noreply, NewState}; + +handle_cast({get_state_13001}, State) -> + handle(get_self_info, a, State#robot.socket), + {noreply, State}; + +handle_cast({init_skill_list, SkillList_ABin}, _State) -> + NewSkillList = robot_battle:make_skill_list(SkillList_ABin, []), + {noreply, _State#robot{skill_list = NewSkillList}}; + +handle_cast({upgrade_state_13001, [Scene, X, Y, Hp]}, State) -> + random:seed(erlang:now()), + EnterX = 20 + random:uniform(10) - 5, + EnterY = 10 + random:uniform(10) - 5, + case Hp =< 0 of + true -> + NewState = State#robot{x = EnterX, y = EnterY, scene = Scene, hp = Hp, status = dead}; + %%NewState = State#robot{x=X, y=Y,scene=Scene,hp = Hp, act = run, status = dead} ; + false -> + NewState = State#robot{x = EnterX, y = EnterY, scene = Scene, hp = Hp, status = standing} + %%NewState = State#robot{x=X, y=Y,scene=Scene,hp = Hp, act = %%run, status = standing} + end, +%% io:format("========upgrade_state_13001 enter scene:~p , ~p , ~p , ~p ~n", [Scene,Hp,NewState#robot.act,NewState#robot.status]) , + handle(enter_scene, [Scene, EnterX, EnterY], NewState#robot.socket), + %%handle(enter_scene, [Scene,X,Y] ,NewState#robot.socket), + {noreply, NewState}; + +handle_cast({upgrade_state_revive, [NewSceneId, ReviveX, ReviveY]}, State) -> + NewState = State#robot{status = standing, x = ReviveX, y = ReviveY}, + handle(enter_scene, [NewSceneId, ReviveX, ReviveY], State#robot.socket), + {noreply, NewState}; + +handle_cast({upgrade_state_13099, [IdLists]}, State) -> + IdLists1 = [[State#robot.id] | IdLists], + NewState = State#robot{frda = IdLists1}, + {noreply, NewState}; + +handle_cast({run}, State) -> + State2 = State#robot{act = run}, + {noreply, State2}; + +handle_cast({stop}, State) -> + State2 = State#robot{act = undefined}, + {noreply, State2}; + +handle_cast({stop, _Reason}, State) -> + ?TRACE("~s_quit_2: /~p/~p/~p/,~n", [misc:time_format(now()), State#robot.acid, State#robot.id, _Reason]), + {stop, normal, State}; + +handle_cast({get_bag_list, [Location]}, State) -> + io:format("*********20130916 robot handle_cast get_bag_list Location = ~p~n", [Location]), + {noreply, State}; + +%%处理模块发到某个模块的消息 +handle_cast({Mod, Msg}, State) -> + case lists:member(Mod, ?RANDOM_MODULE) of + true -> + Module = list_to_atom(lists:concat(["robot_", Mod])), + case catch Module:handle_cast(Msg, State) of + {noreply, NewState} when is_record(NewState, robot) -> + NewState; + _ -> + State + end; + false -> + ?TRACE("Error cast call: Mod:~p Msg: ~p~n", [Mod, Msg]), + State + end, + {noreply, State}; + +handle_cast(_Msg, State) -> + {noreply, State}. + +%% -------------------------------------------------------------------- +%% Function: handle_info/2 +%% Description: Handling all non call/cast messages +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%% -------------------------------------------------------------------- +handle_info({random}, State) -> + NewState = handle_action_random(State), + erlang:send_after(?RANDOM_INTERVAL, self(), {random}), + {noreply, NewState}; + +handle_info({stop, _Reason}, State) -> + ?TRACE("~s ------ robot stop: /~p/~p/~p/,~n", [misc:time_format(now()), State#robot.acid, State#robot.id, _Reason]), + {stop, normal, State}; + +handle_info(close, State) -> + gen_tcp:close(State#robot.socket), + {noreply, State}; + +%%处理模块发到某个模块的消息 +handle_info({Mod, Msg}, State) -> + case lists:member(Mod, ?RANDOM_MODULE) of + true -> + Module = list_to_atom(lists:concat(["robot_", Mod])), + case catch Module:handle_info(Msg, State) of + {noreply, NewState} when is_record(NewState, robot) -> + NewState; + _ -> + State + end; + false -> + ?TRACE("Error msg call: Mod:~p Msg: ~p~n", [Mod, Msg]), + State + end, + {noreply, State}; + +handle_info(_Info, State) -> + {noreply, State}. + +%% -------------------------------------------------------------------- +%% Function: terminate/2 +%% Description: Shutdown the server +%% Returns: any (ignored by gen_server) +%% -------------------------------------------------------------------- +terminate(_Reason, State) -> + ?TRACE(" ----------terminate-----------~s_quit_4: /~p/~p/~p/,~n", [misc:time_format(now()), State#robot.acid, State#robot.id, _Reason]), + if is_port(State#robot.socket) -> + gen_tcp:close(State#robot.socket); + true -> no_socket + end, + ok. + +%% -------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Convert process state when code is changed +%% Returns: {ok, NewState} +%% -------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%========================================================================= +%% 业务处理函数 +%%========================================================================= +%%登录游戏服务器 +login(N, Pid, Port, IP) -> + case connect_server(IP, Port) of + {ok, Socket} -> + ?TRACE("~s ---connect to IP:~p Port: ~p ok...~n", [misc:time_format(now()), IP, Port]), + Accid = N, + AccName = "ROBOT" ++ integer_to_list(Accid), + handle(login, {Accid, AccName}, Socket), + spawn_link(fun() -> do_parse_packet(Socket, Pid) end), + {ok, Socket}; + _Reason2 -> + ?TRACE("Connect to server failed: ~p~n", [_Reason2]), + error + end. + +%%连接服务端 +connect_server(Ip, Port) -> + gen_tcp:connect(Ip, Port, ?TCP_OPTS, 10000). + +%% 接受信息 +async_recv(Sock, Length, Timeout) when is_port(Sock) -> + case prim_inet:async_recv(Sock, Length, Timeout) of + {error, Reason} -> throw({Reason}); + {ok, Res} -> Res; + Res -> Res + end. + +%%接收来自服务器的数据 - 登陆后进入游戏逻辑 +%%Socket:socket id +%%Client: client记录 +do_parse_packet(Socket, Pid) -> + Ref = async_recv(Socket, ?HEADER_LENGTH, ?HEART_TIMEOUT), + receive + {inet_async, Socket, Ref, {ok, <>}} -> + ?TRACE("receive command: ~p, length: ~p~n", [Cmd, Len]), + BodyLen = Len - ?HEADER_LENGTH, + RecvData = + case BodyLen > 0 of + true -> + Ref1 = async_recv(Socket, BodyLen, ?TCP_TIMEOUT), + receive + {inet_async, Socket, Ref1, {ok, Binary}} -> + {ok, Binary}; + Other -> + ?TRACE("Data recv Error: ~p~n", [Other]), + {fail, Other} + end; + false -> + {ok, <<>>} + end, + case RecvData of + {ok, BinData} -> + case Cmd of + 10000 -> + <> = BinData, + case Code of + 0 -> + gen_server:cast(Pid, {login_ok, 0}), + <<_:32, PlayerId:64, _Bin2/binary>> = _Bin1, + handle(enter_player, {PlayerId}, Socket), + ok; + 1 -> + <> = _Bin1, + gen_server:cast(Pid, {login_ok, 1}), + handle(select_role, Accid, Socket), + ok; + _ -> + gen_server:cast(Pid, login_failed), + ?TRACE("login failed: Code: ~p~n", [Code]), + failed + end; + 10003 -> + <> = BinData, + ?TRACE("10003: Code: ~p PlayerId~p~n", [Code, PlayerId]), + if Code =:= 1 -> + handle(enter_player, {PlayerId}, Socket), + gen_server:cast(Pid, {playerid, PlayerId}); + true -> + gen_server:cast(Pid, {stop}) + end; + 10004 -> + <> = BinData, + if + Code =/= 0 -> + %% 选择玩家信息以进入场景 + gen_server:cast(Pid, {get_state_13001}); + true -> + gen_server:cast(Pid, {stop}) + end; + 13001 -> + ?TRACE("hahhaha ~n", []), + NewData = zlib:uncompress(BinData), + <<_Uid:64, _Gender:8, _Level:8, _Career:8, _Speed:8, SceneId:16, X:8, Y:8, Hp:32, _Other/binary>> = NewData, + %%更新信息 + + gen_server:cast(Pid, {upgrade_state_13001, [SceneId, X, Y, Hp]}), + ok; + 12001 -> + <> = BinData, + %% io:format("========receive 12001:~p ~n", [SceneId]) , + if + SceneId > 0 -> + gen_tcp:send(Socket, pack(12005, <<>>)), + %% 在场景中走路 + gen_server:cast(Pid, enter_ok); + true -> + gen_server:cast(Pid, {stop}) + end, + ok; + 12002 -> %% 更新场景怪物 + NewData = zlib:uncompress(BinData), + State = gen_server:call(Pid, {get_state}), + robot_battle:reflesh_monster(State#robot.acid, NewData); + 20003 -> %%人物被攻击 + ?TRACE("==20003 ~p~n", [BinData]), + <<_Id1:32, _Hp1:32, _Mp1:32, _Sid1:32, _Slv1:8, _X1:8, _Y1:8, _:32, DLen:16, TarBin/binary>> = BinData, + gen_server:cast(Pid, {after_fight, DLen, TarBin}), + ok; + 21000 -> + ?TRACE("==21000 ~p~n", [BinData]), + NewData = zlib:uncompress(BinData), + <<_:16, SkillList_ABin/binary>> = NewData, + gen_server:cast(Pid, {init_skill_list, SkillList_ABin}); + 12021 -> + <> = BinData, + case Code of + 1 -> + gen_server:cast(Pid, {upgrade_state_revive, [NewSceneId, ReviveX, ReviveY]}); + _ -> + gen_server:cast(Pid, {stop}) + end, + ok; + 10007 -> + <<_Code:8>> = BinData, + ?TRACE("==10007 ~p~n", [_Code]), + ok; + 30006 -> + NewData = zlib:uncompress(BinData), + <> = NewData, + TaskList = robot_task:parse_task_data(Data, []), + gen_server:call(Pid, {upgrade_state_30006, TaskList}), + ok; + + _Chat when _Chat >= 11000 andalso _Chat < 12000 -> + skip; + %% robot_chat:do_parse_packet(Socket, Pid, Cmd, BinData); + _Guild when _Guild >= 40000 andalso _Guild < 41000 -> + ?TRACE("==_Guild= ~p~n", [_Guild]), + robot_guild:do_parse_packet(Socket, Pid, Cmd, BinData); + %no_action + 13021 ->%%修改机器人帮派属性 + <> = BinData, + {GuildName, _GuildName_DoneBin} = pt:read_string(_GuildName_RestBin), + <> = _GuildName_DoneBin, + gen_server:cast(Pid, {refresh_robot_guild_state, [GuildId, Position]}); + + 15002 -> + io:format("******20130916 robot recv 15002 ~n"), + gen_server:cast(Pid, {get_bag_list, [0]}); + _ -> + no_action + end, + do_parse_packet(Socket, Pid); + {fail, _} -> + [{1, Num}] = ets:lookup(off_line_static, 1), + NewNum = Num + 1, + ets:insert(off_line_static, {1, NewNum}), + ?TRACE("do_parse_packet total off ~p recv data failed:/~p/~p/~n~p~n", [NewNum, Socket, Pid, RecvData]), + gen_tcp:close(Socket), + gen_server:cast(Pid, {stop, socket_error_1}) + end; + %%超时处理 + {inet_async, Socket, Ref, {error, timeout}} -> + io:format("do_parse_packet timeout:/~p/~p/~n", [Socket, Pid]), + do_parse_packet(Socket, Pid); + %%用户断开连接或出错 + Reason -> + [{1, Num}] = ets:lookup(off_line_static, 1), + NewNum = Num + 1, + ets:insert(off_line_static, {1, NewNum}), + io:format("do_parse_packet: total off ~p Error Reason:/~p/~p/~n", [NewNum, Socket, Reason]), + gen_tcp:close(Socket), + gen_server:cast(Pid, {stop, socket_error_3}) + end. + +%% 随机事件处理 +handle_action_random(State) -> + Actions = ?RANDOM_MODULE, + if Actions =/= [] -> + Action = lists:nth(random:uniform(length(Actions)), Actions), + Module = list_to_atom(lists:concat(["robot_", Action])), + case catch Module:handle(State) of + NewState when is_record(NewState, robot) -> + NewState; + _Error -> + io:format("ERROR: ~p~n", [_Error]), + State + end; + true -> + State + end. + +%%游戏相关操作%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%心跳包 +handle(heart, _, Socket) -> + case gen_tcp:send(Socket, pack(10006, <<>>)) of + ok -> + sleep(24 * 1000), + handle(heart, a, Socket); + _ -> + error + end; + +%%登陆 +handle(login, {Accid, AccName}, Socket) -> + ?TRACE("sending login request entry socket: ~p ~p ~p~n", [Accid, AccName, Socket]), + AccStamp = 1273027133, + Tick = integer_to_list(Accid) ++ AccName ++ integer_to_list(AccStamp) ++ ?TICKET, + TickMd5 = util:md5(Tick), + TickMd5Bin = list_to_binary(TickMd5), + TLen = byte_size(TickMd5Bin), + AccNameLen = byte_size(list_to_binary(AccName)), + AccNameBin = list_to_binary(AccName), + Data = <<9999:16, Accid:32, AccStamp:32, AccNameLen:16, AccNameBin/binary, TLen:16, TickMd5Bin/binary>>, + ?TRACE("sending login request: ~p ~p~n", [Accid, AccName]), + gen_tcp:send(Socket, pack(10000, Data)), + ok; + +%%选择角色进入 +handle(select_role, Accid, Socket) -> + NickName = "GUEST" ++ integer_to_list(Accid), + NameBin = list_to_binary(NickName), + TLen = byte_size(NameBin), + random:seed(erlang:now()), + Gender = random:uniform(2), + Career = random:uniform(3), + StrOsVersion = pt:pack_string("2.3.4"), + Device = pt:pack_string("test"), + Screen = pt:pack_string("test"), + gen_tcp:send(Socket, pack(10003, <<9999:16, Career:8, Gender:8, TLen:16, NameBin/binary, 0:8, StrOsVersion/binary, Device/binary, 0:8, 0:8, 0:8, Screen/binary>>)), + ok; + + +%%选择角色进入 +handle(enter_player, {PlayerId}, Socket) -> + StrOsVersion = pt:pack_string("2.3.4"), + Device = pt:pack_string("test"), + Screen = pt:pack_string("test"), + %0:8,StrOsVersion/binary,Device/binary,0:8,Screen/binary,0:8,0:8 + gen_tcp:send(Socket, pack(10004, <<9999:16, PlayerId:64, 30:8, 20:8, 0:8, StrOsVersion/binary, Device/binary, 0:8, 0:8, 0:8, Screen/binary>>)), + ok; + +%%跑步 +handle(run, {DestX, DestY}, Socket) -> + gen_tcp:send(Socket, pack(12011, <>)); + +%%跑步 +handle(broad_path, {DestX, DestY, Path}, Socket) -> + Len = length(Path), + Fun = fun({X, Y}) -> + <> + end, + MoveBin = tool:to_binary([Fun(M) || M <- Path]), + gen_tcp:send(Socket, pack(12010, <>)); + +%% %%ai模式跑步 +%% handle(run, {X,Y, SX, SY}, Socket) -> +%% ?TRACE("----running:[~p][~p]~n",[X,Y]), +%% gen_tcp:send(Socket, pack(12001, <>)); + +%%进入场景 +handle(enter_scene, [SceneId, Posx, Posy], Socket) -> +%% Posx = random:uniform(30) , +%% Posy = random:uniform(20) , + %%io:format("========handle(enter_scene:~p ~n", [SceneId]) , + gen_tcp:send(Socket, pack(12001, <>)); + +%%静止 +handle(undefined, a, _Socket) -> + ok; + +%%获取其他玩家信息 +handle(get_player_info, Id, Socket) -> + gen_tcp:send(Socket, pack(13004, <>)); + +%%获取自己信息 +handle(get_self_info, _, Socket) -> + ?TRACE("get_self_info: sending 13001~n"), + gen_tcp:send(Socket, pack(13001, <<>>)); + +%%原地复活 +handle(revive, _, Socket) -> +%% gen_tcp:send(Socket, pack(20004, <<3:8>>)), +%% Action = tool:to_binary("-加血 100000"), +%% ActionLen= byte_size(Action), +%% Data = <>, +%% Packet = pack(11020, Data), +%% gen_tcp:send(Socket, Packet); + io:format("====handle(revive ~p~n", [revive]), + gen_tcp:send(Socket, pack(12021, <<0:16>>)); + +handle(_Handle, _Data, Socket) -> + ?TRACE("handle error: /~p/~p/~n", [_Handle, _Data]), + {reply, handle_no_match, Socket}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%辅助函数 +%%读取字符串 +read_string(Bin) -> + case Bin of + <> -> + case Bin1 of + <> -> + {binary_to_list(Str), Rest}; + _R1 -> + {[], <<>>} + end; + _R1 -> + {[], <<>>} + end. + +random_sleep(T) -> + N = random:uniform(T), + timer:sleep(N * 100). + + +sleep(T) -> + receive + after T -> ok + end. + +for(Max, Max, _F) -> + []; +for(Min, Max, F) -> + [F(Min) | for(Min + 1, Max, F)]. + +for(Max, Max, _F, X) -> + X; +for(Min, Max, F, X) -> + F(X), + for(Min + 1, Max, F, X). + +sleep_send({T, S}) -> + receive + after T -> handle(run, a, S) + end. + +get_pid(Name) -> + case whereis(Name) of + undefined -> + err; + Pid -> Pid + end. + +ping(Node) -> + case net_adm:ping(Node) of + pang -> + ?TRACE("ping ~p error.~n", [Node]); + pong -> + ?TRACE("ping ~p success.~n", [Node]); + _Error -> + ?TRACE("error: ~p ~n", [_Error]) + end. + +get_robot_status(0, _TargetBin, DataList) -> + DataList; +get_robot_status(Len, TargetBin, DataList) -> + <<_:8, UId:64, CurHp:32, _:32, _:32, _:32, _:8, OtherBin/binary>> = TargetBin, + NewDataList = DataList ++ [{UId, CurHp}], + get_robot_status(Len - 1, OtherBin, NewDataList). + + +%%根据机器人状态进行动作 +ai(Pid) -> + %%更新信息 +%% gen_server:cast(Pid,{get_state_13001}), + Random_interval = random:uniform(1000) + 100, + sleep(Random_interval), + State = gen_server:call(Pid, {get_state}), + io:format("========ai(Pid):~p ~p~n", [State#robot.act, State#robot.status]), + case State#robot.act of + run -> + case State#robot.status of + standing -> + io:format("====ai(Pid)standing ~p~n", [standing]), + State2 = robot_battle:stand_call_back(State), + gen_server:cast(State2#robot.pid, {upgrade_state, State2}), + sleep(800); + running -> + io:format("====ai(Pid)running ~p~n", [running]), + if State#robot.step =/= [] -> %%当前坐标不等于目的坐标 + [{NextX, NextY} | LeftPath] = State#robot.step, + handle(run, {NextX, NextY}, State#robot.socket), + State2 = State#robot{x = NextX, y = NextY, step = LeftPath, status = running}, + gen_server:cast(State#robot.pid, {upgrade_state, State2}); + true -> + State2 = State#robot{status = standing}, %%到达目的地, 换个状态为站 + gen_server:cast(State#robot.pid, {upgrade_state, State2}) %%更新机器人状态 + end; + dead -> + %io:format("====ai(Pid)dead ~p~n",[dead]) , + handle(revive, a, State#robot.socket); %%让其复活 + fighting -> + %io:format("====ai(Pid)fighting ~p~n",[fighting]) , + robot_battle:begin_attrack(State), + sleep(800); + _ -> + ?TRACE("robot status error!~n") + end, + ai(Pid); + test_goods -> + case ?START_ROBOT_GOODS of + true -> + io:format("***********20130916 test_goods ~n"), + robot_goods:start_robot_test(State), + sleep(800), + ai(Pid); + _ -> + ok + end; + do_task -> + TargetTask = robot_task:get_rand_taskPid(State#robot.task_list), + %%RandTid = rand(1,?MAX_TASK_NUM), + if + is_record(TargetTask, task_list) -> + RandAction = rand(1, 4), + if + RandAction =:= 1 -> + robot_task:accept_task(State#robot.socket, TargetTask#task_list.id); + RandAction =:= 2 -> + robot_task:finish_task(State#robot.socket, rand(1, ?MAX_TASK_NUM)); + RandAction =:= 3 -> + robot_task:submit_task(State#robot.socket, TargetTask#task_list.id); + true -> + robot_task:handle(get_task, {}, State#robot.socket)%%获取任务列表 + end; + true -> + skip + end, + ai(Pid); + chat -> + {X, Y, Z} = erlang:now(), + LastChatTime = get(last_chat_time), + if + LastChatTime == undefined -> + IsHandle = true; + true -> + io:format("Y = ~p LastChatTime = ~p~n", [Y, LastChatTime]), + if + Y - LastChatTime > 5 -> + IsHandle = true; + true -> + IsHandle = false + end + end, + + if + IsHandle == true -> + robot_chat:handle(State), + put(last_chat_time, Y); + true -> + skip + end, + ai(Pid); + mail -> + robot_mail:handle(State), + ai(Pid); + mount -> + skip, + ai(Pid); + openfunc -> + skip, + ai(Pid); + newbie -> + skip, + ai(Pid); + _ -> + ok + end. + +pack(Cmd, Data) -> + L = byte_size(Data) + ?HEADER_LENGTH, + <>. + + +rand(Same, Same) -> Same; +rand(Min, Max) -> + M = Min - 1, + if + Max - M =< 0 -> + 0; + true -> + random:uniform(Max - M) + M + end. + + +%%@spec 获取怪物追击路径 +make_move_path(StartX, StartY, EndX, EndY, Path) -> + if + StartX =:= EndX andalso StartY =:= EndY -> + Path; + StartX =:= EndX -> + NextX = StartX, + NextY = make_next_step(StartY, EndY), + NewPath = Path ++ [{NextX, NextY}], + make_move_path(NextX, NextY, EndX, EndY, NewPath); + StartY =:= EndY -> + NextX = make_next_step(StartX, EndX), + NextY = EndY, + NewPath = Path ++ [{NextX, NextY}], + make_move_path(NextX, NextY, EndX, EndY, NewPath); + true -> + NextX = make_next_step(StartX, EndX), + NextY = make_next_step(StartY, EndY), + NewPath = Path ++ [{NextX, NextY}], + make_move_path(NextX, NextY, EndX, EndY, NewPath) + end. +make_next_step(Current, Target) -> + if Current > Target -> + if Current - Target > 1 -> + Current - 1; + true -> + Target + end; + true -> + if Target - Current > 1 -> + Current + 1; + true -> + Target + end + end. + +rand(Min) when Min =< 0 -> + 0; +rand(Max) -> + case get("rand_seed") of + undefined -> + RandSeed = now(), + random:seed(RandSeed), + put("rand_seed", RandSeed); + _ -> skip + end, + random:uniform(Max). + diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/robot.hrl b/src/srvNodeMgr/tools/gameWorld/test/robot/robot.hrl new file mode 100644 index 0000000..e149ef0 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/robot.hrl @@ -0,0 +1,103 @@ +%%% ------------------------------------------------------------------- +%%% Author: +%%% Description : +%%% Created : +%%% ------------------------------------------------------------------- +-include("common.hrl"). +-include("record.hrl"). + +-include_lib("stdlib/include/ms_transform.hrl"). +-define(CONFIG_FILE, "../config/gateway.config"). + +%%连接服务器端口 +-define(GATEWAY_ADD, "192.168.43.135"). +-define(GATEWAY_PORT, 7777). +%% -define(SERVER_ADD,"192.168.51.175"). +%% -define(SERVER_PORT,7777). +-define(SERVER_ADD, "192.168.43.135"). +-define(SERVER_PORT, 7788). + +-define(ACTION_SPEED_CONTROL, 10). +-define(ACTION_INTERVAL, ?ACTION_SPEED_CONTROL * 1000). % 自动行为最大时间间隔 +-define(ACTION_MIN, 3000). % 自动行为最小时间间隔 + +%%需要随机调用模块列表如robot_chat, +-define(RANDOM_MODULE, [guild]). +-define(RANDOM_INTERVAL, 1000). %%随机操作触发的间隔(毫秒) +-define(MAX_TASK_NUM, 200). %% 任务的个数 + +%%设为1, 登录后发GM指令加钱等 +-define(INITIAL_GM, 1). + +%%TCP Socket的参数 +-define(TCP_OPTS, [ + binary, + {packet, 0}, % no packaging + {reuseaddr, true}, % allow rebind without waiting + {nodelay, false}, + {delay_send, true}, + {active, false}, + {exit_on_close, false} +]). + +%%断言以及打印调试信息宏 +%%不需要时启用 -undefine行 +%% -define(debug,1). +%-undefine(debug). +-ifdef(debug). +-define(TRACE(Str), io:format(Str)). +-define(TRACE(Str, Args), io:format(Str, Args)). +% unicode版 +-define(TRACE_W(Str), io:format("~ts", [list_to_binary(io_lib:format(Str, []))])). +-define(TRACE_W(Str, Args), io:format("~ts", [list_to_binary(io_lib:format(Str, Args))])). +-else. +-define(TRACE(Str), void). +-define(TRACE(Str, Args), void). + +-define(TRACE_W(Str), void). +-define(TRACE_W(Str, Args), void). +-endif. + + +%%机器进程状态 +-record(robot, { + orig_n, + login, + acid, %%account id + accname, %%account id + socket, %%socket + pid, %%process id + rpid, + count = 0, + x, %%x坐标 + y, %%y坐标 + scene, + tox, + toy, + hp, + id, %% ID + act, %% 动作 + status, %% 当前状态 + dstScene, + skill_list = [], + attr_target = 0, + step, + guild, %%0不在帮派中 + guild_post, %%1帮主 + frda, %% 好友信息 + bkda, %% 黑名单信息, + sgda, %% 陌生人信息 + task_list, + task_cd = 0, + move_cd = 0, + skill_cd = 0, + completeId = 0 +}). + +-record(task_list, { + id, + taskId, + state, + mark, + grade +}). diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/robot_battle.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_battle.erl new file mode 100644 index 0000000..7a577a3 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_battle.erl @@ -0,0 +1,131 @@ +%% @author Administrator +%% @doc @todo Add description to robot_battle. +-module(robot_battle). +-include("robot.hrl"). +%% ==================================================================== +%% API functions +%% ==================================================================== +-compile(export_all). + + +%-------------------------- +% 逻辑处理 +%-------------------------- +%初始化玩家技能 +make_skill_list(<>, List) -> + make_skill_list(Rest, [SkillId | List]); +make_skill_list(<<>>, List) -> + List. +%获取周围怪物信息 +reflesh_monster(AId, BinData) -> + {ok, [_, _, MonList, _]} = read(BinData), + NewMonsterList = lists:map(fun(MonItem) -> + [MonId, _, PosX, PosY, _, _, CurHp, MaxHp, _, _, _] = MonItem, + {MonId, PosX, PosY, CurHp, MaxHp} + end + , MonList), + ets:insert(player_mon_info, {AId, NewMonsterList}). +%%获取默认路径 +make_default_path(State) -> + random:seed(erlang:now()), + DestX = State#robot.x + random:uniform(10) - 3, + DestY = State#robot.y + random:uniform(10) - 3, + Path = robot:make_move_path(State#robot.x, State#robot.y, DestX, DestY, []), + robot:handle(broad_path, {DestX, DestY, Path ++ [{DestX, DestY}]}, State#robot.socket), + State#robot{tox = DestX, toy = DestY, step = Path, status = running}. +%%获取到目标怪物的路径 +make_battle_path(State) -> + case ets:lookup(player_mon_info, State#robot.acid) of + [] -> + make_default_path(State); + [{_, []}] -> + make_default_path(State); + [{_, MonsterLists}] -> + Len = length(MonsterLists), + random:seed(erlang:now()), + Index = random:uniform(Len), + MonInfo = lists:nth(Index, MonsterLists), + {MonId, PosX, PosY, _, _} = MonInfo, + DestX = PosX + random:uniform(5), + DestY = PosY + random:uniform(5), + Path = robot:make_move_path(State#robot.x, State#robot.y, DestX, DestY, []), + robot:handle(broad_path, {DestX, DestY, Path ++ [{DestX, DestY}]}, State#robot.socket), + State#robot{tox = DestX, toy = DestY, step = Path, status = running, attr_target = MonId} + end. +%%漫游完成后回调 +stand_call_back(State) -> + case State#robot.attr_target of + 0 -> + make_battle_path(State); + TargetId -> + case ets:lookup(player_mon_info, State#robot.acid) of + [] -> + make_battle_path(State); + [{_, List}] -> + case lists:keyfind(TargetId, 1, List) of + {_, PosX, PosY, CurHp, _} -> + random:seed(erlang:now()), + Flag = random:uniform(10), + if CurHp > 0 andalso abs(State#robot.x - PosX) =< 2 andalso abs(State#robot.y - PosY) =< 2 andalso Flag > 7 -> + begin_attrack(State), + State#robot{status = fighting}; + true -> + make_battle_path(State#robot{attr_target = 0}) + end; + _ -> + make_battle_path(State#robot{attr_target = 0}) + end + end + end. +%%攻击逻辑 +begin_attrack(State) -> + SkillId = get_random_skill(State), + MonId = State#robot.attr_target, + gen_tcp:send(State#robot.socket, robot:pack(21003, <>)). +%%随机使用技能 +get_random_skill(State) -> + case State#robot.skill_list of + [] -> + 0; + List -> + Len = length(List), + Index = random:uniform(Len), + lists:nth(Index, List) + end. + +%% ==================================================================== +%% 相关协议解析 +%% ==================================================================== +%%解析12002包 +read(<>) -> + <> = BinData, + Fun_PlayerList = fun(_Idx, {RestBin, ResultList}) -> + <> = RestBin, + {NmBin, _NmBin_DoneBin} = pt:read_string(_NmBin_RestBin), + <> = _NmBin_DoneBin, + {PetName, _PetName_DoneBin} = pt:read_string(_PetName_RestBin), + <> = _PetName_DoneBin, + {_, _PlayerList_RestBin} = pt:read_string(GulidRestBin), + {_PlayerList_RestBin, [[PosX, PosY, UId, NmBin, Stts, Sex, Crr, CurHp, MaxHp, Magic, MagicMax, Weapon, Armor, Fashion, WwaponAcc, Wing, Mount, WeaponStrenLv, ArmorStrenLv, FashionStrenLv, WaponAccStrenLv, WingStrenLv, PetStatus, PetQualityLv, PetFacade, PetName, Level] | ResultList]} + end, + {_PlayerList_DoneBin, PlayerList} = lists:foldl(Fun_PlayerList, {PlayerListBin, []}, lists:seq(1, PlayerListLen)), + <> = _PlayerList_DoneBin, + Fun_MonList = fun(_Idx, {RestBin, ResultList}) -> + <> = RestBin, + <> = _BuffList_RestBin, + Fun_BuffList = fun(_Idx, {RestBin1, ResultList}) -> + <> = RestBin1, + {_BuffList_RestBin, [[BuffId, ExpirTime] | ResultList]} + end, + {_BuffList_DoneBin, BuffList} = lists:foldl(Fun_BuffList, {BuffListBin, []}, lists:seq(1, BuffListLen)), + {_BuffList_DoneBin, [[MonId, MonTId, PosX, PosY, Towards, Stts, CurHp, MaxHp, Magic, MagicMax, lists:reverse(BuffList)] | ResultList]} + end, + {_MonList_DoneBin, MonList} = lists:foldl(Fun_MonList, {MonListBin, []}, lists:seq(1, MonListLen)), + <> = _MonList_DoneBin, + Fun_DropList = fun(_Idx, {RestBin2, ResultList}) -> + <> = RestBin2, + {_DropList_RestBin, [[DropId, MonId, GoodsId, GoodsNum, DropX, DropY, EftTime] | ResultList]} + end, + {_DropList_DoneBin, DropList} = lists:foldl(Fun_DropList, {DropListBin, []}, lists:seq(1, DropListLen)), + {ok, [ScenedId, lists:reverse(PlayerList), lists:reverse(MonList), lists:reverse(DropList)]}. \ No newline at end of file diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/robot_chat.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_chat.erl new file mode 100644 index 0000000..30019a3 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_chat.erl @@ -0,0 +1,91 @@ +-module(robot_chat). +-compile(export_all). + +%-include("common.hrl"). +-include("robot.hrl"). + +%%---------------------- 自动聊天机数据 ---------------------- +-define(AUTO_CHAT_LIST, [ + <<"伟大的中国共产党">>, + <<"跟随失败,队长不能自己跟随自己.">>, + <<"石家庄在下雪">>, + <<"是鹅毛大雪">>, + <<"像是宰了一群鹅">>, + <<"拔了好多鹅毛">>, + <<"也不装进袋子里">>, + <<"像是羽绒服破了">>, + <<"也不缝上">>, + <<"北京也在下雪">>, + <<"不是鹅毛大雪">>, + <<"是白沙粒">>, + <<"有些像白砂糖">>, + <<"有些像碘盐">>, + <<"廊坊夹在石家庄和北京之间">>, + <<"廊坊什么雪也不下">>, + <<"看不到鹅毛">>, + <<"也看不到白砂糖和碘盐">>, + <<"廊坊只管阴着天">>, + <<"像一个女人吊着脸">>, + <<"说话尖酸、刻薄">>, + <<"还冷飕飕的">>, + <<"汉皇①重色思倾国,御宇②多年求不得。杨家有女初长成,养在深闺人未识。">>, + <<"天生丽质难自弃,一朝选在君王侧。回眸一笑百媚生,六宫粉黛无颜色。 ">>, + <<"春寒赐浴华清池,温泉水滑洗凝脂。侍儿扶起娇无力,始是新承恩泽时。 ">>, + <<"云鬓花颜金步摇,芙蓉帐暖度春宵。春宵苦短日高起,从此君王不早朝。 ">>, + <<"承欢侍宴无闲暇,春从春游夜专夜。 后宫佳丽三千人,三千宠爱在一身。 ">>, + <<"金屋妆成娇侍夜,玉楼宴罢醉和春。姊妹弟兄皆列土,可怜光彩生门户③。">>, + <<"遂令天下父母心,不重生男重生女。骊宫高处入青云,仙乐风飘处处闻。 ">>, + <<"缓歌谩舞凝丝竹,尽日君王看不足。渔阳鼙鼓④动地来,惊破霓裳羽衣曲。">>, + <<"九重城阙烟尘生,千乘万骑西南行。翠华摇摇行复止,西出都门百余里。 ">>, + <<"六军不发无奈何,宛转蛾眉马前死。花钿委地无人收,翠翘金雀玉搔头。 ">>, + <<"君王掩面救不得,回看血泪相和流。黄埃散漫风萧索,云栈萦纡登剑阁。 ">>, + <<"峨嵋山下少人行,旌旗无光日色薄⑤。蜀江水碧蜀山青,圣主朝朝暮暮情。 ">>, + <<"行宫见月伤心色,夜雨闻铃肠断声。 天旋地转回龙驭,到此踌躇不能去。 ">>, + <<"马嵬坡下泥土中,不见玉颜空死处。君臣相顾尽沾衣,东望都门信⑥马归。">>, + <<"归来池苑皆依旧,太液芙蓉未央柳。芙蓉如面柳如眉,对此如何不泪垂。 ">>, + <<"春风桃李花开日,秋雨梧桐叶落时。 西宫南内多秋草,落叶满阶红不扫。 ">>, + <<"梨园弟子白发新,椒房阿监青娥老。夕殿萤飞思悄然,孤灯挑尽未成眠。 ">>, + <<"迟迟钟鼓初长夜,耿耿星河欲曙天。 鸳鸯瓦冷霜华重,翡翠衾寒谁与共。 ">>, + <<"悠悠生死别经年,魂魄不曾来入梦。 临邛道士鸿都客,能以精诚致魂魄。 ">>, + <<"为感君王辗转思,遂教方士殷勤觅。排空驭气奔如电,升天入地求之遍。 ">>, + <<"上穷碧落⑦下黄泉,两处茫茫皆不见。忽闻海上有仙山,山在虚无缥渺间。 ">>, + <<"楼阁玲珑五云起,其中绰约多仙子。中有一人字太真,雪肤花貌参差是。 ">>, + <<"金阙西厢叩玉扃⑧,转教小玉报双成。闻道汉家天子使,九华帐里梦魂惊。 ">>, + <<"揽衣推枕起徘徊,珠箔银屏迤逦开⑨。云鬓半偏新睡觉,花冠不整下堂来。 ">>, + <<"风吹仙袂飘飘举,犹似霓裳羽衣舞。玉容寂寞泪阑干⑩,梨花一枝春带雨。">>, + <<"含情凝睇谢君王,一别音容两渺茫。昭阳殿里恩爱绝,蓬莱宫中日月长。 ">>, + <<"回头下望人寰处,不见长安见尘雾。惟将旧物表深情,钿合金钗寄将去。 ">>, + <<"钗留一股合一扇,钗擘黄金合分钿。但教心似金钿坚,天上人间会相见。 ">>, + <<"临别殷勤重寄词,词中有誓两心知。 七月七日长生殿,夜半无人私语时。 ">>, + <<"在天愿作比翼鸟,在地愿为连理枝。 天长地久有时尽,此恨绵绵无绝期。">>, + <<"明年上国富春光">>, + <<"朝廷自昔选才良">>, + <<"时平空山老壮士">>, + <<"代言直似汉文章">>, + <<"生来自秀培来秀">>, + <<"日移花影上窗香">>, + <<"快意一时荷叶雨">>, + <<"乐来一顾遇孙阳">>, + <<"メールアドレス">>, + <<"バックアップファイルのパスを入力して下さい">>, + <<"項目を埋めて Jabber User を検索して下さい">>, + <<"ユーザー統計の取得">>, + <<"は提携が変更されたためキックされました">>, + <<"该点不可行走!">> +]). + + +handle(State) -> + io:format("chat handle"), + Cmds = [11001], + Chat_List = ?AUTO_CHAT_LIST, + Msg = tool:to_list(lists:nth(random:uniform(length(Chat_List)), Chat_List)), + Cmd = lists:nth(random:uniform(length(Cmds)), Cmds), + {ok, BinData} = ptr_11:write(Cmd, [1, Msg]), + gen_tcp:send(State#robot.socket, BinData), + State. + +do_parse_packet(_Socket, _Pid, Cmd, BinData) -> + {ok, _Result} = ptr_11:read(Cmd, BinData), + ?TRACE("Cmd: ~p, Result: ~p~n", [Cmd, _Result]). + diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/robot_gateway.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_gateway.erl new file mode 100644 index 0000000..8db6f3c --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_gateway.erl @@ -0,0 +1,589 @@ +%%% ------------------------------------------------------------------- +%%% Author: SMXX +%%% Description : 机器人 +%%% Created : +%%% ------------------------------------------------------------------- +-module(robot_gateway). +-behaviour(gen_server). +-include("robot.hrl"). + +-compile(export_all). +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). + +%%% +%%% API + +start() -> + start(20000, 10000), + ok. + +%%StartId 起始AccountID +%%Num int 数量 +%%Mod 跑步模式 1 ,2 +start(StartId, Num) -> + sleep(100), + F = fun(N) -> + io:format("start robot-~p~n", [N]), + sleep(200), + start_link(StartId + N) + end, + for(0, Num, F), + ok. + + +%%创建 一个ROBOT 进程 +start_link(N) -> + case gen_server:start(?MODULE, [N], []) of + {ok, _Pid} -> + io:format("--robot~p start finish!-~n", [N]); + %gen_server:cast(Pid, {start_action}); + _ -> + fail + end. + +%% -------------------------------------------------------------------- +%% Function: init/1 +%% Description: Initiates the server +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% -------------------------------------------------------------------- +%%初始化玩家数据 +init([N]) -> + process_flag(trap_exit, true), + Pid = self(), + Robot = #robot{login = 0, + acid = N, + id = 0, + pid = Pid + }, + erlang:send_after(10, self(), {'action'}), + %%登陆成功后开始动作 + {ok, Robot}. + + +%% -------------------------------------------------------------------- +%% Function: handle_call/3 +%% Description: Handling call messages +%% Returns: {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | (terminate/2 is called) +%% {stop, Reason, State} (terminate/2 is called) +%% -------------------------------------------------------------------- +handle_call({get_state}, _From, State) -> + {reply, State, State}; + +%%处理模块发到某个模块的消息 +handle_call({Mod, Msg}, _From, State) -> + case lists:member(Mod, ?RANDOM_MODULE) of + true -> + Module = list_to_atom(lists:concat(["robot_", Mod])), + case catch Module:handle_call(State, Msg) of + {reply, Reply, NewState} when is_record(NewState, robot) -> + {reply, Reply, NewState}; + _ -> + {reply, noreply, State} + end; + false -> + io:format("Error cast call: Mod:~p Msg: ~p~n", [Mod, Msg]), + {reply, error, State} + end; + +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. + +%% -------------------------------------------------------------------- +%% Function: handle_cast/2 +%% Description: Handling cast messages +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%% -------------------------------------------------------------------- +handle_cast({gateway_fallback}, State) -> + io:format("====gateway_fallback: ~p~n", [State#robot.pid]), + gen_tcp:close(State#robot.socket), + exit(State#robot.rpid, nomal), + erlang:send_after(1000, self(), {'action'}), + {noreply, State#robot{socket = []}}; + +handle_cast(login_failed, State) -> + io:format("login failed~n"), + {stop, normal, State}; + +handle_cast({playerid, Id}, State) -> + NewState = State#robot{id = Id}, + {noreply, NewState}; + +handle_cast(enter_ok, State) -> + NewState = State#robot{act = run, status = standing}, + gen_server:cast(self(), {start_action}), + {noreply, NewState}; + +handle_cast({after_fight, Len, TargetBin}, State) -> + DataList = get_robot_status(Len, TargetBin, []), + case lists:keyfind(State#robot.id, 1, DataList) of + {_, CurHp} -> + case CurHp > 0 of + true -> + NewState = State; + false -> + NewState = State#robot{status = dead} + end; + _ -> + NewState = State + end, + {noreply, NewState}; + + +handle_cast({upgrade_state, NewState}, _State) -> + {noreply, NewState}; + +handle_cast({get_state_13001}, State) -> + handle(get_self_info, a, State#robot.socket), + {noreply, State}; + +handle_cast({upgrade_state_13001, [Scene, X, Y]}, State) -> + NewState = State#robot{x = X, y = Y, scene = Scene}, + handle(enter_scene, [Scene], State#robot.socket), + {noreply, NewState}; + +handle_cast({upgrade_state_revive, []}, State) -> + NewState = State#robot{status = standing}, + {noreply, NewState}; + +handle_cast({upgrade_state_13099, [IdLists]}, State) -> + IdLists1 = [[State#robot.id] | IdLists], + NewState = State#robot{frda = IdLists1}, + {noreply, NewState}; + +handle_cast({run}, State) -> + State2 = State#robot{act = run}, + {noreply, State2}; + +handle_cast({stop}, State) -> + State2 = State#robot{act = undefined}, + {noreply, State2}; + +handle_cast({stop, _Reason}, State) -> + io:format("~s_quit_2: /~p/~p/~p/,~n", [misc:time_format(now()), State#robot.acid, State#robot.id, _Reason]), + {stop, normal, State}; + +%%处理模块发到某个模块的消息 +handle_cast({Mod, Msg}, State) -> + case lists:member(Mod, ?RANDOM_MODULE) of + true -> + Module = list_to_atom(lists:concat(["robot_", Mod])), + case catch Module:handle_cast(Msg, State) of + {noreply, NewState} when is_record(NewState, robot) -> + NewState; + _ -> + State + end; + false -> + io:format("Error cast call: Mod:~p Msg: ~p~n", [Mod, Msg]), + State + end, + {noreply, State}; + +handle_cast(_Msg, State) -> + {noreply, State}. + +%% -------------------------------------------------------------------- +%% Function: handle_info/2 +%% Description: Handling all non call/cast messages +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%% -------------------------------------------------------------------- +handle_info({'action'}, State) -> + case connect_server(?GATEWAY_ADD, ?GATEWAY_PORT) of + {ok, Socket} -> + Accid = State#robot.acid, + AccName = "ROBOT" ++ integer_to_list(Accid), + handle(login_gateway, {Accid, AccName}, Socket), + RPid = spawn_link(fun() -> do_parse_packet(Socket, State#robot.pid) end), + NewState = State#robot{socket = Socket, rpid = RPid}, + {ok, Socket}; + _Reason2 -> + NewState = State, + io:format("Connect to server failed: ~p~n", [_Reason2]), + error + end, + {noreply, NewState}; + + + +handle_info({random}, State) -> + NewState = handle_action_random(State), + erlang:send_after(?RANDOM_INTERVAL, self(), {random}), + {noreply, NewState}; + +handle_info({stop, _Reason}, State) -> + io:format("~s ------ robot stop: /~p/~p/~p/,~n", [misc:time_format(now()), State#robot.acid, State#robot.id, _Reason]), + {stop, normal, State}; + +handle_info(close, State) -> + gen_tcp:close(State#robot.socket), + {noreply, State}; + +%%处理模块发到某个模块的消息 +handle_info({Mod, Msg}, State) -> + case lists:member(Mod, ?RANDOM_MODULE) of + true -> + Module = list_to_atom(lists:concat(["robot_", Mod])), + case catch Module:handle_info(Msg, State) of + {noreply, NewState} when is_record(NewState, robot) -> + NewState; + _ -> + State + end; + false -> + io:format("Error msg call: Mod:~p Msg: ~p~n", [Mod, Msg]), + State + end, + {noreply, State}; + +handle_info(_Info, State) -> + {noreply, State}. + +%% -------------------------------------------------------------------- +%% Function: terminate/2 +%% Description: Shutdown the server +%% Returns: any (ignored by gen_server) +%% -------------------------------------------------------------------- +terminate(_Reason, State) -> + io:format(" ----------terminate-----------~s_quit_4: /~p/~p/~p/,~n", [misc:time_format(now()), State#robot.acid, State#robot.id, _Reason]), + if is_port(State#robot.socket) -> + gen_tcp:close(State#robot.socket); + true -> no_socket + end, + ok. + +%% -------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Convert process state when code is changed +%% Returns: {ok, NewState} +%% -------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%========================================================================= +%% 业务处理函数 +%%========================================================================= +%%登录游戏服务器 +login(N, Pid) -> + case connect_server(?GATEWAY_ADD, ?GATEWAY_PORT) of + {ok, Socket} -> + Accid = N, + AccName = "ROBOT" ++ integer_to_list(Accid), + handle(login_gateway, {Accid, AccName}, Socket), + spawn_link(fun() -> do_parse_packet(Socket, Pid) end), + {ok, Socket}; + _Reason2 -> + io:format("Connect to server failed: ~p~n", [_Reason2]), + error + end. + +%%连接服务端 +connect_server(Ip, Port) -> + gen_tcp:connect(Ip, Port, ?TCP_OPTS, 10000). + +%% 接受信息 +async_recv(Sock, Length, Timeout) when is_port(Sock) -> + case prim_inet:async_recv(Sock, Length, Timeout) of + {error, Reason} -> throw({Reason}); + {ok, Res} -> Res; + Res -> Res + end. + +%%接收来自服务器的数据 - 登陆后进入游戏逻辑 +%%Socket:socket id +%%Client: client记录 +do_parse_packet(Socket, Pid) -> + Ref = async_recv(Socket, ?HEADER_LENGTH, ?HEART_TIMEOUT), + receive + {inet_async, Socket, Ref, {ok, <>}} -> + BodyLen = Len - ?HEADER_LENGTH, + RecvData = + case BodyLen > 0 of + true -> + Ref1 = async_recv(Socket, BodyLen, ?TCP_TIMEOUT), + receive + {inet_async, Socket, Ref1, {ok, Binary}} -> + {ok, Binary}; + Other -> + io:format("Data recv Error: ~p~n", [Other]), + {fail, Other} + end; + false -> + {ok, <<>>} + end, + case RecvData of + {ok, _BinData} -> + case Cmd of + 60000 -> + gen_server:cast(Pid, {gateway_fallback}); + _ -> + io:format("do_parse_packet recv data failed:/~p/~p/~n~p~n", [Socket, Pid, RecvData]) + end; + + {fail, _} -> + io:format("do_parse_packet recv data failed:/~p/~p/~n~p~n", [Socket, Pid, RecvData]), + gen_tcp:close(Socket), + gen_server:cast(Pid, {stop, socket_error_1}) + end; + %%超时处理 + {inet_async, Socket, Ref, {error, timeout}} -> + io:format("do_parse_packet timeout:/~p/~p/~n", [Socket, Pid]), + do_parse_packet(Socket, Pid); + %%用户断开连接或出错 + Reason -> + io:format("do_parse_packet: Error Reason:/~p/~p/~n", [Socket, Reason]), + gen_tcp:close(Socket), + gen_server:cast(Pid, {stop, socket_error_3}) + end. + +%% 随机事件处理 +handle_action_random(State) -> + Actions = ?RANDOM_MODULE, + if Actions =/= [] -> + Action = lists:nth(random:uniform(length(Actions)), Actions), + Module = list_to_atom(lists:concat(["robot_", Action])), + case catch Module:handle(State) of + NewState when is_record(NewState, robot) -> + NewState; + _Error -> + io:format("ERROR: ~p~n", [_Error]), + State + end; + true -> + State + end. + +%%游戏相关操作%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%心跳包 +handle(heart, _, Socket) -> + case gen_tcp:send(Socket, pack(10006, <<>>)) of + ok -> + sleep(24 * 1000), + handle(heart, a, Socket); + _ -> + error + end; + + +%%连接网关 +handle(login_gateway, {Accid, AccName}, Socket) -> + io:format("======sending login_gateway : ~p ~p~n", [Accid, Socket]), + StrBin = tool:to_binary(AccName), + Len = byte_size(StrBin), + Data = <>, + gen_tcp:send(Socket, pack(60000, Data)), + ok; + + +%%选择角色进入 +handle(select_role, Accid, Socket) -> + NickName = "GUEST" ++ integer_to_list(Accid), + NameBin = list_to_binary(NickName), + TLen = byte_size(NameBin), + Gender = random:uniform(2), + Career = random:uniform(3), + gen_tcp:send(Socket, pack(10003, <<9999:16, Career:8, Gender:8, TLen:16, NameBin/binary>>)), + ok; + +%%选择角色进入 +handle(enter_player, {PlayerId}, Socket) -> +%% Posx = random:uniform(30) , +%% Posy = random:uniform(20) , + gen_tcp:send(Socket, pack(10004, <<9999:16, PlayerId:64, 30:8, 20:8>>)), + ok; + +%%跑步 +handle(run, {DestX, DestY}, Socket) -> + gen_tcp:send(Socket, pack(12011, <>)); + +%%跑步 +handle(broad_path, {DestX, DestY, Path}, Socket) -> + Len = length(Path), + Fun = fun({X, Y}) -> + <> + end, + MoveBin = tool:to_binary([Fun(M) || M <- Path]), + gen_tcp:send(Socket, pack(12010, <>)); + +%%ai模式跑步 +handle(run, {X, Y, SX, SY}, Socket) -> + io:format("----running:[~p][~p]~n", [X, Y]), + gen_tcp:send(Socket, pack(12001, <>)); + +%%进入场景 +handle(enter_scene, [SceneId], Socket) -> + Posx = random:uniform(30), + Posy = random:uniform(20), + gen_tcp:send(Socket, pack(12001, <>)); + +%%静止 +handle(undefined, a, _Socket) -> + ok; + +%%获取其他玩家信息 +handle(get_player_info, Id, Socket) -> + gen_tcp:send(Socket, pack(13004, <>)); + +%%获取自己信息 +handle(get_self_info, _, Socket) -> + io:format("get_self_info: sending 13001~n"), + gen_tcp:send(Socket, pack(13001, <<>>)); + +%%原地复活 +handle(revive, _, Socket) -> +%% gen_tcp:send(Socket, pack(20004, <<3:8>>)), +%% Action = tool:to_binary("-加血 100000"), +%% ActionLen= byte_size(Action), +%% Data = <>, +%% Packet = pack(11020, Data), +%% gen_tcp:send(Socket, Packet); + gen_tcp:send(Socket, pack(12020, <<>>)); + +handle(_Handle, _Data, Socket) -> + io:format("handle error: /~p/~p/~n", [_Handle, _Data]), + {reply, handle_no_match, Socket}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%辅助函数 +%%读取字符串 +read_string(Bin) -> + case Bin of + <> -> + case Bin1 of + <> -> + {binary_to_list(Str), Rest}; + _R1 -> + {[], <<>>} + end; + _R1 -> + {[], <<>>} + end. + +random_sleep(T) -> + N = random:uniform(T), + timer:sleep(N * 100). + + +sleep(T) -> + receive + after T -> ok + end. + +for(Max, Max, _F) -> + []; +for(Min, Max, F) -> + [F(Min) | for(Min + 1, Max, F)]. + +for(Max, Max, _F, X) -> + X; +for(Min, Max, F, X) -> + F(X), + for(Min + 1, Max, F, X). + +sleep_send({T, S}) -> + receive + after T -> handle(run, a, S) + end. + +get_pid(Name) -> + case whereis(Name) of + undefined -> + err; + Pid -> Pid + end. + +ping(Node) -> + case net_adm:ping(Node) of + pang -> + io:format("ping ~p error.~n", [Node]); + pong -> + io:format("ping ~p success.~n", [Node]); + _Error -> + io:format("error: ~p ~n", [_Error]) + end. + +get_robot_status(0, _TargetBin, DataList) -> + DataList; +get_robot_status(Len, TargetBin, DataList) -> + <<_:8, UId:64, CurHp:32, _:32, _:32, _:32, _:8, OtherBin/binary>> = TargetBin, + NewDataList = DataList ++ [{UId, CurHp}], + get_robot_status(Len - 1, OtherBin, NewDataList). + + + +pack(Cmd, Data) -> + L = byte_size(Data) + ?HEADER_LENGTH, + <>. + + +rand(Same, Same) -> Same; +rand(Min, Max) -> + M = Min - 1, + if + Max - M =< 0 -> + 0; + true -> + random:uniform(Max - M) + M + end. + + +%%@spec 获取怪物追击路径 +make_move_path(StartX, StartY, EndX, EndY, Path) -> + if + StartX =:= EndX andalso StartY =:= EndY -> + Path; + StartX =:= EndX -> + NextX = StartX, + NextY = make_next_step(StartY, EndY), + NewPath = Path ++ [{NextX, NextY}], + make_move_path(NextX, NextY, EndX, EndY, NewPath); + StartY =:= EndY -> + NextX = make_next_step(StartX, EndX), + NextY = EndY, + NewPath = Path ++ [{NextX, NextY}], + make_move_path(NextX, NextY, EndX, EndY, NewPath); + true -> + NextX = make_next_step(StartX, EndX), + NextY = make_next_step(StartY, EndY), + NewPath = Path ++ [{NextX, NextY}], + make_move_path(NextX, NextY, EndX, EndY, NewPath) + end. +make_next_step(Current, Target) -> + if Current > Target -> + if Current - Target > 1 -> + Current - 1; + true -> + Target + end; + true -> + if Target - Current > 1 -> + Current + 1; + true -> + Target + end + end. + +rand(Min) when Min =< 0 -> + 0; +rand(Max) -> + case get("rand_seed") of + undefined -> + RandSeed = now(), + random:seed(RandSeed), + put("rand_seed", RandSeed); + _ -> skip + end, + random:uniform(Max). + diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/robot_gm.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_gm.erl new file mode 100644 index 0000000..0a171ba --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_gm.erl @@ -0,0 +1,39 @@ +-module(robot_gm). +-compile(export_all). + +-include("robot.hrl"). + +%%断言以及打印调试信息宏 +%%不需要时启用 -undefine行 +%%-define(gm_debug, 1). +%-undefine(gm_debug). +-ifdef(gm_debug). +-define(MYTRACE(Str), io:format(Str)). +-define(MYTRACE(Str, Args), io:format(Str, Args)). +-else. +-define(MYTRACE(Str), void). +-define(MYTRACE(Str, Args), void). +-endif. + +%%---------------------- 初始帐号 ---------------------- +-define(AUTO_CHAT_LIST, [ + <<"-level 10">>, +%% <<"-coin 1000000">>, +%% <<"-bcoin 1000000">>, +%% <<"-gold 100000">>, +%% <<"-bgold 100000">>, + <<"-exp 10000000">> +]). + +handle(State) -> + F = fun(Msg) -> + {ok, BinData} = ptr_11:write(11005, [1, Msg]), + mysend(State#robot.socket, BinData) + end, + lists:foreach(F, ?AUTO_CHAT_LIST), + State. + +mysend(Socket, BinData) -> + <<_:16, _Cmd:16, _/binary>> = BinData, + ?MYTRACE("sending: cmd: ~p~n", [_Cmd]), + gen_tcp:send(Socket, BinData). diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/robot_goods.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_goods.erl new file mode 100644 index 0000000..fcb12c0 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_goods.erl @@ -0,0 +1,62 @@ +%% Author: Administrator +%% Created: 2013-9-16 +%% Description: TODO: Add description to robot_goods +-module(robot_goods). +-behaviour(gen_server). + +%% +%% Include files +%% +-include("robot.hrl"). +%% +%% Exported Functions +%% +-compile(export_all). +%% -export([]). + +%% +%% API Functions +%% + +%% 开始机器人逻辑,RS = RobotStatus #robot +start_robot_test(RS) -> + io:format("**********20130916 robot_goods start_robot_test~n"), + handle(15000, RS), + handle(15002, RS#robot.socket), + handle(15003, RS#robot.socket), + handle(15004, RS), + ok. + +%% 查询物品详细信息 +handle(15000, RS) -> + ?TRACE("**********20130916 robot_goods 15000 handle~n"), + Id = RS#robot.id, + gen_tcp:send(RS#robot.socket, pack(15000, <>)), + ok; + +%% 测试背包物品获取 +handle(15002, Socket) -> + ?TRACE("**********20130916 robot_goods 15002 handle~n"), + gen_tcp:send(Socket, pack(15002, <<0:8>>)), + ok; + +%% 扩充背包 +handle(15003, Socket) -> + ?TRACE("**********20130916 robot_goods 15003 handle~n"), + gen_tcp:send(Socket, pack(15003, <<0:8, 1:8>>)), + ok; + +%% 背包内拖动物品 +handle(15004, RS) -> + ?TRACE("**********20130916 robot_goods 15004 handle~n"), + gen_tcp:send(RS#robot.socket, pack(15004, <<123:64, 1:16, 2:16>>)), + ok. + + +%% +%% Local Functions +%% + +pack(Cmd, Data) -> + L = byte_size(Data) + ?HEADER_LENGTH, + <>. diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/robot_guild.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_guild.erl new file mode 100644 index 0000000..1ce307c --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_guild.erl @@ -0,0 +1,232 @@ +%%%--------------------------------------------- +%%% @Module : robot_guild +%%% @Author : smxx +%%% @Created : 2013.03.01 +%%% @Description: 客户端测试程序 +%%%--------------------------------------------- +-module(robot_guild). +-include("robot.hrl"). +-compile(export_all). + +%%断言以及打印调试信息宏 +%%不需要时启用 -undefine行 +-define(guild_debug, 1). +%-undefine(guild_debug). +-ifdef(guild_debug). +-define(MYTRACE(Str), io:format(Str)). +-define(MYTRACE(Str, Args), io:format(Str, Args)). +-else. +-define(MYTRACE(Str), void). +-define(MYTRACE(Str, Args), void). +-endif. + +%%Robot进程调用 (外部调用) +handle(State) -> + ?TRACE("guild handle : begin"), + if State#robot.guild =:= 0 -> %%没有帮派情况 + case robot:rand(100) of + Int when Int =< 90 -> %%大部分都是加入,不建 + Cmd = 40001; + _ -> + Cmd = 40002 + end, + case Cmd of + 40001 -> %查找帮派 -> + IsNotFull = robot:rand(100) rem 2, + IsSameGroup = robot:rand(100) rem 2, + {ok, BinData} = ptr_40:write(Cmd, [1, IsNotFull, IsSameGroup]); + 40002 -> %建一个帮派 + %%加建帮令 + GoodsId = 390004205, + Content = string:concat("-addgoods ", util:term_to_string(GoodsId)), + Content2 = string:concat(Content, " 1"), + ContentLen = length(Content2), + NewContent = list_to_binary(Content2), + gen_tcp:send(State#robot.socket, pack(11005, <<0:8, <>/binary>>)), + %%升级 + Level = 45, + LevelContent = string:concat("-level ", util:term_to_string(Level)), + LevelContentLen = length(LevelContent), + NewLevelContent = list_to_binary(LevelContent), + gen_tcp:send(State#robot.socket, pack(11005, <<0:8, <>/binary>>)), + + + GName = "Guild" ++ integer_to_list(State#robot.acid), + {ok, BinData} = ptr_40:write(Cmd, [GName, <<" ">>]) + end, + mysend(State#robot.socket, BinData), + State; + true -> + if State#robot.guild_post =:= 1 -> %帮主 + %%{ok, Bin} = ptr_40:write(40031, [0]), %%先处理帮派申请 + %% mysend(State#robot.socket, Bin), + ActionCmds = [40005, 40010, 40031, 40034], + Cmd = lists:nth(robot:rand(length(ActionCmds)), ActionCmds); + true -> + %ActionCmds = [40004, 40005, 40006], + case robot:rand(100) < 20 of + true -> + Cmd = 40004; + false -> + ActionCmds = [40005], + Cmd = lists:nth(robot:rand(length(ActionCmds)), ActionCmds) + end + end, + case Cmd of + 40004 -> %退出帮派 + {ok, BinData} = ptr_40:write(Cmd, [0]); + 40005 -> %查询帮派成员 + {ok, BinData} = ptr_40:write(Cmd, [0, 0]); +%% 40006 -> %弹劾帮主 +%% {ok, BinData} = ptr_40:write(Cmd, [0]); + 40010 ->%请求帮派信息 + {ok, BinData} = ptr_40:write(Cmd, [0, 0]); +%% 40011 -> %查看帮派成员的属性值 +%% +%% skip; + 40031 -> %申请列表 + {ok, BinData} = ptr_40:write(Cmd, [0]) +%% 40034 -> %解散帮派 +%% {ok, BinData} = ptr_40:write(Cmd, [0]) +%% _ -> +%% skip + end, + mysend(State#robot.socket, BinData), + State + end. + +%%Robot进程 +handle_cast({guild_create_ok}, State) -> + NewState = State#robot{guild = 1, guild_post = 1}, + {noreply, NewState}; + +%%随便加入帮派 +handle_cast({request_join, GuildId}, State) -> + if State#robot.guild =:= 0 -> + {ok, BinData} = ptr_40:write(40003, [GuildId]), + mysend(State#robot.socket, BinData); + true -> skip end, + {noreply, State}; + +handle_cast({join_guild_approve}, State) -> + NewState = State#robot{guild = 1, guild_post = 0}, + {noreply, NewState}; + +handle_cast({refresh_robot_guild_state, [GuildId, Position]}, State) -> + ?TRACE("refresh_robot_guild_state:GuildId=~p,Position=~p ", [GuildId, Position]), + NewState = State#robot{guild = GuildId, guild_post = Position}, + {noreply, NewState}; + +handle_cast({quit_guild}, State) -> + NewState = State#robot{guild = 0, guild_post = 0}, + {noreply, NewState}; + +%%帮主对成员的操作 +handle_cast({member, Uid}, State) -> + if State#robot.guild =:= 1 andalso State#robot.guild_post =:= 1 -> %帮主 + ActionCmds = [40033, 40035, 40037], + Cmd = lists:nth(robot:rand(length(ActionCmds)), ActionCmds), + case Cmd of + 40033 -> %任命副帮主(帮主操作) + Position = robot:rand(2) + 1, %2妇帮 3长老 + {ok, BinData} = ptr_40:write(Cmd, [Uid, Position]); + 40035 -> %踢出成员(帮主/副帮主) + {ok, BinData} = ptr_40:write(Cmd, [Uid]); + 40037 -> %帮主让位 + {ok, BinData} = ptr_40:write(Cmd, [Uid]) + end, + mysend(State#robot.socket, BinData); + true -> skip end, + {noreply, State}; + +%%成员的操作 +handle_cast({new_guild_chief, Uid}, State) -> + if State#robot.guild =:= 1 andalso State#robot.id =:= Uid -> %新帮主 + {noreply, State#robot{guild_post = 1}}; + true -> {noreply, State} + end; + +handle_cast(_, State) -> + {noreply, State}. + +%%在另一个进程中 +do_parse_packet(Socket, Pid, Cmd, BinData) ->%%模拟客户端收包 + %%?MYTRACE("do_parse_packet begin: Cmd: ~p, BinData: ~p~n", [Cmd, BinData]), + {ok, DecodeMsg} = ptr_40:read(Cmd, BinData), + %%?MYTRACE("Cmd: ~p, Result: ~p~n", [Cmd, DecodeMsg]), + case Cmd of + 40001 -> %%帮派列表 + [_, _, GuildList] = DecodeMsg, + if GuildList =/= [] -> + [[GuildId | _T1] | _T2] = GuildList, + gen_server:cast(Pid, {guild, {request_join, GuildId}}); + true -> skip end; + + 40002 -> %%建帮派成功 + [Result] = DecodeMsg, + if Result =:= 1 -> gen_server:cast(Pid, {guild, {guild_create_ok}}); + true -> skip end; + + 40003 -> %%加入帮派成功 + skip; + + 40004 -> %%退出帮派成功 + [Result] = DecodeMsg, + if Result =:= 1 -> gen_server:cast(Pid, {guild, {quit_guild}}); + true -> skip end; + + 40005 -> %成员列表 + [StCode | T] = DecodeMsg, + if StCode =:= 1 andalso length(T) >= 1 -> + [Uid | _] = lists:nth(robot:rand(length(T)), T), + gen_server:cast(Pid, {guild, {member, Uid}}); + true -> skip end; + + 40006 -> %%弹劾操作返回 + skip; + + 40007 -> %弹劾投票返回 + skip; + + 40008 -> %弹劾结果 + skip; + + 40031 -> %%申请列表 + F = fun(Apply) -> + [Uid | _T] = Apply, + Ops = robot:rand(2), + {ok, BinData} = ptr_40:write(40032, [Uid, Ops]), + mysend(Socket, BinData) + end, + lists:foreach(F, DecodeMsg); + + 40034 -> %%解散帮派(帮主) + [Result] = DecodeMsg, + if Result =:= 1 -> gen_server:cast(Pid, {guild, {quit_guild}}); + true -> skip end; + + 40070 -> %新成员 + [Uid | _] = DecodeMsg, + gen_server:cast(Pid, {guild, {member, Uid}}); + + 40071 -> %被踢了 + gen_server:cast(Pid, {guild, {quit_guild}}); + + 40074 -> %被让位了 + [_, _, Uid, _] = DecodeMsg, + gen_server:cast(Pid, {guild, {new_guild_chief, Uid}}); + + 40078 -> %通过加入帮派的申请 + gen_server:cast(Pid, {guild, {join_guild_approve}}); + _ -> skip + end. + + +mysend(Socket, BinData) -> + <<_:16, _Cmd:16, _/binary>> = BinData, + ?MYTRACE("sending: cmd: ~p~n", [_Cmd]), + gen_tcp:send(Socket, BinData).%%最后会给玩家进程 + +pack(Cmd, Data) -> + L = byte_size(Data) + ?HEADER_LENGTH, + <>. \ No newline at end of file diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/robot_mail.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_mail.erl new file mode 100644 index 0000000..0d39185 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_mail.erl @@ -0,0 +1,23 @@ +%%--------------------------------------------- +%% @Module : robot_mail +%% @Author : smxx +%% @Created : 2013.09.13 +%% @Description: 邮件功能测试客户端 +%%--------------------------------------------- +-module(robot_mail). +-include("robot.hrl"). +-compile(export_all). + +handle(Status) -> + %%因为发邮件不是用户请求协议触发的 所以要靠gm指令来压 + Cmd = 11005, + Type = 0, + Content = "-mail 1", + {ok, BinData} = ptr_11:write(Cmd, [0, Content]), + gen_tcp:send(Status#robot.socket, BinData), + Status. + + + + + diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/robot_market.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_market.erl new file mode 100644 index 0000000..c1fa580 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_market.erl @@ -0,0 +1,158 @@ +-module(robot_market). +-compile(export_all). + +%-include("common.hrl"). +-include("robot.hrl"). +-record(bag_list, { + id, + tid, + cell, + num, + stren, + strenPer, + bind +}). +-record(sale_list, { + saleId, + goodsUid, + goodsId, + leftTime, + num, + price +}). +-define(ADD_GOODS, 1). +-define(DO_SALE, 2). +-define(DO_BUY, 3). +-define(GOODS, [262035204]). +-define(ACTIONS, [?ADD_GOODS, ?DO_SALE, ?DO_BUY]). + +handle(State) -> + Cmds = [41001], + %%Chat_List = ?AUTO_CHAT_LIST, + %%Msg = tool:to_list(lists:nth(random:uniform(length(Chat_List)), Chat_List)), + Rand = random:uniform(100), + case Rand > 50 of + true -> + Act = ?DO_SALE; + false -> + case Rand > 20 of + true -> + Act = ?ADD_GOODS; + false -> + Act = ?ADD_GOODS + end + end, + %%Act = lists:nth(random:uniform(length(?ACTIONS)), ?ACTIONS), + do_action(State, Act), + State. + +do_action(State, Act) -> + case Act of + ?ADD_GOODS -> + io:format("do_action:add_goods~n"), + become_vip(State), + sale_add_goods(State); + ?DO_SALE -> + io:format("do_action:query_bag~n"), + query_bag(State); + ?DO_BUY -> + io:format("do_action:do_query~n"), + do_buy(State); + _ -> + skip + end. + +do_parse_packet(_Socket, _Pid, Cmd, BinData) -> + {ok, _Result} = ptr_41:read(Cmd, BinData), + case Cmd of + 41001 -> + case _Result of + <> -> + case parse_sale_list(SellingBin, []) of + SaleList when length(SaleList) > 0 -> + Sale = lists:nth(random:uniform(length(SaleList)), SaleList), + io:format("SaleList~p~n", [Sale#sale_list.saleId]), + {ok, BinData2} = ptr_41:write(41002, [Sale#sale_list.saleId]), + gen_tcp:send(_Socket, BinData2); + _ -> + skip + end; + _ -> + skip + end; + _ -> + skip + end, + io:format("Cmd: ~p, Result: ~p~n", [Cmd, _Result]). + +do_buy(State) -> + sale_add_gold(State), + do_query(State). + +do_query(State) -> + {ok, BinData} = ptr_41:write(41001, [0]), + gen_tcp:send(State#robot.socket, BinData). + +do_sale(State, BagList) -> + lists:map(fun(Data) -> + case lists:member(Data#bag_list.tid, ?GOODS) of + true -> + io:format("do_sale::~p~n", [Data#bag_list.id]), + {ok, BinData} = ptr_41:write(41003, [Data#bag_list.id, 1, 10]), + gen_tcp:send(State#robot.socket, BinData); + false -> + skip + end + end, BagList). + +refresh_bag(State, BinData) -> + <> = BinData, + BagList = parse_bag_data(ListBin, []), + do_sale(State, BagList). + +parse_sale_list(BinData, Result) -> + case BinData of + <> -> + Result2 = Result ++ [#sale_list{saleId = SaleId, goodsUid = GoodsUId, goodsId = GoodsId, leftTime = LeftTime, num = Num, price = Price}], + parse_sale_list(LeftData, Result2); + _ -> + Result + end. + +parse_bag_data(BinData, Result) -> + case BinData of + <> -> + Result2 = Result ++ [#bag_list{id = GoodsId, tid = TypeId, cell = Cell, num = GoodsNum, stren = Stren, strenPer = StrenPer, bind = Bind}], + parse_bag_data(LeftData, Result2); + _ -> + io:format("market:parse_bag_data::~p~n", [Result]), + Result + end. + +become_vip(State) -> + Content = "-gold 1000010", + ContentLen = length(Content), + NewContent = list_to_binary(Content), + gen_tcp:send(State#robot.socket, pack(11005, <<0:8, <>/binary>>)). + +sale_add_goods(State) -> + GoodsId = lists:nth(random:uniform(length(?GOODS)), ?GOODS), + Content = string:concat("-addgoods ", util:term_to_string(GoodsId)), + Content2 = string:concat(Content, " 1"), + ContentLen = length(Content2), + NewContent = list_to_binary(Content2), + gen_tcp:send(State#robot.socket, pack(11005, <<0:8, <>/binary>>)). + +sale_add_gold(State) -> + GoodsId = lists:nth(random:uniform(length(?GOODS)), ?GOODS), + Content = "-addgold 11", + ContentLen = length(Content), + NewContent = list_to_binary(Content), + gen_tcp:send(State#robot.socket, pack(11005, <<0:8, <>/binary>>)). + +query_bag(State) -> + gen_tcp:send(State#robot.socket, pack(15002, <<0:8>>)). + +pack(Cmd, Data) -> + L = byte_size(Data) + ?HEADER_LENGTH, + <>. diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/robot_mount.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_mount.erl new file mode 100644 index 0000000..cb51e21 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_mount.erl @@ -0,0 +1,44 @@ +%%%------------------------------------ +%%% @Author : +%%% @Created : 2010.09.27 +%%% @Description: 坐骑处理 +%%%------------------------------------ +-module(robot_mount). +-include("robot.hrl"). +-compile(export_all). + +handle(Status) -> + RandNum = util:rand(1, 3), + if + RandNum == 1 -> + upgrade_mount_star(Status); + RandNum == 2 -> + upgrade_mount_level(Status); + RandNum == 3 -> + upgrade_mount_skill(Status); + true -> + skip + end, + Status. + +upgrade_mount_star(Status) -> + Cmd = 44006, + AutoBuy = 1, + BatchUpgrade = 1, + {ok, BinData} = ptr_44:write(Cmd, [AutoBuy, BatchUpgrade]), + gen_tcp:send(Status#robot.socket, BinData), + Status. + +upgrade_mount_level(Status) -> + Cmd = 44007, + AutoBuy = 1, + {ok, BinData} = ptr_44:write(Cmd, [AutoBuy]), + gen_tcp:send(Status#robot.socket, BinData), + Status. + +upgrade_mount_skill(Status) -> + Cmd = 44001, + UpgradeType = 1, + {ok, BinData} = ptr_44:write(Cmd, [UpgradeType]), + gen_tcp:send(Status#robot.socket, BinData), + Status. \ No newline at end of file diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/robot_newbie.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_newbie.erl new file mode 100644 index 0000000..72243b0 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_newbie.erl @@ -0,0 +1,18 @@ +%%--------------------------------------------- +%% @Module : robot_newbie +%% @Author : smxx +%% @Created : 2013.09.13 +%% @Description: 新手引导测试客户端 +%%--------------------------------------------- +-module(robot_newbie). +-include("robot.hrl"). +-compile(export_all). + +get_newbie_list(Status) -> + + Status. + + + + + diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/robot_openfunc.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_openfunc.erl new file mode 100644 index 0000000..7ac0720 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_openfunc.erl @@ -0,0 +1,23 @@ +%%--------------------------------------------- +%% @Module : robot_openfunc +%% @Author : smxx +%% @Created : 2013.03.01 +%% @Description: 功能开放测试客户端 +%%--------------------------------------------- +-module(robot_openfunc). +-include("robot.hrl"). +-compile(export_all). + +handle(Status) -> + %%通过-openfunc XX XX的gm指令来压 + Cmd = 11005, + Type = 0, + Content = "-openfunc 1 20", + {ok, BinData} = ptr_11:write(Cmd, [0, Content]), + gen_tcp:send(Status#robot.socket, BinData), + Status. + + + + + diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/robot_pet.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_pet.erl new file mode 100644 index 0000000..020ecbd --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_pet.erl @@ -0,0 +1,9 @@ +%%%--------------------------------------------- +%%% @Module : robot_pet +%%% @Author : smxx +%%% @Created : 2013.03.xx +%%% @Description: 宠物客户端测试程序 +%%%--------------------------------------------- +-module(robot_pet). +%% -include("robot.hrl"). +%% -compile(export_all). diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/robot_shop.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_shop.erl new file mode 100644 index 0000000..4e0636b --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_shop.erl @@ -0,0 +1,51 @@ +-module(robot_shop). +-compile(export_all). + +%-include("common.hrl"). +-include("robot.hrl"). +-define(SHOP_REFRESH, 1). +-define(SHOP_BUY, 2). +-define(ADD_GOLD, 3). +-define(ACTIONS, [?SHOP_REFRESH, ?SHOP_BUY]). + +handle(State) -> + Rand = random:uniform(100), + case Rand > 50 of + true -> + Act = ?SHOP_REFRESH; + false -> + Act = ?SHOP_BUY + end, + add_gold(State), + do_action(State, Act), + State. + +do_action(State, Act) -> + case Act of + ?SHOP_REFRESH -> + io:format("do_action:shop_refresh~n"), + do_shop_refresh(State); + ?SHOP_BUY -> + io:format("do_action:shop_buy~n"), + do_shop_buy(State); + _ -> + skip + end. + +do_shop_refresh(State) -> + BinData = pt:pack(15042, <<>>), + gen_tcp:send(State#robot.socket, BinData). + +do_shop_buy(State) -> + BinData = pt:pack(15043, <<>>), + gen_tcp:send(State#robot.socket, BinData). + +add_gold(State) -> + Content = "-addgold 111", + ContentLen = length(Content), + NewContent = list_to_binary(Content), + gen_tcp:send(State#robot.socket, pack(11005, <<0:8, <>/binary>>)). + +pack(Cmd, Data) -> + L = byte_size(Data) + ?HEADER_LENGTH, + <>. diff --git a/src/srvNodeMgr/tools/gameWorld/test/robot/robot_task.erl b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_task.erl new file mode 100644 index 0000000..c69dca7 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/robot/robot_task.erl @@ -0,0 +1,140 @@ +%%-------------------------------------------- +%%---------任务机器人相关接口----------------- +%%-----------------by CXF--------------------- +%%-------------------------------------------- + +-module(robot_task). +-include("robot.hrl"). + +%% gen_server callbacks +-compile(export_all). + +accept_task(Socket, TaskProcessId) -> + io:format("accept_task.....................~n"), + gen_tcp:send(Socket, pack(30002, <>)). + +finish_task(Socket, TaskId) -> + io:format("finish_task.....................~n"), + Content = string:concat("-taskgoto ", util:term_to_string(TaskId)), + ContentLen = length(Content), + NewContent = list_to_binary(Content), + gen_tcp:send(Socket, pack(11005, <<0:8, <>/binary>>)). + +submit_task(Socket, TaskProcessId) -> + io:format("submit_task.....................~n"), + gen_tcp:send(Socket, pack(30004, <>)). + +parse_task_data(BinData, Result) -> + case BinData of + <> -> + Result2 = Result ++ [#task_list{id = Id, taskId = TaskId, state = State, mark = Mark, grade = Grade}], + parse_task_data(LeftData, Result2); + _ -> + Result + end. + +%%随机获得玩家的某个任务 +get_rand_taskPid(TaskList) -> + io:format("VVVVVVVVVVVVVVVVVVVVV333::::::::~p~n", [length(TaskList)]), + if + is_list(TaskList) -> + io:format("VVVVVVVVVVVVVVVVVVVVV666::::::::~p~n", [length(TaskList)]), + RandNum = rand(length(TaskList)), + io:format("VVVVVVVVVVVVVVVVVVVVV444::::::::~p~n", [RandNum]), + TargetTask = lists:nth(RandNum, TaskList); + true -> + skip + end. + +handle(get_task, {}, Socket) -> + TotalNum = ?MAX_TASK_NUM, + gen_tcp:send(Socket, pack(30006, <>)). + +pack(Cmd, Data) -> + L = byte_size(Data) + ?HEADER_LENGTH, + <>. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%辅助函数 +%%读取字符串 +read_string(Bin) -> + case Bin of + <> -> + case Bin1 of + <> -> + {binary_to_list(Str), Rest}; + _R1 -> + {[], <<>>} + end; + _R1 -> + {[], <<>>} + end. + +random_sleep(T) -> + N = random:uniform(T), + timer:sleep(N * 100). + + +sleep(T) -> + receive + after T -> ok + end. + +for(Max, Max, _F) -> + []; +for(Min, Max, F) -> + [F(Min) | for(Min + 1, Max, F)]. + +for(Max, Max, _F, X) -> + X; +for(Min, Max, F, X) -> + F(X), + for(Min + 1, Max, F, X). + +%%@spec 获取怪物追击路径 +make_move_path(StartX, StartY, EndX, EndY, Path) -> + if + StartX =:= EndX andalso StartY =:= EndY -> + Path; + StartX =:= EndX -> + NextX = StartX, + NextY = make_next_step(StartY, EndY), + NewPath = Path ++ [{NextX, NextY}], + make_move_path(NextX, NextY, EndX, EndY, NewPath); + StartY =:= EndY -> + NextX = make_next_step(StartX, EndX), + NextY = EndY, + NewPath = Path ++ [{NextX, NextY}], + make_move_path(NextX, NextY, EndX, EndY, NewPath); + true -> + NextX = make_next_step(StartX, EndX), + NextY = make_next_step(StartY, EndY), + NewPath = Path ++ [{NextX, NextY}], + make_move_path(NextX, NextY, EndX, EndY, NewPath) + end. +make_next_step(Current, Target) -> + if Current > Target -> + if Current - Target > 1 -> + Current - 1; + true -> + Target + end; + true -> + if Target - Current > 1 -> + Current + 1; + true -> + Target + end + end. + +rand(Min) when Min =< 0 -> + 0; +rand(Max) -> + case get("rand_seed") of + undefined -> + RandSeed = now(), + random:seed(RandSeed), + put("rand_seed", RandSeed); + _ -> skip + end, + random:uniform(Max). + diff --git a/src/srvNodeMgr/tools/gameWorld/test/tools/mmake.erl b/src/srvNodeMgr/tools/gameWorld/test/tools/mmake.erl new file mode 100644 index 0000000..ffaabcb --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/tools/mmake.erl @@ -0,0 +1,350 @@ +%% 多进程编译,修改自otp/lib/tools/src/make.erl +%% 解析Emakefile,根据获取{mods, options}列表, +%% 按照次序编译每项(解决编译顺序的问题) +%% 其中mods也可以包含多个模块,当大于1个时, +%% 可以启动多个process进行编译,从而提高编译速度. +-module(mmake). +-export([all/1, all/2, files/2, files/3]). + +-include_lib("kernel/include/file.hrl"). + +-define(MakeOpts, [noexec, load, netload, noload]). + +all(Worker) when is_integer(Worker) -> + all(Worker, []). + +all(Worker, Options) when is_integer(Worker) -> + {MakeOpts, CompileOpts} = sort_options(Options, [], []), + case read_emakefile('Emakefile', CompileOpts) of + Files when is_list(Files) -> + do_make_files(Worker, Files, MakeOpts); + error -> + error + end. + +files(Worker, Fs) -> + files(Worker, Fs, []). + +files(Worker, Fs0, Options) -> + Fs = [filename:rootname(F, ".erl") || F <- Fs0], + {MakeOpts, CompileOpts} = sort_options(Options, [], []), + case get_opts_from_emakefile(Fs, 'Emakefile', CompileOpts) of + Files when is_list(Files) -> + do_make_files(Worker, Files, MakeOpts); + error -> error + end. + +do_make_files(Worker, Fs, Opts) -> + %io:format("worker:~p~nfs:~p~nopts:~p~n", [Worker, Fs, Opts]), + process(Fs, Worker, lists:member(noexec, Opts), load_opt(Opts)). + +sort_options([H | T], Make, Comp) -> + case lists:member(H, ?MakeOpts) of + true -> + sort_options(T, [H | Make], Comp); + false -> + sort_options(T, Make, [H | Comp]) + end; +sort_options([], Make, Comp) -> + {Make, lists:reverse(Comp)}. + +%%% Reads the given Emakefile and returns a list of tuples: {Mods,Opts} +%%% Mods is a list of module names (strings) +%%% Opts is a list of options to be used when compiling Mods +%%% +%%% Emakefile can contain elements like this: +%%% Mod. +%%% {Mod,Opts}. +%%% Mod is a module name which might include '*' as wildcard +%%% or a list of such module names +%%% +%%% These elements are converted to [{ModList,OptList},...] +%%% ModList is a list of modulenames (strings) +read_emakefile(Emakefile, Opts) -> + case file:consult(Emakefile) of + {ok, Emake} -> + transform(Emake, Opts, [], []); + {error, enoent} -> + %% No Emakefile found - return all modules in current + %% directory and the options given at command line + Mods = [filename:rootname(F) || F <- filelib:wildcard("*.erl")], + [{Mods, Opts}]; + {error, Other} -> + io:format("make: Trouble reading 'Emakefile':~n~p~n", [Other]), + error + end. + +transform([{Mod, ModOpts} | Emake], Opts, Files, Already) -> + case expand(Mod, Already) of + [] -> + transform(Emake, Opts, Files, Already); + Mods -> + transform(Emake, Opts, [{Mods, ModOpts ++ Opts} | Files], Mods ++ Already) + end; +transform([Mod | Emake], Opts, Files, Already) -> + case expand(Mod, Already) of + [] -> + transform(Emake, Opts, Files, Already); + Mods -> + transform(Emake, Opts, [{Mods, Opts} | Files], Mods ++ Already) + end; +transform([], _Opts, Files, _Already) -> + lists:reverse(Files). + +expand(Mod, Already) when is_atom(Mod) -> + expand(atom_to_list(Mod), Already); +expand(Mods, Already) when is_list(Mods), not is_integer(hd(Mods)) -> + lists:concat([expand(Mod, Already) || Mod <- Mods]); +expand(Mod, Already) -> + case lists:member($*, Mod) of + true -> + Fun = fun(F, Acc) -> + M = filename:rootname(F), + case lists:member(M, Already) of + true -> Acc; + false -> [M | Acc] + end + end, + lists:foldl(Fun, [], filelib:wildcard(Mod ++ ".erl")); + false -> + Mod2 = filename:rootname(Mod, ".erl"), + case lists:member(Mod2, Already) of + true -> []; + false -> [Mod2] + end + end. + +%%% Reads the given Emakefile to see if there are any specific compile +%%% options given for the modules. +get_opts_from_emakefile(Mods, Emakefile, Opts) -> + case file:consult(Emakefile) of + {ok, Emake} -> + Modsandopts = transform(Emake, Opts, [], []), + ModStrings = [coerce_2_list(M) || M <- Mods], + get_opts_from_emakefile2(Modsandopts, ModStrings, Opts, []); + {error, enoent} -> + [{Mods, Opts}]; + {error, Other} -> + io:format("make: Trouble reading 'Emakefile':~n~p~n", [Other]), + error + end. + +get_opts_from_emakefile2([{MakefileMods, O} | Rest], Mods, Opts, Result) -> + case members(Mods, MakefileMods, [], Mods) of + {[], _} -> + get_opts_from_emakefile2(Rest, Mods, Opts, Result); + {I, RestOfMods} -> + get_opts_from_emakefile2(Rest, RestOfMods, Opts, [{I, O} | Result]) + end; +get_opts_from_emakefile2([], [], _Opts, Result) -> + Result; +get_opts_from_emakefile2([], RestOfMods, Opts, Result) -> + [{RestOfMods, Opts} | Result]. + +members([H | T], MakefileMods, I, Rest) -> + case lists:member(H, MakefileMods) of + true -> + members(T, MakefileMods, [H | I], lists:delete(H, Rest)); + false -> + members(T, MakefileMods, I, Rest) + end; +members([], _MakefileMods, I, Rest) -> + {I, Rest}. + + +%% Any flags that are not recognixed as make flags are passed directly +%% to the compiler. +%% So for example make:all([load,debug_info]) will make everything +%% with the debug_info flag and load it. +load_opt(Opts) -> + case lists:member(netload, Opts) of + true -> + netload; + false -> + case lists:member(load, Opts) of + true -> + load; + _ -> + noload + end + end. + +%% 处理 +process([{[], _Opts} | Rest], Worker, NoExec, Load) -> + process(Rest, Worker, NoExec, Load); +process([{L, Opts} | Rest], Worker, NoExec, Load) -> + Len = length(L), + Worker2 = erlang:min(Len, Worker), + case catch do_worker(L, Opts, NoExec, Load, Worker2) of + error -> + error; + ok -> + process(Rest, Worker, NoExec, Load) + end; +process([], _Worker, _NoExec, _Load) -> + up_to_date. + +%% worker进行编译 +do_worker(L, Opts, NoExec, Load, Worker) -> + WorkerList = do_split_list(L, Worker), + %io:format("worker:~p worker list(~p)~n", [Worker, length(WorkerList)]), + % 启动进程 + Ref = make_ref(), + Pids = + [begin + start_worker(E, Opts, NoExec, Load, self(), Ref) + end || E <- WorkerList], + do_wait_worker(length(Pids), Ref). + +%% 等待结果 +do_wait_worker(0, _Ref) -> + ok; +do_wait_worker(N, Ref) -> + receive + {ack, Ref} -> + do_wait_worker(N - 1, Ref); + {error, Ref} -> + throw(error); + {'EXIT', _P, _Reason} -> + do_wait_worker(N, Ref); + _Other -> + io:format("receive unknown msg:~p~n", [_Other]), + do_wait_worker(N, Ref) + end. + +%% 将L分割成最多包含N个子列表的列表 +do_split_list(L, N) -> + Len = length(L), + % 每个列表的元素数 + LLen = (Len + N - 1) div N, + do_split_list(L, LLen, []). + +do_split_list([], _N, Acc) -> + lists:reverse(Acc); +do_split_list(L, N, Acc) -> + {L2, L3} = lists:split(erlang:min(length(L), N), L), + do_split_list(L3, N, [L2 | Acc]). + +%% 启动worker进程 +start_worker(L, Opts, NoExec, Load, Parent, Ref) -> + Fun = + fun() -> + [begin + case recompilep(coerce_2_list(F), NoExec, Load, Opts) of + error -> + Parent ! {error, Ref}, + exit(error); + _ -> + ok + end + end || F <- L], + Parent ! {ack, Ref} + end, + spawn_link(Fun). + +recompilep(File, NoExec, Load, Opts) -> + ObjName = lists:append(filename:basename(File), + code:objfile_extension()), + ObjFile = case lists:keysearch(outdir, 1, Opts) of + {value, {outdir, OutDir}} -> + filename:join(coerce_2_list(OutDir), ObjName); + false -> + ObjName + end, + case exists(ObjFile) of + true -> + recompilep1(File, NoExec, Load, Opts, ObjFile); + false -> + recompile(File, NoExec, Load, Opts) + end. + +recompilep1(File, NoExec, Load, Opts, ObjFile) -> + {ok, Erl} = file:read_file_info(lists:append(File, ".erl")), + {ok, Obj} = file:read_file_info(ObjFile), + recompilep1(Erl, Obj, File, NoExec, Load, Opts). + +recompilep1(#file_info{mtime = Te}, + #file_info{mtime = To}, File, NoExec, Load, Opts) when Te > To -> + recompile(File, NoExec, Load, Opts); +recompilep1(_Erl, #file_info{mtime = To}, File, NoExec, Load, Opts) -> + recompile2(To, File, NoExec, Load, Opts). + +%% recompile2(ObjMTime, File, NoExec, Load, Opts) +%% Check if file is of a later date than include files. +recompile2(ObjMTime, File, NoExec, Load, Opts) -> + IncludePath = include_opt(Opts), + case check_includes(lists:append(File, ".erl"), IncludePath, ObjMTime) of + true -> + recompile(File, NoExec, Load, Opts); + false -> + false + end. + +include_opt([{i, Path} | Rest]) -> + [Path | include_opt(Rest)]; +include_opt([_First | Rest]) -> + include_opt(Rest); +include_opt([]) -> + []. + +%% recompile(File, NoExec, Load, Opts) +%% Actually recompile and load the file, depending on the flags. +%% Where load can be netload | load | noload + +recompile(File, true, _Load, _Opts) -> + io:format("Out of date: ~s\n", [File]); +recompile(File, false, noload, Opts) -> + io:format("Recompile: ~s\n", [File]), + compile:file(File, [report_errors, report_warnings, error_summary | Opts]); +recompile(File, false, load, Opts) -> + io:format("Recompile: ~s\n", [File]), + c:c(File, Opts); +recompile(File, false, netload, Opts) -> + io:format("Recompile: ~s\n", [File]), + c:nc(File, Opts). + +exists(File) -> + case file:read_file_info(File) of + {ok, _} -> + true; + _ -> + false + end. + +coerce_2_list(X) when is_atom(X) -> + atom_to_list(X); +coerce_2_list(X) -> + X. + +%%% If you an include file is found with a modification +%%% time larger than the modification time of the object +%%% file, return true. Otherwise return false. +check_includes(File, IncludePath, ObjMTime) -> + Path = [filename:dirname(File) | IncludePath], + case epp:open(File, Path, []) of + {ok, Epp} -> + check_includes2(Epp, File, ObjMTime); + _Error -> + false + end. + +check_includes2(Epp, File, ObjMTime) -> + case epp:parse_erl_form(Epp) of + {ok, {attribute, 1, file, {File, 1}}} -> + check_includes2(Epp, File, ObjMTime); + {ok, {attribute, 1, file, {IncFile, 1}}} -> + case file:read_file_info(IncFile) of + {ok, #file_info{mtime = MTime}} when MTime > ObjMTime -> + epp:close(Epp), + true; + _ -> + check_includes2(Epp, File, ObjMTime) + end; + {ok, _} -> + check_includes2(Epp, File, ObjMTime); + {eof, _} -> + epp:close(Epp), + false; + {error, _Error} -> + check_includes2(Epp, File, ObjMTime) + end. diff --git a/src/srvNodeMgr/tools/gameWorld/test/tools/mtop.erl b/src/srvNodeMgr/tools/gameWorld/test/tools/mtop.erl new file mode 100644 index 0000000..b23b6e9 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/tools/mtop.erl @@ -0,0 +1,298 @@ +%% minitop, etop的简化版 +%% 使用方法和etop类似,但参数只剩下 lines 和 sort +%% all :除了etop的内容以外,还会加上 具体进程的 erlang:process_info 信息 +%% top :输出的功能和etop一样 +%% stack: etop的信息和堆栈信息 +%% messages: etop的信息和进程消息队列的信息 +%% option: etop的信息和指定的erlang:process_info信息 + +-module(mtop). +-export([all/0, all/1]). +-export([top/0, top/1]). +-export([stack/0, stack/1]). +-export([option/1, option/2]). +-export([messages/0, messages/1]). + +-define(SYSFORM, + " ~-72w~10s~n" + " Load: cpu ~8w Memory: total ~8w binary ~8w~n" + " procs~8w processes~8w code ~8w~n" + " runq ~8w atom ~8w ets ~8w~n"). + +-define(PROCFORM, "~-15w~-20s~8w~8w~8w~8w ~-20s~n"). + +-record(mtop_info, +{now = {0, 0, 0}, + n_procs = 0, + wall_clock = {0, 0}, + runtime = {0, 0}, + run_queue = 0, + alloc_areas = [], + memi = [{total, 0}, + {processes, 0}, + {ets, 0}, + {atom, 0}, + {code, 0}, + {binary, 0}], + procinfo = [] +}). + +-record(mtop_proc_info, +{pid, + mem = 0, + reds = 0, + name, + runtime = 0, + cf, + mq = 0}). + +-record(opts, {lines = 10, sort = msg_q}). + + +make_info(ProcInfo) -> + #mtop_info{now = now(), + n_procs = length(ProcInfo), + run_queue = erlang:statistics(run_queue), + wall_clock = erlang:statistics(wall_clock), + runtime = erlang:statistics(runtime), + memi = mtop_memi(), + procinfo = ProcInfo + }. + +all() -> + all([{lines, 10}, {sort, msg_q}]). +all(Opts) -> + Config = handle_args(init:get_arguments() ++ Opts, #opts{}), + ProcInfo = get_infos(Config), + Info = make_info(ProcInfo), + print_infos_all(standard_io, Info). + + +top() -> + top([{lines, 10}, {sort, msg_q}]). + +top(Opts) -> + Config = handle_args(init:get_arguments() ++ Opts, #opts{}), + ProcInfo = get_infos(Config), + Info = make_info(ProcInfo), + print_infos_top(standard_io, Info). + +stack() -> + stack([{lines, 10}, {sort, msg_q}]). + +stack(Opts) -> + Config = handle_args(init:get_arguments() ++ Opts, #opts{}), + ProcInfo = get_infos(Config), + Info = make_info(ProcInfo), + print_infos_stack(standard_io, Info). + +option(OptionList) -> + option([{lines, 10}, {sort, msg_q}], OptionList). + +option(Opts, OptionList) -> + Config = handle_args(init:get_arguments() ++ Opts, #opts{}), + ProcInfo = get_infos(Config), + Info = make_info(ProcInfo), + print_infos_option(standard_io, Info, OptionList). + +messages() -> + messages([{lines, 10}, {sort, msg_q}]). + +messages(Opts) -> + option(Opts, [messages]). + + +mtop_memi() -> + try + [{total, c:memory(total)}, + {processes, c:memory(processes)}, + {ets, c:memory(ets)}, + {atom, c:memory(atom)}, + {code, c:memory(code)}, + {binary, c:memory(binary)}] + catch + error:notsup -> + undefined + end. + +get_infos(Opts) -> + ProcInfo = mtop_collect(), + ProcInfo1 = lists:map(fun(PI) -> PI#mtop_proc_info{runtime = '-'} end, ProcInfo), + sort(Opts, ProcInfo1). + +mtop_collect() -> + mtop_collect(processes(), []). + +mtop_collect([P | Ps], Acc) when P =:= self() -> + mtop_collect(Ps, Acc); +mtop_collect([P | Ps], Acc) -> + Fs = [registered_name, initial_call, memory, reductions, current_function, message_queue_len], + case process_info(P, Fs) of + undefined -> + mtop_collect(Ps, Acc); + [{registered_name, Reg}, {initial_call, Initial}, {memory, Mem}, + {reductions, Reds}, {current_function, Current}, {message_queue_len, Qlen}] -> + Name = case Reg of + [] -> Initial; + _ -> Reg + end, + Info = #mtop_proc_info{pid = P, mem = Mem, reds = Reds, name = Name, + cf = Current, mq = Qlen}, + mtop_collect(Ps, [Info | Acc]) + end; +mtop_collect([], Acc) -> Acc. + +sort(Opts, PI) -> + Tag = get_tag(Opts#opts.sort), + PI1 = lists:reverse(lists:keysort(Tag, PI)), + lists:sublist(PI1, Opts#opts.lines). + +get_tag(runtime) -> #mtop_proc_info.runtime; +get_tag(memory) -> #mtop_proc_info.mem; +get_tag(reductions) -> #mtop_proc_info.reds; +get_tag(msg_q) -> #mtop_proc_info.mq. + + +print_infos_top(Fd, Info) -> + {Cpu, NProcs, RQ, Clock} = loadinfo(Info), + io:nl(Fd), + writedoubleline(Fd), + case Info#mtop_info.memi of + undefined -> + io:fwrite(Fd, " ~-72w~10s~n" + " Load: cpu ~8w~n" + " procs~8w~n" + " runq ~8w~n", + [node(), Clock, + Cpu, NProcs, RQ]); + Memi -> + [Tot, Procs, Atom, Bin, Code, Ets] = + meminfo(Memi, [total, processes, atom, binary, code, ets]), + io:fwrite(Fd, ?SYSFORM, + [node(), Clock, + Cpu, Tot, Bin, + NProcs, Procs, Code, + RQ, Atom, Ets]) + end, + io:nl(Fd), + writepinfo_header(Fd), + writesingleline(Fd), + writepinfo(Fd, Info#mtop_info.procinfo), + writedoubleline(Fd), + io:nl(Fd). + +writepinfo_header(Fd) -> + io:fwrite(Fd, "Pid Name or Initial Func Time Reds Memory MsgQ Current Function~n", []). + +writesingleline(Fd) -> + io:fwrite(Fd, "----------------------------------------------------------------------------------------~n", []). +writedoubleline(Fd) -> + io:fwrite(Fd, "========================================================================================~n", []). + +formatmfa({M, F, A}) -> + io_lib:format("~w:~w/~w", [M, F, A]). + +to_list(Name) when is_atom(Name) -> atom_to_list(Name); +to_list({_M, _F, _A} = MFA) -> formatmfa(MFA). + +writepinfo(Fd, [#mtop_proc_info{pid = Pid, + mem = Mem, + reds = Reds, + name = Name, + runtime = Time, + cf = MFA, + mq = MQ} + | T]) -> + io:fwrite(Fd, ?PROCFORM, [Pid, to_list(Name), Time, Reds, Mem, MQ, formatmfa(MFA)]), + writepinfo(Fd, T); +writepinfo(_Fd, []) -> + ok. + + +loadinfo(SysI) -> + #mtop_info{n_procs = Procs, + run_queue = RQ, + now = Now, + wall_clock = {_, WC}, + runtime = {_, RT}} = SysI, + Cpu = round(100 * RT / WC), + Clock = io_lib:format("~2.2.0w:~2.2.0w:~2.2.0w", + tuple_to_list(element(2, calendar:now_to_datetime(Now)))), + {Cpu, Procs, RQ, Clock}. + +meminfo(MemI, [Tag | Tags]) -> + [round(get_mem(Tag, MemI) / 1024) | meminfo(MemI, Tags)]; +meminfo(_MemI, []) -> []. + +get_mem(Tag, MemI) -> + case lists:keysearch(Tag, 1, MemI) of + {value, {Tag, I}} -> I; %these are in bytes + _ -> 0 + end. + +handle_args([{lines, Lines} | R], Config) when is_integer(Lines) -> + NewC = Config#opts{lines = Lines}, + handle_args(R, NewC); +handle_args([{lines, [Lines]} | R], Config) when is_list(Lines) -> + NewC = Config#opts{lines = list_to_integer(Lines)}, + handle_args(R, NewC); +handle_args([{sort, Sort} | R], Config) when is_atom(Sort) -> + NewC = Config#opts{sort = Sort}, + handle_args(R, NewC); +handle_args([{sort, [Sort]} | R], Config) when is_list(Sort) -> + NewC = Config#opts{sort = list_to_atom(Sort)}, + handle_args(R, NewC); + +handle_args([_ | R], C) -> + handle_args(R, C); +handle_args([], C) -> + C. + +print_infos_all(Fd, Info) -> + print_infos_top(Fd, Info), + writeprocessinfo(Fd, Info#mtop_info.procinfo). + +writebt(Fd, Pid) -> + {_, P} = erlang:process_info(Pid, backtrace), + io:fwrite(Fd, "~s~n", [erlang:binary_to_list(P)]). + +writeprocessinfo(Fd, [#mtop_proc_info{pid = Pid} + | T]) -> + writedoubleline(Fd), + io:fwrite(Fd, "~p~n", [erlang:process_info(Pid)]), + writesingleline(Fd), + writebt(Fd, Pid), + writeprocessinfo(Fd, T); + +writeprocessinfo(_Fd, []) -> + ok. + + +print_infos_stack(Fd, Info) -> + print_infos_top(Fd, Info), + writebtinfo(Fd, Info#mtop_info.procinfo). + +writebtinfo(Fd, [#mtop_proc_info{pid = Pid} + | T]) -> + writedoubleline(Fd), + writebt(Fd, Pid), + writebtinfo(Fd, T); + +writebtinfo(_Fd, []) -> + ok. + + +print_infos_option(Fd, Info, OptionList) -> + print_infos_top(Fd, Info), + writeprocessinfo_option(Fd, OptionList, Info#mtop_info.procinfo). + +writeprocessinfo_option(Fd, OptionList, [#mtop_proc_info{pid = Pid} + | T]) -> + writedoubleline(Fd), + io:fwrite(Fd, "~p~n", [erlang:process_info(Pid, OptionList)]), + writesingleline(Fd), + writebt(Fd, Pid), + writeprocessinfo_option(Fd, OptionList, T); + +writeprocessinfo_option(_Fd, _OptionList, []) -> + ok. diff --git a/src/srvNodeMgr/tools/gameWorld/test/tools/record_to_code.erl b/src/srvNodeMgr/tools/gameWorld/test/tools/record_to_code.erl new file mode 100644 index 0000000..a83d874 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/tools/record_to_code.erl @@ -0,0 +1,480 @@ +%%%-------------------------------------- +%%% @Module : game_gateway +%%% @Author : csj +%%% @Created : 2010.10.27 +%%% @Description: 将record 转换成 erl code +%%% 暂时先处理 player, 以便方便的按所需字段读写player字段值。 +%%% 生成文件: "../src/lib/lib_player_rw.erl" +%%%-------------------------------------- + +-module(record_to_code). + +%% +%% Include files +%% +-include("common.hrl"). +-include("record.hrl"). + +%% +%% Exported Functions +%% +-compile(export_all). + +-define(CONFIG_FILE, "../config/gateway.config"). + +%% +%% API Functions +%% +start() -> + convert_player(), + case get_db_config(?CONFIG_FILE) of + [Host, Port, User, Password, DB, Encode] -> + start_erlydb(Host, Port, User, Password, DB), + mysql:start_link(?DB_SERVER, Host, Port, User, Password, DB, fun(_, _, _, _) -> ok end, Encode), + mysql:connect(?DB_SERVER, Host, Port, User, Password, DB, Encode, true), + table_fields_all(DB), + get_all_tables(DB), + ok; + _ -> mysql_config_fail + end, + halt(), + ok. + +get_db_config(Config_file) -> + try + {ok, [L]} = file:consult(Config_file), + {_, C} = lists:keyfind(gateway, 1, L), + {_, Mysql_config} = lists:keyfind(mysql_config, 1, C), + {_, Host} = lists:keyfind(host, 1, Mysql_config), + {_, Port} = lists:keyfind(port, 1, Mysql_config), + {_, User} = lists:keyfind(user, 1, Mysql_config), + {_, Password} = lists:keyfind(password, 1, Mysql_config), + {_, DB} = lists:keyfind(db, 1, Mysql_config), + {_, Encode} = lists:keyfind(encode, 1, Mysql_config), + [Host, Port, User, Password, DB, Encode] + catch + _:_ -> no_config + end. + +%% +%% Local Functions +%% +start_erlydb(IP, Port, User, Password, Db) -> + erlydb:start(mysql, [{pool_id, erlydb_mysql}, + {hostname, IP}, + {port, Port}, + {username, User}, + {password, Password}, + {database, Db}, + {logfun, fun(_, _, _, _) -> ok end}, + {encoding, utf8}, + {pool_size, 10}]). + + +convert_player() -> + io:format("~n~n~n~n~n~nBegin create ../src/lib/lib_player_rw.erl!~n~n"), + P_list = record_info(fields, player), + O_list = record_info(fields, player_other), + B_list = record_info(fields, battle_attr), + + File = "../src/lib/lib_player_rw.erl", + + file:write_file(File, ""), + + file:write_file(File, ""), + file:write_file(File, "%%%------------------------------------------------\t\n", [append]), + file:write_file(File, "%%% File : lib_player_rw.erl\t\n", [append]), + file:write_file(File, "%%% Author : csj\t\n", [append]), + Bytes0 = list_to_binary(io_lib:format("%%% Created : ~s\t\n", [time_format(now())])), + file:write_file(File, Bytes0, [append]), + file:write_file(File, "%%% Description: 从record生成的代码\t\n", [append]), + file:write_file(File, "%%% Warning: 由程序自动生成,请不要随意修改!\t\n", [append]), + file:write_file(File, "%%%------------------------------------------------ \t\n", [append]), + file:write_file(File, " \t\n", [append]), + file:write_file(File, "-module(lib_player_rw).\t\n", [append]), + file:write_file(File, " \t\n", [append]), + file:write_file(File, "%% \t\n", [append]), + file:write_file(File, "%% Include files \t\n", [append]), + file:write_file(File, "-include(\"common.hrl\"). \t\n", [append]), + file:write_file(File, "-include(\"record.hrl\"). \t\n", [append]), + file:write_file(File, " \t\n", [append]), + file:write_file(File, "%% \t\n", [append]), + file:write_file(File, "%% Exported Functions \t\n", [append]), + file:write_file(File, "%% \t\n", [append]), + file:write_file(File, "-compile(export_all). \t\n", [append]), + file:write_file(File, " \t\n", [append]), + + file:write_file(File, "%%获取用户信息(按[字段1,字段2,...])\t\n", [append]), + file:write_file(File, "%% handle_call({'PLAYER', [x ,y]}, _from, Status)\t\n", [append]), + file:write_file(File, "get_player_info_fields(Player, List) ->\t\n", [append]), + file:write_file(File, " lists:map(fun(T) ->\t\n", [append]), + file:write_file(File, " case T of\t\n", [append]), + lists:foreach(fun(Field_name) -> + case lists:member(Field_name, B_list) of + false -> + Bytes00 = lists:concat([" ", Field_name, " -> Player#player.", Field_name, ";\t\n"]), + file:write_file(File, Bytes00, [append]); + true -> + no_action + end + end, + P_list), + lists:foreach(fun(Field_name) -> + Bytes00 = lists:concat([" ", Field_name, " -> Player#player.battle_attr#battle_attr.", Field_name, ";\t\n"]), + file:write_file(File, Bytes00, [append]) + end, + B_list), + lists:foreach(fun(Field_name) -> + Bytes00 = lists:concat([" ", Field_name, " -> Player#player.other#player_other.", Field_name, ";\t\n"]), + file:write_file(File, Bytes00, [append]) + end, + O_list), + file:write_file(File, " _ -> undefined\t\n", [append]), + file:write_file(File, " end\t\n", [append]), + file:write_file(File, " end, List).\t\n", [append]), + file:write_file(File, " \t\n", [append]), + + file:write_file(File, "%%设置用户信息(按[{字段1,值1},{字段2,值2, add},{字段3,值3, sub}...])\t\n", [append]), + file:write_file(File, "%% handle_cast({'SET_PLAYER',[{x, 10} ,{y, 20, add}, ,{hp, 20, sub}]}, Status)\t\n", [append]), + file:write_file(File, "set_player_info_fields(Player, []) ->\t\n", [append]), + file:write_file(File, " Player;\t\n", [append]), + file:write_file(File, "set_player_info_fields(Player, [H|T]) ->\t\n", [append]), + file:write_file(File, " NewPlayer =\t\n", [append]), + file:write_file(File, " case H of\t\n", [append]), + lists:foreach(fun(Field_name) -> + case Field_name =:= other orelse Field_name =:= battle_attr orelse lists:member(Field_name, B_list) of + false -> + Bytes1 = lists:concat([" {", Field_name, ", Val, add} -> Player#player{", Field_name, "=Player#player.", Field_name, " + Val};\t\n"]), + file:write_file(File, Bytes1, [append]), + Bytes2 = lists:concat([" {", Field_name, ", Val, sub} -> Player#player{", Field_name, "=Player#player.", Field_name, " - Val};\t\n"]), + file:write_file(File, Bytes2, [append]), + Bytes3 = lists:concat([" {", Field_name, ", Val, _} -> Player#player{", Field_name, "= Val};\t\n"]), + file:write_file(File, Bytes3, [append]), + Bytes4 = lists:concat([" {", Field_name, ", Val} -> Player#player{", Field_name, "= Val};\t\n"]), + file:write_file(File, Bytes4, [append]); + true -> no_action + end + end, + P_list), + lists:foreach(fun(Field_name) -> + Bytes1 = lists:concat([" {", Field_name, + ", Val, add} -> Player#player{other=Player#player.other#player_other{", Field_name, + " = Player#player.other#player_other.", Field_name, " + Val}};\t\n"]), + file:write_file(File, Bytes1, [append]), + Bytes2 = lists:concat([" {", Field_name, + ", Val, sub} -> Player#player{other=Player#player.other#player_other{", Field_name, + " = Player#player.other#player_other.", Field_name, " - Val}};\t\n"]), + file:write_file(File, Bytes2, [append]), + Bytes3 = lists:concat([" {", Field_name, + ", Val, _} -> Player#player{other=Player#player.other#player_other{", Field_name, + " = Val}};\t\n"]), + file:write_file(File, Bytes3, [append]), + Bytes4 = lists:concat([" {", Field_name, + ", Val} -> Player#player{other=Player#player.other#player_other{", Field_name, + " = Val}};\t\n"]), + file:write_file(File, Bytes4, [append]) + end, + O_list), + lists:foreach(fun(Field_name) -> + case lists:member(Field_name, P_list) of + true -> %%同时字段也是player顶层成员, 两边都改 + Bytes11 = lists:concat([" {", Field_name, + ", Val, add} -> Player1 = Player#player{battle_attr=Player#player.battle_attr#battle_attr{", Field_name, + " = Player#player.battle_attr#battle_attr.", Field_name, " + Val}},\t\n"]), + file:write_file(File, Bytes11, [append]), + Bytes12 = lists:concat([" Player1#player{", Field_name, "=Player1#player.", Field_name, " + Val};\t\n"]), + file:write_file(File, Bytes12, [append]), + + Bytes21 = lists:concat([" {", Field_name, + ", Val, sub} -> Player1 = Player#player{battle_attr=Player#player.battle_attr#battle_attr{", Field_name, + " = Player#player.battle_attr#battle_attr.", Field_name, " - Val}},\t\n"]), + file:write_file(File, Bytes21, [append]), + Bytes22 = lists:concat([" Player1#player{", Field_name, "=Player1#player.", Field_name, " - Val};\t\n"]), + file:write_file(File, Bytes22, [append]), + + + Bytes31 = lists:concat([" {", Field_name, + ", Val, _} -> Player1 = Player#player{battle_attr=Player#player.battle_attr#battle_attr{", Field_name, + " = Val}},\t\n"]), + file:write_file(File, Bytes31, [append]), + Bytes32 = lists:concat([" Player1#player{", Field_name, "= Val};\t\n"]), + file:write_file(File, Bytes32, [append]), + + + Bytes41 = lists:concat([" {", Field_name, + ", Val} -> Player1 = Player#player{battle_attr=Player#player.battle_attr#battle_attr{", Field_name, + " = Val}},\t\n"]), + file:write_file(File, Bytes41, [append]), + Bytes42 = lists:concat([" Player1#player{", Field_name, "= Val};\t\n"]), + file:write_file(File, Bytes42, [append]); + false -> + Bytes1 = lists:concat([" {", Field_name, + ", Val, add} -> Player#player{battle_attr=Player#player.battle_attr#battle_attr{", Field_name, + " = Player#player.battle_attr#battle_attr.", Field_name, " + Val}};\t\n"]), + file:write_file(File, Bytes1, [append]), + Bytes2 = lists:concat([" {", Field_name, + ", Val, sub} -> Player#player{battle_attr=Player#player.battle_attr#battle_attr{", Field_name, + " = Player#player.battle_attr#battle_attr.", Field_name, " - Val}};\t\n"]), + file:write_file(File, Bytes2, [append]), + Bytes3 = lists:concat([" {", Field_name, + ", Val, _} -> Player#player{battle_attr=Player#player.battle_attr#battle_attr{", Field_name, + " = Val}};\t\n"]), + file:write_file(File, Bytes3, [append]), + Bytes4 = lists:concat([" {", Field_name, + ", Val} -> Player#player{battle_attr=Player#player.battle_attr#battle_attr{", Field_name, + " = Val}};\t\n"]), + file:write_file(File, Bytes4, [append]) + end + end, + B_list), + file:write_file(File, " _ -> Player\t\n", [append]), + file:write_file(File, " end,\t\n", [append]), + file:write_file(File, " set_player_info_fields(NewPlayer, T).\t\n", [append]), + + file:write_file(File, " \t\n", [append]), + file:write_file(File, "%%设置宠物信息(按[{字段1,值1},{字段2,值2, add},{字段3,值3, sub}...])\t\n", [append]), + file:write_file(File, "%% handle_cast({'SET_PET',[{x, 10} ,{y, 20, add}, ,{hp, 20, sub}]}, Status)\t\n", [append]), + io:format("Create ../src/lib/lib_player_rw.erl finished!~n~n"), + ok. + + +%% 根据表名获取其完全字段 +table_fields_all(DB_name) -> + Filename = "../src/lib/lib_player_rw.erl", + Sql = lists:concat(["SELECT table_name FROM information_schema.tables WHERE table_schema='", tool:to_list(DB_name), "' and table_type ='BASE TABLE'"]), + try + case db_esql:get_all(list_to_binary(Sql)) of + [] -> error1; + A -> + file:write_file(Filename, + list_to_binary(io_lib:format("\t\n\t\n%% 根据表名获取其完全字段\t\n", [])), + [append]), + + file:write_file(Filename, + list_to_binary(io_lib:format("get_table_fields(Table_name) ->\t\n", [])), + [append]), + + file:write_file(Filename, + list_to_binary(io_lib:format(" Table_fileds = [ \t\n", [])), + [append]), + + L = lists:flatten(A), + F = fun(T) -> +%% io:format("~p~n",[T]), + Sql1 = lists:concat(["SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema= '", tool:to_list(DB_name), "' AND table_name= '", tool:to_list(T), "'"]), + case db_esql:get_all(list_to_binary(Sql1)) of + [] -> error2; + B -> +%% D = lists:flatten(B), + {DL, _} = + lists:mapfoldl(fun([Field, Data_type0, Default0], Sum) -> + Data_type = tool:to_atom(Data_type0), + Default = + case Default0 of + undefined -> + case erlydb_field:get_erl_type(Data_type) of + binary -> + ""; + integer -> + 0; + _ -> 0 + end; + <<>> -> + case erlydb_field:get_erl_type(Data_type) of + binary -> + ""; + integer -> + 0; + _ -> "" + end; + <<"[]">> -> + []; + Val -> + case erlydb_field:get_erl_type(Data_type) of + binary -> + lists:concat(["", binary_to_list(Val), ""]); + integer -> + tool:to_integer(binary_to_list(Val)); + decimal -> + tool:to_float(binary_to_list(Val)); + _ -> + lists:concat([binary_to_list(Val)]) + end + end, + S = if Sum == length(B) -> + io_lib:format("{~s, ~p}", [tool:to_atom(Field), Default]); + true -> + io_lib:format("{~s, ~p},", [tool:to_atom(Field), Default]) + end, + {S, Sum + 1} + end, + 1, B), + E = io_lib:format('{~s,[~s]}', [tool:to_atom(T), lists:flatten(DL)]), + file:write_file(Filename, + list_to_binary(io_lib:format(" ~s,\t\n", [E])), + [append]), + ok + end + end, + [F(T) || T <- L], + file:write_file(Filename, + list_to_binary(io_lib:format(' {null,""}], \t\n', [])), + [append]), + + file:write_file(Filename, + list_to_binary(io_lib:format(' case lists:keysearch(Table_name,1, Table_fileds) of \t\n', [])), + [append]), + file:write_file(Filename, + list_to_binary(io_lib:format(' {value,{_, Val}} -> Val; \t\n', [])), + [append]), + file:write_file(Filename, + list_to_binary(io_lib:format(' _ -> undefined \t\n', [])), + [append]), + file:write_file(Filename, + list_to_binary(io_lib:format(' end. \t\n', [])), + [append]), + ok + end + catch + _:_ -> fail + end. + +%%生成所有的表 +get_all_tables(DB_name) -> + Filename = "../src/lib/lib_player_rw.erl", + Sql = lists:concat(["SELECT table_name FROM information_schema.tables WHERE table_schema='", tool:to_list(DB_name), "' and table_type ='BASE TABLE'"]), + try + case db_esql:get_all(list_to_binary(Sql)) of + [] -> error1; + A -> + file:write_file(Filename, + list_to_binary(io_lib:format("\t\n\t\n%% 获取所有表名\t\n", [])), + [append]), + + file:write_file(Filename, + list_to_binary(io_lib:format("get_all_tables() ->\t\n", [])), + [append]), + + file:write_file(Filename, + list_to_binary(io_lib:format(" [ \t\n", [])), + [append]), + + L = lists:flatten(A), + F = fun(T) -> + file:write_file(Filename, + list_to_binary(io_lib:format(" ~s,\t\n", [tool:to_atom(T)])), + [append]) + end, + [F(T) || T <- L], + file:write_file(Filename, + list_to_binary(io_lib:format(' null \t\n', [])), + [append]), + file:write_file(Filename, + list_to_binary(io_lib:format(" ]. \t\n", [])), + [append]), + ok + end + catch + _:_ -> fail + end. + +%% 根据表名获取其完全字段(前一版本) +table_fields_all_bak(DB_name) -> + Filename = "../src/lib/lib_player_rw.erl", + Sql = lists:concat(["SELECT table_name FROM information_schema.tables WHERE table_schema='", tool:to_list(DB_name), "' and table_type ='BASE TABLE'"]), + try + case db_esql:get_all(list_to_binary(Sql)) of + [] -> error1; + A -> + file:write_file(Filename, + list_to_binary(io_lib:format("\t\n\t\n%% 根据表名获取其完全字段\t\n", [])), + [append]), + + file:write_file(Filename, + list_to_binary(io_lib:format("get_table_fields(Table_name) ->\t\n", [])), + [append]), + + file:write_file(Filename, + list_to_binary(io_lib:format(" Table_fileds = [ \t\n", [])), + [append]), + + L = lists:flatten(A), + F = fun(T) -> +%% io:format("~p~n",[T]), + Sql1 = lists:concat(["SELECT column_name FROM information_schema.columns WHERE table_schema= '", tool:to_list(DB_name), "' AND table_name= '", tool:to_list(T), "'"]), + case db_esql:get_all(list_to_binary(Sql1)) of + [] -> error2; + B -> + D = lists:flatten(B), + {DL, _} = + lists:mapfoldl(fun(F, Sum) -> + S = if Sum == length(D) -> + io_lib:format("~s", [tool:to_atom(F)]); + true -> + io_lib:format("~s,", [tool:to_atom(F)]) + end, + {S, Sum + 1} + end, + 1, D), + E = io_lib:format('{~s,"~s"}', [tool:to_atom(T), lists:flatten(DL)]), + file:write_file(Filename, + list_to_binary(io_lib:format(" ~s,\t\n", [E])), + [append]), + ok + end + end, + [F(T) || T <- L], + file:write_file(Filename, + list_to_binary(io_lib:format(' {null,""}], \t\n', [])), + [append]), + + file:write_file(Filename, + list_to_binary(io_lib:format(' case lists:keysearch(Table_name,1, Table_fileds) of \t\n', [])), + [append]), + file:write_file(Filename, + list_to_binary(io_lib:format(' {value,{_, Val}} -> Val; \t\n', [])), + [append]), + file:write_file(Filename, + list_to_binary(io_lib:format(' _ -> undefined \t\n', [])), + [append]), + file:write_file(Filename, + list_to_binary(io_lib:format(' end. \t\n', [])), + [append]), + ok + end + catch + _:_ -> fail + end. + + +%% -------------------------------------------------- +%% time format +one_to_two(One) -> io_lib:format("~2..0B", [One]). + +%% @doc get the time's seconds for integer type +%% @spec get_seconds(Time) -> integer() +get_seconds(Time) -> + {_MegaSecs, Secs, _MicroSecs} = Time, + Secs. + +time_format(Now) -> + {{Y, M, D}, {H, MM, S}} = calendar:now_to_local_time(Now), + lists:concat([Y, "-", one_to_two(M), "-", one_to_two(D), " ", + one_to_two(H), ":", one_to_two(MM), ":", one_to_two(S)]). +date_format(Now) -> + {{Y, M, D}, {_H, _MM, _S}} = calendar:now_to_local_time(Now), + lists:concat([Y, "-", one_to_two(M), "-", one_to_two(D)]). +date_hour_format(Now) -> + {{Y, M, D}, {H, _MM, _S}} = calendar:now_to_local_time(Now), + lists:concat([Y, "-", one_to_two(M), "-", one_to_two(D), " ", one_to_two(H)]). +date_hour_minute_format(Now) -> + {{Y, M, D}, {H, MM, _S}} = calendar:now_to_local_time(Now), + lists:concat([Y, "-", one_to_two(M), "-", one_to_two(D), " ", one_to_two(H), "-", one_to_two(MM)]). +%% split by - +minute_second_format(Now) -> + {{_Y, _M, _D}, {H, MM, _S}} = calendar:now_to_local_time(Now), + lists:concat([one_to_two(H), "-", one_to_two(MM)]). + +hour_minute_second_format(Now) -> + {{_Y, _M, _D}, {H, MM, S}} = calendar:now_to_local_time(Now), + lists:concat([one_to_two(H), ":", one_to_two(MM), ":", one_to_two(S)]). diff --git a/src/srvNodeMgr/tools/gameWorld/test/tools/table_to_erlang.erl b/src/srvNodeMgr/tools/gameWorld/test/tools/table_to_erlang.erl new file mode 100644 index 0000000..eb417dc --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/tools/table_to_erlang.erl @@ -0,0 +1,492 @@ +%%%-------------------------------------- +%%% @Module : game_gateway +%%% @Author : +%%% @Created : 2010.10.27 +%%% @Description: 将mysql数据表 转换成 erl record +%%% 生成文件: "../include/table_to_record.hrl" +-module(table_to_erlang). +-compile(export_all). + +%% +%% Include files +%% +-include("common.hrl"). +-include("table_to_record.hrl"). + +-define(CONFIG_FILE, "../config/gateway.config"). +-define(TMP_TABLE_PATH, "./tmptable/"). +-define(SRC_TABLE_PATH, "../src/table/"). +-define(BEAM_PATH, "./"). + +-define(TABLES_TPLS, [ + %数据库表名 Record名 %erlang文件名 %参数 + {temp_combat_attr, temp_combat_attr, tpl_combat_attr, [1, 2]}, + {temp_goods, temp_goods, tpl_goods, [1]}, + {temp_goods_contain, temp_goods_contain, tpl_goods_contain, [1]}, + {temp_goods_equipment, temp_goods_equipment, tpl_goods_equipment, [1]}, + {temp_goods_gem, temp_goods_gem, tpl_goods_gem, [1]}, + {temp_goods_suit, temp_goods_suit, tpl_goods_suit, [1, 2]}, + %%{temp_mon_layout, temp_mon_layout, data_scene_mon, [1]} , + %{temp_notice,temp_notice, []} , + %{temp_npc,temp_npc, []} , + {temp_npc_layout, temp_npc_layout, tpl_npc_layout, [2, 3]}, + {temp_skill, temp_skill, tpl_skill, [1]}, + {temp_skill_attr, temp_skill_attr, tpl_skill_attr, [2, 3]}, + {temp_task, tpl_task, tpl_task, [1]}, + %{temp_talk,temp_talk, temp_talk,[1]}, + {temp_buff, temp_buff, tpl_buff, [1]}, + {temp_drop_main, temp_drop_main, tpl_drop_main, [1]}, + {temp_drop_sub, temp_drop_sub, tpl_drop_sub, [1]}, + {temp_stren, temp_stren, tpl_stren, [1]}, + {temp_polish, temp_polish, tpl_polish, [1]}, + {temp_upgrade, temp_upgrade, tpl_upgrade, [1]}, + {temp_task_detail, temp_task_detail, tpl_task_detail, [1]}, + {temp_all_stren_reward, temp_all_stren_reward, tpl_all_stren_reward, [1]}, + {temp_polish_goods, temp_polish_goods, tpl_polish_goods, [1]}, + {temp_suit_reward, temp_suit_reward, tpl_suit_reward, [1, 2]}, + {temp_all_gem_reward, temp_all_gem_reward, tpl_all_gem_reward, [1]}, + {temp_gilding, temp_gilding, tpl_gilding, [1, 2]}, + {temp_gold_bag, temp_gold_bag, tpl_gold_bag, [1]}, + {temp_vip_bag, temp_vip_bag, tpl_vip_bag, [1]}, + {temp_god_tried, temp_god_tried, tpl_god_tried, [1]}, + {temp_compose, temp_compose, tpl_compose, [1]}, + {temp_npc_shop, temp_npc_shop, tpl_npc_shop, [1, 2]}, + {temp_meridian, tpl_meridian, tpl_meridian, [2, 3, 4]}, + {temp_bones, tpl_bones, tpl_bones, [1]}, + {temp_shop, temp_shop, tpl_shop, [1, 2]}, + {temp_activity, temp_activity, tpl_activity, [1]}, + {temp_activity_reward, temp_activity_reward, tpl_activity_reward, [1]}, + {temp_mount_attr, temp_mount_attr, tpl_mount_attr, [2, 3]}, + {temp_mount_medicine, temp_mount_medicine, tpl_mount_medicine, [1]}, + {temp_mount_quality, temp_mount_quality, tpl_mount_quality, [1]}, + {temp_mount_skill, temp_mount_skill, tpl_mount_skill, [2, 3]}, + {temp_label, temp_label, tpl_label, [1]}, + {temp_goods_buff, temp_goods_buff, tpl_goods_buff, [1]}, + {temp_cultivation, tpl_cultivation, tpl_cultivation, [1]}, + {temp_pet, temp_pet, tpl_pet, [1]}, + {temp_pet_quality, temp_pet_quality, tpl_pet_quality, [1]}, + {temp_pet_growth, temp_pet_growth, tpl_pet_growth, [1]}, + {temp_pet_aptitude, temp_pet_aptitude, tpl_pet_aptitude, [1]}, + {temp_pet_medicine, temp_pet_medicine, tpl_pet_medicine, [1]}, + {temp_dungeon_group, temp_dungeon_group, tpl_dungeon_group, [1]}, + {temp_dungeon, temp_dungeon, tpl_dungeon, [1]}, + {temp_dungeon_trigger, temp_dungeon_trigger, tpl_dungeon_trigger, [2, 3]}, + {temp_dungeon_obj, temp_dungeon_obj, tpl_dungeon_obj, [2, 3, 4]}, + {temp_rand_shop, temp_rand_shop, tpl_rand_shop, [1]}, + {temp_rand_shop_goods, temp_rand_shop_goods, tpl_rand_shop_goods, [1]}, + {temp_goods_facade, temp_goods_facade, tpl_goods_facade_ex, [1, 2]}, + {temp_goods_facade, temp_goods_facade, tpl_goods_facade, [1]}, + {temp_pet_skill_book, temp_pet_skill_book, tpl_pet_skill_book, [4]}, + {temp_mon_ai, temp_mon_ai, tpl_mon_ai, [1]}, + {temp_tips, temp_tips, tpl_tips, [1]}, + {temp_task_factor, temp_task_factor, tpl_task_factor, [1, 2]}, + {temp_level_bag, temp_level_bag, tpl_level_bag, [1]}, + {temp_energy, temp_energy, tpl_energy, [2, 3]}, + {temp_download_gift, temp_download_gift, tpl_download_gift, [1]}, + {temp_vip, temp_vip, tpl_vip, [1]}, + {temp_vip, temp_vip, tpl_vip2, [9]}, + {temp_guild_level, temp_guild_level, tpl_guild_level, [1]}, + {temp_charge, temp_charge, tpl_charge, [1]}, + {temp_guild_contribution, temp_guild_contribution, tpl_guild_contribution, [1]}, + {temp_pet_skill_list, temp_pet_skill_list, tpl_pet_skill_list, [1]}, + {temp_all_polish_reward, temp_all_polish_reward, tpl_all_polish_reward, [1]}, + {temp_skill_point, temp_skill_point, tpl_skill_point, [1]}, + {temp_task_daily, tpl_task_daily, tpl_task_daily, [2]}, + {temp_cdkey_awards, temp_cdkey_awards, tpl_cdkey_awards, [1]} +]). + +%%用于生成返回值为列表的函数 +-define(TABLES_LIST, [ + %数据库表名 %erlang文件名 %参数 %Id名 + {temp_task, tpl_task, [type], [tid]}, + {temp_task, tpl_task, [type, level], [tid]}, + {temp_dungeon, tpl_dungeon, [grp], [sid]}, + {temp_dungeon_trigger, tpl_dungeon_trigger, [sid], [sid, action]}, + {temp_mount_skill, tpl_mount_skill, [mount_level], [sid, level]}, + {temp_label, tpl_label, [type, condition_id], [leader_id]}, + {temp_activity, tpl_activity, [btype, stype], [id]}, + {temp_dungeon_obj, tpl_dungeon_obj, [dun_id], [dun_id, obj_id, action]}, + {temp_rand_shop_goods, tpl_rand_shop_goods, [goods_lv], [goods_id]}, + {temp_goods_facade, tpl_goods_facade_ex, [facade], [gtid, facade]}, + {temp_goods_facade, tpl_goods_facade, [facade], [gtid]}, + {temp_pet_skill_list, tpl_pet_skill_list, [type, condition_id], [list_id]}, + {temp_pet_skill_book, tpl_pet_skill_book, [sid, skill_level], [skill_book_id]} +%% {temp_task, tpl_task,[type,tid,ongoing_dialog],tid}, +%% {temp_task, tpl_task,[type],tid} , +%% {temp_skill_buff,tpl_skill_buff,[name],buff_id}, +%% {temp_task, tpl_task,[start_npc],tid} +]). +%% +%% Exported Functions +%% + +%% +%% API Functions +%% +start() -> + case get_db_config(?CONFIG_FILE) of + [Host, Port, User, Password, DB, Encode, _Conns] -> + start_erlydb(Host, Port, User, Password, DB), + mysql:start_link(?DB_SERVER, Host, Port, User, Password, DB, fun(_, _, _, _) -> ok end, Encode), + mysql:connect(?DB_SERVER, Host, Port, User, Password, DB, Encode, true), + tables_to_erlang(), + tables_to_erlang_list(), + ok; + _ -> mysql_config_fail + end, + halt(), + ok. + +get_db_config(Config_file) -> + {ok, [L]} = file:consult(Config_file), + {_, C} = lists:keyfind(gateway, 1, L), + {_, Mysql_config} = lists:keyfind(mysql_config, 1, C), + {_, Host} = lists:keyfind(host, 1, Mysql_config), + {_, Port} = lists:keyfind(port, 1, Mysql_config), + {_, User} = lists:keyfind(user, 1, Mysql_config), + {_, Password} = lists:keyfind(password, 1, Mysql_config), + {_, DB} = lists:keyfind(db, 1, Mysql_config), + {_, Encode} = lists:keyfind(encode, 1, Mysql_config), + {_, Conns} = lists:keyfind(conns, 1, Mysql_config), + [Host, Port, User, Password, DB, Encode, Conns]. + +%% +%% Local Functions +%% +start_erlydb(IP, Port, User, Password, Db) -> + erlydb:start(mysql, [{pool_id, erlydb_mysql}, + {hostname, IP}, + {port, Port}, + {username, User}, + {password, Password}, + {database, Db}, + {logfun, fun(_, _, _, _) -> ok end}, + {encoding, utf8}, + {pool_size, 10}]). + +%% @doc 生成指定的表名的beam文件 +%% @spec code_gen/0 +%% unilog_mysql_pool:code_gen() +code_gen() -> + code_gen(?TABLES_TPLS). + +code_gen(TableName) -> + TableList = writeTempFile(TableName), + erlydb:code_gen(TableList, {mysql, + [{allow_unsafe_statements, true}, + {skip_fk_checks, true}]}, + [debug_info, {skip_fk_checks, true}, + {outdir, "../ebin/"}]), + clearTempFile(), + ok. + +%% @doc 通过beam生成erl文件,方便开发查看模块方法 +%% 调用该方法之前,必须先调用code_gen()方法,生成表对应的beam文件 +%% @spec code_gen_src/0 +code_gen_src() -> + lists:foreach(fun(TableName) -> + Beam = lists:concat([?BEAM_PATH, TableName, ".beam"]), + case beam_lib:chunks(Beam, [abstract_code]) of + {ok, {_, [{abstract_code, {_, AC}}]}} -> + Code = erl_prettypr:format(erl_syntax:form_list(AC)), + file:write_file(lists:concat([?SRC_TABLE_PATH, TableName, ".erl"]), list_to_binary(Code)), + io:format("build beam:~p to erl:~p success.~n", [TableName, TableName]); + {error, beam_lib, Reason} -> + io:format("code_gen_erl_file error, reason:~p~n", [Reason]) + end + end, ?TABLES_TPLS). + +%% @doc 为指定的表名生成module文件,给code_gen/0 使用 +%% @spec writeTempFile/0 ->[TableFilePath] +%% eg: TableFilePath -> "./tmptable/tuser_friend_log.erl" +writeTempFile(TableName) -> + clearTempFile(), + ok = file:make_dir(?TMP_TABLE_PATH), + lists:map(fun(F) -> + Filename = + ?TMP_TABLE_PATH ++ atom_to_list(F) ++ ".erl", + Bytes = list_to_binary(io_lib:format("-module(~w).", [F])), + file:write_file(Filename, Bytes), + Filename + end, TableName). + +clearTempFile() -> + case file:list_dir(?TMP_TABLE_PATH) of + {ok, Filenames} -> + lists:foreach(fun(F) -> + file:delete(?TMP_TABLE_PATH ++ F) end, Filenames); + {error, _} -> ignore + end, + file:del_dir(?TMP_TABLE_PATH). + + +%% time format +one_to_two(One) -> io_lib:format("~2..0B", [One]). + +%% @doc get the time's seconds for integer type +%% @spec get_seconds(Time) -> integer() +get_seconds(Time) -> + {_MegaSecs, Secs, _MicroSecs} = Time, + Secs. + +time_format(Now) -> + {{Y, M, D}, {H, MM, S}} = calendar:now_to_local_time(Now), + lists:concat([Y, "-", one_to_two(M), "-", one_to_two(D), " ", + one_to_two(H), ":", one_to_two(MM), ":", one_to_two(S)]). +date_format(Now) -> + {{Y, M, D}, {_H, _MM, _S}} = calendar:now_to_local_time(Now), + lists:concat([Y, "-", one_to_two(M), "-", one_to_two(D)]). +date_hour_format(Now) -> + {{Y, M, D}, {H, _MM, _S}} = calendar:now_to_local_time(Now), + lists:concat([Y, "-", one_to_two(M), "-", one_to_two(D), " ", one_to_two(H)]). +date_hour_minute_format(Now) -> + {{Y, M, D}, {H, MM, _S}} = calendar:now_to_local_time(Now), + lists:concat([Y, "-", one_to_two(M), "-", one_to_two(D), " ", one_to_two(H), "-", one_to_two(MM)]). +%% split by - +minute_second_format(Now) -> + {{_Y, _M, _D}, {H, MM, _S}} = calendar:now_to_local_time(Now), + lists:concat([one_to_two(H), "-", one_to_two(MM)]). + +hour_minute_second_format(Now) -> + {{_Y, _M, _D}, {H, MM, S}} = calendar:now_to_local_time(Now), + lists:concat([one_to_two(H), ":", one_to_two(MM), ":", one_to_two(S)]). + +tables_to_erlang() -> + io:format("~nstart converting table to erlang data TABLES_TPLS ~n", []), + F = fun({TableName, RecordName, FileName, ParamList}) -> + table_to_erlang(atom_to_list(TableName), atom_to_list(RecordName), atom_to_list(FileName), ParamList) + end, + lists:foreach(F, ?TABLES_TPLS). + +table_to_erlang(TableName, RecordName, FileName, ParamList) -> + io:format("~s => ~s.erl, \tTable fields ~p as parametes~n", [TableName, FileName, ParamList]), + DataFileName = lists:concat(["../src/data/", FileName, ".erl"]), + %Bakfile = re:replace(lists:flatten(lists:concat([DataFileName , "_", time_format(now())])),"[ :]","_",[global,{return,list}]), + %file:rename(DataFileName, Bakfile), + file:write_file(DataFileName, ""), + file:write_file(DataFileName, "%%%------------------------------------------------\t\n", [append]), + FileBytes = list_to_binary(io_lib:format("%%% File : ~s.erl\t\n", [FileName])), + file:write_file(DataFileName, FileBytes, [append]), + file:write_file(DataFileName, "%%% Author : table_to_erlang\t\n", [append]), + %Bytes = list_to_binary(io_lib:format("%%% Created : ~s\t\n", [time_format(now())])), + Bytes = list_to_binary("%%% Created : \n"), + file:write_file(DataFileName, Bytes, [append]), + TableNameBytes = list_to_binary(io_lib:format("%%% Description:从数据库表~s生成\n", [TableName])), + file:write_file(DataFileName, TableNameBytes, [append]), + file:write_file(DataFileName, "%%% WARNING:程序生成,请不要增加手工代码!\n", [append]), + file:write_file(DataFileName, "%%%------------------------------------------------ \t\n", [append]), + file:write_file(DataFileName, " \t\n", [append]), + ModuleName = lists:concat(["-module(", FileName, ")."]), + file:write_file(DataFileName, ModuleName, [append]), + file:write_file(DataFileName, " \t\n", [append]), + file:write_file(DataFileName, "-compile(export_all).", [append]), + file:write_file(DataFileName, " \t\n", [append]), + + %%从MYSQL查表所有内容 + Sql = io_lib:format("select * from ~s;", [TableName]), + Lists = db_esql:get_all(Sql), + TableRecordAtom = list_to_atom(RecordName), + F = fun(ValueList) -> + list_to_tuple([TableRecordAtom | ValueList]) + end, + RecordList = lists:map(F, Lists), + %[Key1|T] = ParamList, + %SortedRecordList = lists:keysort(Key1+1, RecordList), + SortedRecordList = lists:sort(RecordList), + F2 = fun(Record) -> + record_to_erlang(DataFileName, Record, ParamList) + end, + lists:foreach(F2, SortedRecordList), + record_to_erlang_end(DataFileName, ParamList). + +%%转换表到Erlang文件, Record为数据库一条记录对应的Record +%%DataFileName为文件名, ParamList为入口参数列表[] +%%如get(Level, Career), ParamList应该指定 Level,Career在数据表位置 +record_to_erlang(DataFileName, Record, ParamList) -> + [RecordName | ValueList] = tuple_to_list(Record), + F1 = fun(Index) -> + Idx = lists:nth(Index, ParamList), + Value = lists:nth(Idx, ValueList), + if Index =:= length(ParamList) -> + Bytes = lists:concat([integer_to_list(Value), ")->\n\t"]); + true -> + Bytes = lists:concat([integer_to_list(Value), ", "]) + end, + file:write_file(DataFileName, list_to_binary(Bytes), [append]) + end, + + %%写get(xxx,xxx) -> + file:write_file(DataFileName, "\t\n", [append]), + file:write_file(DataFileName, "get(", [append]), + lists:foreach(F1, lists:seq(1, length(ParamList))), + + %%写 {record_name, + file:write_file(DataFileName, list_to_binary(io_lib:format("{~s, ", [RecordName])), [append]), + F2 = fun(Index2) -> + Value2 = lists:nth(Index2, ValueList), + if is_integer(Value2) -> + if Index2 =:= length(ValueList) -> + file:write_file(DataFileName, list_to_binary(io_lib:format("~p};", [Value2])), [append]); + true -> + file:write_file(DataFileName, list_to_binary(io_lib:format("~p, ", [Value2])), [append]) + end; + %%列表类型(字符串) + is_list(Value2) orelse is_binary(Value2) -> + Value3 = case is_binary(Value2) of + true -> binary_to_list(Value2); + false -> Value2 + end, + if Index2 =:= length(ValueList) -> + %%检查是否是[(91),{(123),"(34)开头,如果是, 不在前面加引号, 否则输出字符串时加引号 + case length(Value3) >= 1 andalso + (lists:nth(1, Value3) =:= 91 orelse lists:nth(1, Value3) =:= 123 orelse lists:nth(1, Value3) =:= 34) of + true -> + file:write_file(DataFileName, list_to_binary(io_lib:format("~s};", [Value3])), [append]); + false -> + file:write_file(DataFileName, list_to_binary(io_lib:format("<<\"~s\">>};", [Value3])), [append]) + end; + true -> + %%检查是否是[(91),{(123),"(34)开头,如果是, 不在前面加引号, 否则输出字符串时加引号 + case length(Value3) >= 1 andalso + (lists:nth(1, Value3) =:= 91 orelse lists:nth(1, Value3) =:= 123 orelse lists:nth(1, Value3) =:= 34) of + true -> + file:write_file(DataFileName, list_to_binary(io_lib:format("~s,", [Value3])), [append]); + false -> + file:write_file(DataFileName, list_to_binary(io_lib:format("<<\"~s\">>, ", [Value3])), [append]) + end + end; + true -> + if Index2 =:= length(ValueList) -> + file:write_file(DataFileName, list_to_binary(io_lib:format("~p};", [Value2])), [append]); + true -> + file:write_file(DataFileName, list_to_binary(io_lib:format("~p, ", [Value2])), [append]) + end + end + + end, + lists:foreach(F2, lists:seq(1, length(ValueList))). + +%% %%写get(_,_, ...) -> []. +record_to_erlang_end(DataFileName, ParamList) -> + F = fun(Index) -> + if Index =:= length(ParamList) -> + Bytes = "_)->\t\n"; + true -> + Bytes = "_, " + end, + file:write_file(DataFileName, list_to_binary(Bytes), [append]) + end, + file:write_file(DataFileName, "\t\n", [append]), + file:write_file(DataFileName, "get(", [append]), + lists:foreach(F, lists:seq(1, length(ParamList))), + file:write_file(DataFileName, "\t[].\t\n", [append]). + + +%%============将数据库中的列表转换为erlang列表============= +tables_to_erlang_list() -> + io:format("~nstart converting table to erlang data list~n", []), + F = fun({TableName, FileName, ParamList, IdList}) -> + conver_start(atom_to_list(TableName), atom_to_list(FileName), ParamList, list_to_string(IdList)) + end, + lists:foreach(F, ?TABLES_LIST). +%%获取列表宏的元素,逐一操作数据表 +conver_start(TableName, FileName, ParamList, IdList) -> + io:format("~s => ~s.erl, \tTable fields ~p as parametes~n", [TableName, FileName, ParamList]), + DataFileName = lists:concat(["../src/data/", FileName, ".erl"]), + get_filter_data(DataFileName, TableName, ParamList, IdList, FileName). + +get_filter_data(DataFileName, TableName, ParamList, IdList, FileName) -> + F = fun(Param, Result) -> + case Result of + 0 -> lists:concat([Param]); + _ -> lists:concat([Result, ",", Param]) end end, + Res = lists:foldl(F, 0, ParamList), + Sql = io_lib:format("select distinct ~s from ~s;", [Res, TableName]), + Lists = db_esql:get_all(Sql), + lists:foreach(fun(Obj) -> + construts_data(DataFileName, TableName, ParamList, Obj, IdList, FileName) end, Lists), + EndRes = lists:foldl(fun(_Item, Sum) -> + case Sum of + 0 -> ["_"]; + _ -> Sum ++ ["_"] end end, 0, ParamList), + make_fun_head(DataFileName, ParamList, EndRes), + file:write_file(DataFileName, " [].\t\n", [append]). + + +%%构造erlang函数 +construts_data(DataFileName, TableName, ParamList, Res, IdList, FileName) -> + Filter = for(ParamList, Res, 0, length(ParamList), []), + Sql = io_lib:format("select ~s from ~s ~s;", [IdList, TableName, Filter]), + Lists = db_esql:get_all(Sql), + + %%写get(xxx,xxx) -> + make_fun_head(DataFileName, ParamList, Res), + file:write_file(DataFileName, " lists:map(fun([", [append]), + file:write_file(DataFileName, string:to_upper(IdList), [append]), + file:write_file(DataFileName, "])->", [append]), + file:write_file(DataFileName, FileName, [append]), + file:write_file(DataFileName, ":get(", [append]), + file:write_file(DataFileName, string:to_upper(IdList), [append]), + file:write_file(DataFileName, ") end,\n\t", [append]), + file:write_file(DataFileName, term_to_string(Lists), [append]), + file:write_file(DataFileName, ");\t\n", [append]). +%%构造函数头 +make_fun_head(DataFileName, ParamList, Res) -> + file:write_file(DataFileName, "\t\n", [append]), + file:write_file(DataFileName, "get_by", [append]), + lists:foreach(fun(Item) -> + file:write_file(DataFileName, lists:concat(["_", Item]), [append]) end, ParamList), + file:write_file(DataFileName, "(", [append]), + lists:foldl(fun(Index, Sum) -> + Value = lists:nth(Index, Res), + R = case is_binary(Value) of + true -> + lists:concat(["\"", binary_to_list(Value), "\""]); + false -> + Value end, + case Sum of + 0 -> + file:write_file(DataFileName, lists:concat([R]), [append]); + _ -> + file:write_file(DataFileName, lists:concat([",", R]), [append]) end + end, 0, lists:seq(1, length(ParamList))), + file:write_file(DataFileName, ")->\t\n", [append]). +%%构造where语句 +for(_ParamList, _ResList, _Index, _Index, Out) -> + Out; +for(ParamList, ResList, I, Index, Out) -> + [Pamram | PRest] = ParamList, + + [TempRes | RRest] = ResList, + Res = convert_bin_4(TempRes), + Sql = case I of + 0 -> + lists:concat([" where ", Pamram, "=", Res]); + _ -> + lists:concat([Out, " and ", Pamram, "=", Res]) end, + for(PRest, RRest, I + 1, Index, Sql). + +convert_bin_4(In) -> + case is_binary(In) of + true -> + lists:concat(["'", binary_to_list(In), "'"]); + false -> + In end. + +%%将列表转换为string [a,b,c] -> "a,b,c" +list_to_string(List) -> + case List == [] orelse List == "" of + true -> ""; + false -> + F = fun(E) -> + atom_to_list(E) ++ "," + end, + L1 = [F(E) || E <- List], + L2 = lists:concat(L1), + string:substr(L2, 1, length(L2) - 1) + end. + +%% term序列化,term转换为string格式,e.g., [{a},1] => "[{a},1]" +term_to_string(Term) -> + binary_to_list(list_to_binary(io_lib:format("~w", [Term]))). + diff --git a/src/srvNodeMgr/tools/gameWorld/test/tools/table_to_record.erl b/src/srvNodeMgr/tools/gameWorld/test/tools/table_to_record.erl new file mode 100644 index 0000000..841cb68 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/tools/table_to_record.erl @@ -0,0 +1,552 @@ +%%%-------------------------------------- +%%% @Module : game_gateway +%%% @Author : csj +%%% @Created : 2010.10.27 +%%% @Description: 将mysql数据表 转换成 erl record +%%% 生成文件: "../include/table_to_record.hrl" +%%%-------------------------------------- +-module(table_to_record). + +%% +%% Include files +%% +-include("common.hrl"). + +-define(CONFIG_FILE, "../config/gateway.config"). + +-define(TMP_TABLE_PATH, "./tmptable/"). +-define(SRC_TABLE_PATH, "../src/table/"). +-define(RECORD_FILENAME, "../include/table_to_record.hrl"). +-define(BEAM_PATH, "./"). + +-define(TABLES, + [ + {server, server}, + {config_server, config_server}, + {server_player, server_player}, + {player, player}, + {goods, goods}, + {goods_attribute, goods_attribute}, + {skill, skill}, + {system_config, system_config}, + {feedback, feedback}, + {temp_combat_attr, temp_combat_attr}, + {temp_goods, temp_goods}, + {temp_goods_contain, temp_goods_contain}, + {temp_goods_equipment, temp_goods_equipment}, + {temp_goods_gem, temp_goods_gem}, + {temp_goods_suit, temp_goods_suit}, + {temp_mon_layout, temp_mon_layout}, + {temp_notice, temp_notice}, + {temp_npc, temp_npc}, + {temp_npc_layout, temp_npc_layout}, + {temp_scene, temp_scene}, + {temp_skill, temp_skill}, + {temp_buff, temp_buff}, + {temp_skill_attr, temp_skill_attr}, + {mount, mount}, + {leader, leader}, + {activity, activity}, + {bubble_msg, bubble_msg}, + {business_announce, business_announce}, + {contact, contact}, + {relation, relation}, + {temp_drop_main, temp_drop_main}, + {temp_drop_sub, temp_drop_sub}, + {temp_task, tpl_task}, %任务模板 + {temp_task_detail, temp_task_detail},%任务模板子表 + {task_finish, task_finish}, %已完成任务 + {task_process, task_process},%任务进度 + {temp_stren, temp_stren}, + {temp_compose, temp_compose}, + {temp_polish, temp_polish}, + {temp_upgrade, temp_upgrade}, + {temp_all_stren_reward, temp_all_stren_reward}, + {casting_polish, casting_polish}, + {temp_polish_goods, temp_polish_goods}, + {temp_suit_reward, temp_suit_reward}, + {temp_all_gem_reward, temp_all_gem_reward}, + {temp_gilding, temp_gilding}, + {temp_gold_bag, temp_gold_bag}, + {temp_level_bag, temp_level_bag}, + {temp_vip_bag, temp_vip_bag}, + {temp_god_tried, temp_god_tried}, + {guild, guild}, + {guild_member, guild_member}, + {guild_apply, guild_apply}, + {buy_npc_shop_log, buy_npc_shop_log}, + {temp_npc_shop, temp_npc_shop}, + {goods_cd, goods_cd}, + {temp_meridian, tpl_meridian}, + {temp_bones, tpl_bones}, + {meridian, meridian}, + {bones, bones}, + {temp_shop, temp_shop}, + {buy_shop_log, buy_shop_log}, + {pet, pet}, + {temp_mount_attr, temp_mount_attr}, + {temp_mount_skill, temp_mount_skill}, + {temp_label, temp_label}, + {temp_activity, temp_activity}, + {temp_activity_reward, temp_activity_reward}, + {buff, buff}, + {temp_cultivation, tpl_cultivation}, + {cultivation, cultivation}, + {temp_goods_buff, temp_goods_buff}, + {temp_pet, temp_pet}, + {temp_pet_quality, temp_pet_quality}, + {temp_pet_growth, temp_pet_growth}, + {temp_pet_aptitude, temp_pet_aptitude}, + {temp_pet_medicine, temp_pet_medicine}, + {temp_goods_facade, temp_goods_facade}, + {temp_dungeon_group, temp_dungeon_group}, + {temp_dungeon, temp_dungeon}, + {temp_dungeon_trigger, temp_dungeon_trigger}, + {temp_dungeon_obj, temp_dungeon_obj}, + {dungeon_daily, dungeon_daily}, + {dungeon_finish, dungeon_finish}, + {dungeon_master, dungeon_master}, + {temp_pet_skill_book, temp_pet_skill_book}, + {temp_mon_ai, temp_mon_ai}, + {ban_account_list, ban_account_list}, + {ban_ip_list, ban_ip_list}, + {sys_announce, sys_announce}, + {temp_tips, temp_tips}, + {task_master, task_master}, + {heaven, heaven}, + {task_heaven, task_heaven}, + {temp_task_factor, temp_task_factor}, + {task_daily, task_daily}, + {temp_energy, temp_energy}, + {opera, opera}, + {scene_gift, scene_gift}, + {temp_download_gift, temp_download_gift}, + {temp_vip, temp_vip}, + {temp_rand_shop, temp_rand_shop}, + {temp_rand_shop_goods, temp_rand_shop_goods}, + {rand_shop, rand_shop}, + {market_selling, market_selling}, + {market_request, market_request}, + {temp_guild_level, temp_guild_level}, + {temp_charge, temp_charge}, + {temp_guild_contribution, temp_guild_contribution}, + {temp_pet_skill_list, temp_pet_skill_list}, + {temp_all_polish_reward, temp_all_polish_reward}, + {temp_skill_point, temp_skill_point}, + {temp_task_daily, tpl_task_daily}, + {temp_cdkey_awards, temp_cdkey_awards} + ]). + + +-record(erlydb_field, +{name, name_str, name_bin, type, modifier, erl_type, + html_input_type, + null, key, + default, extra, attributes}). +%% +%% Exported Functions +%% +-compile(export_all). + +%% +%% API Functions +%% +start() -> + case get_db_config(?CONFIG_FILE) of + [Host, Port, User, Password, DB, Encode, _Conns] -> + start_erlydb(Host, Port, User, Password, DB), + mysql:start_link(?DB_SERVER, Host, Port, User, Password, DB, fun(_, _, _, _) -> ok end, Encode), + mysql:connect(?DB_SERVER, Host, Port, User, Password, DB, Encode, true), + tables_to_record(), +%% get_date_box(), +%% make_data_box:get_data_box(), + ok; + _ -> + mysql_config_fail + end, + halt(), + ok. + +get_db_config(Config_file) -> + {ok, [L]} = file:consult(Config_file), + {_, C} = lists:keyfind(gateway, 1, L), + {_, Mysql_config} = lists:keyfind(mysql_config, 1, C), + {_, Host} = lists:keyfind(host, 1, Mysql_config), + {_, Port} = lists:keyfind(port, 1, Mysql_config), + {_, User} = lists:keyfind(user, 1, Mysql_config), + {_, Password} = lists:keyfind(password, 1, Mysql_config), + {_, DB} = lists:keyfind(db, 1, Mysql_config), + {_, Encode} = lists:keyfind(encode, 1, Mysql_config), + {_, Conns} = lists:keyfind(conns, 1, Mysql_config), + [Host, Port, User, Password, DB, Encode, Conns]. + +%% +%% Local Functions +%% +start_erlydb(IP, Port, User, Password, Db) -> + erlydb:start(mysql, [{pool_id, erlydb_mysql}, + {hostname, IP}, + {port, Port}, + {username, User}, + {password, Password}, + {database, Db}, + {logfun, fun(_, _, _, _) -> ok end}, + {encoding, utf8}, + {pool_size, 10}]). + +%% @doc 生成指定的表名的beam文件 +%% @spec code_gen/0 +%% unilog_mysql_pool:code_gen() + +code_gen() -> + code_gen(?TABLES). + +code_gen(TableName) -> + TableList = writeTempFile(TableName), +%% io:format("TableList=~p~n~n",[TableList]), + erlydb:code_gen(TableList, {mysql, + [{allow_unsafe_statements, true}, + {skip_fk_checks, true}]}, + [debug_info, {skip_fk_checks, true}, + {outdir, "../ebin/"}]), + clearTempFile(), + ok. + +%% @doc 通过beam生成erl文件,方便开发查看模块方法 +%% 调用该方法之前,必须先调用code_gen()方法,生成表对应的beam文件 +%% @spec code_gen_src/0 +code_gen_src() -> + lists:foreach(fun(TableName) -> + Beam = lists:concat([?BEAM_PATH, TableName, ".beam"]), + case beam_lib:chunks(Beam, [abstract_code]) of + {ok, {_, [{abstract_code, {_, AC}}]}} -> + Code = erl_prettypr:format(erl_syntax:form_list(AC)), + file:write_file(lists:concat([?SRC_TABLE_PATH, TableName, ".erl"]), list_to_binary(Code)), + io:format("build beam:~p to erl:~p success.~n", [TableName, TableName]); + {error, beam_lib, Reason} -> + io:format("code_gen_erl_file error, reason:~p~n", [Reason]) + end + end, ?TABLES). + +%% @doc 为指定的表名生成module文件,给code_gen/0 使用 +%% @spec writeTempFile/0 ->[TableFilePath] +%% eg: TableFilePath -> "./tmptable/tuser_friend_log.erl" +writeTempFile(TableName) -> + clearTempFile(), + ok = file:make_dir(?TMP_TABLE_PATH), + lists:map(fun(F) -> + Filename = + ?TMP_TABLE_PATH ++ atom_to_list(F) ++ ".erl", + Bytes = list_to_binary(io_lib:format("-module(~w).", [F])), + file:write_file(Filename, Bytes), + Filename + end, TableName). + +clearTempFile() -> + case file:list_dir(?TMP_TABLE_PATH) of + {ok, Filenames} -> + lists:foreach(fun(F) -> + file:delete(?TMP_TABLE_PATH ++ F) end, Filenames); + {error, _} -> ignore + end, + file:del_dir(?TMP_TABLE_PATH). + +tables_to_record() -> + io:format("starting table to record ...~n"), + Bakfile = re:replace( + lists:flatten(lists:concat([?RECORD_FILENAME, "_", time_format(now())])), + "[ :]", "_", [global, {return, list}]), + lists:flatten(lists:concat([?RECORD_FILENAME, "_", time_format(now())])), + file:rename(?RECORD_FILENAME, Bakfile), + + file:write_file(?RECORD_FILENAME, ""), + file:write_file(?RECORD_FILENAME, "%%%------------------------------------------------\t\n", [append]), + file:write_file(?RECORD_FILENAME, "%%% File : table_to_record.erl\t\n", [append]), + file:write_file(?RECORD_FILENAME, "%%% Author : smxx\t\n", [append]), + Bytes = list_to_binary(io_lib:format("%%% Created : ~s\t\n", [time_format(now())])), + file:write_file(?RECORD_FILENAME, Bytes, [append]), + file:write_file(?RECORD_FILENAME, "%%% Description: 从mysql表生成的record\t\n", [append]), + file:write_file(?RECORD_FILENAME, "%%% Warning: 由程序自动生成,请不要随意修改!\t\n", [append]), + file:write_file(?RECORD_FILENAME, "%%%------------------------------------------------ \t\n", [append]), + file:write_file(?RECORD_FILENAME, " \t\n", [append]), + + io:format("~n~n"), + + lists:foreach(fun(Table) -> + case Table of + {Table_name, Record_name} -> table_to_record(Table_name, Record_name, ""); + {Table_name, Record_name, TableComment} -> table_to_record(Table_name, Record_name, TableComment); + _ -> no_action + end + end, + ?TABLES), + io:format("finished!~n~n"), + ok. + +%% table_to_record:table_to_record(user, 1). +%% [A,B]=db_esql:get_row("show create table user;") +%% db_esql:get_row("select * from base_goods_type;") +table_to_record(Table_name, Record_name, TableComment) -> + file:write_file(?RECORD_FILENAME, "\t\n", [append]), + Sql = lists:concat(["show create table ", Table_name]), + try + case db_esql:get_row(Sql) of + {db_error, _} -> + error; + [_, A | _] -> + Create_table_list = re:split(A, "[\n]", [{return, binary}]), + Table_comment = + case TableComment of + "" -> get_table_comment(Create_table_list, Table_name); + _ -> TableComment + end, + file:write_file(?RECORD_FILENAME, + list_to_binary(io_lib:format("%% ~s\t\n", [Table_comment])), + [append]), + file:write_file(?RECORD_FILENAME, + list_to_binary(io_lib:format("%% ~s ==> ~s \t\n", [Table_name, Record_name])), + [append]), + file:write_file(?RECORD_FILENAME, + list_to_binary(io_lib:format("-record(~s, {\t\n", [Record_name])), + [append]), + code_gen([Table_name]), + Table_fields = erlang:apply(Table_name, db_fields, []), + lists:mapfoldl(fun(Field, Sum) -> + Field_comment = get_field_comment(Create_table_list, Sum), + Default = + case Field#erlydb_field.default of + undefined -> ''; + <<>> -> + case erlydb_field:get_erl_type(Field#erlydb_field.type) of + binary -> + lists:concat([" = \"\""]); + integer -> + lists:concat([" = 0"]); + _ -> '' + end; + <<"[]">> -> + lists:concat([" = ", binary_to_list(Field#erlydb_field.default)]); + Val -> + case erlydb_field:get_erl_type(Field#erlydb_field.type) of + binary -> + lists:concat([" = <<\"", binary_to_list(Val), "\">>"]); + _ -> + lists:concat([" = ", binary_to_list(Val)]) + end + end, + T1 = + if Sum == length(Table_fields) -> + ''; + true -> ',' + end, + T2 = io_lib:format("~s~s~s", + [Field#erlydb_field.name, + Default, + T1]), + T3 = lists:duplicate(40 - length(lists:flatten(T2)), " "), + Bytes = list_to_binary(io_lib:format(" ~s~s%% ~s\t\n", + [T2, + T3, + Field_comment])), + file:write_file(?RECORD_FILENAME, + Bytes, + [append]), + { + [], + Sum + 1 + } + end, + 1, Table_fields), + + file:write_file(?RECORD_FILENAME, + list_to_binary(io_lib:format(" }).\t\n", [])), + [append]), + io:format(" ~s ==> ~s ~n", [Table_name, Record_name]), + ok + end + catch + _:_ -> + io:format("error when getting ~s ==> ~s ~n", [Table_name, Record_name]), + error + end. + +get_field_comment(Create_table_list, Loc) -> + try +%% L1 = re:split(lists:nth(Loc+1, Create_table_list),"[ ]",[{return, list}]), + L1 = binary_to_list(lists:nth(Loc + 1, Create_table_list)), +%% io:format("L1 = ~p ~n", [L1]), + Loc1 = string:rstr(L1, "COMMENT "), +%% io:format("Loc = ~p ~n", [Loc1]), + case Loc1 > 0 of + true -> + L2 = string:substr(L1, Loc1 + 8), + L3 = lists:subtract(L2, [39, 44]), + lists:subtract(L3, [39]); + _ -> "" + end + catch + _:_ -> "" + end. + +get_table_comment(Create_table_list, Table_name) -> + try +%% L1 = re:split(lists:nth(Loc+1, Create_table_list),"[ ]",[{return, list}]), + Len = length(Create_table_list), + L1 = binary_to_list(lists:nth(Len, Create_table_list)), +%% io:format("L1 = ~p ~n", [L1]), + Loc1 = string:rstr(L1, "COMMENT="), +%% io:format("Loc = ~p ~n", [Loc1]), + case Loc1 > 0 of + true -> + L2 = string:substr(L1, Loc1 + 8), + L3 = lists:subtract(L2, [39, 44]), + lists:subtract(L3, [39]); + _ -> Table_name + end + catch + _:_ -> Table_name + end. + +%% time format +one_to_two(One) -> io_lib:format("~2..0B", [One]). + +%% @doc get the time's seconds for integer type +%% @spec get_seconds(Time) -> integer() +get_seconds(Time) -> + {_MegaSecs, Secs, _MicroSecs} = Time, + Secs. + +time_format(Now) -> + {{Y, M, D}, {H, MM, S}} = calendar:now_to_local_time(Now), + lists:concat([Y, "-", one_to_two(M), "-", one_to_two(D), " ", + one_to_two(H), ":", one_to_two(MM), ":", one_to_two(S)]). +date_format(Now) -> + {{Y, M, D}, {_H, _MM, _S}} = calendar:now_to_local_time(Now), + lists:concat([Y, "-", one_to_two(M), "-", one_to_two(D)]). +date_hour_format(Now) -> + {{Y, M, D}, {H, _MM, _S}} = calendar:now_to_local_time(Now), + lists:concat([Y, "-", one_to_two(M), "-", one_to_two(D), " ", one_to_two(H)]). +date_hour_minute_format(Now) -> + {{Y, M, D}, {H, MM, _S}} = calendar:now_to_local_time(Now), + lists:concat([Y, "-", one_to_two(M), "-", one_to_two(D), " ", one_to_two(H), "-", one_to_two(MM)]). +%% split by - +minute_second_format(Now) -> + {{_Y, _M, _D}, {H, MM, _S}} = calendar:now_to_local_time(Now), + lists:concat([one_to_two(H), "-", one_to_two(MM)]). + +hour_minute_second_format(Now) -> + {{_Y, _M, _D}, {H, MM, S}} = calendar:now_to_local_time(Now), + lists:concat([one_to_two(H), ":", one_to_two(MM), ":", one_to_two(S)]). + + +%% ------------------------------------------------------------------------------------------------------ +%% *********************************从mysql表生成的诛邪系统物品概率碰撞表 start **************************** +%% ------------------------------------------------------------------------------------------------------ +get_date_box() -> + Careers = lists:seq(1, 5), + lists:map(fun get_data_box_career/1, Careers). +get_data_box_career(Career) -> +%% DataFileName = lists:concat(["../src/data/data_box_", Career, ".erl"]), +%% Bakfile = re:replace( +%% lists:flatten(lists:concat([DataFileName , "_", time_format(now())])), +%% "[ :]","_",[global,{return,list}]), +%% +%% file:rename(DataFileName, Bakfile), +%% +%% file:write_file(DataFileName, ""), +%% file:write_file(DataFileName, "%%%------------------------------------------------\t\n",[append]), +%% file:write_file(DataFileName, "%%% File : data_box_X.erl\t\n",[append]), +%% file:write_file(DataFileName, "%%% Author : xiaomai\t\n",[append]), +%% Bytes = list_to_binary(io_lib:format("%%% Created : ~s\t\n", [time_format(now())])), +%% file:write_file(DataFileName, Bytes,[append]), +%% file:write_file(DataFileName, "%%% Description: 从mysql表生成的诛邪系统物品概率碰撞表\t\n",[append]), +%% file:write_file(DataFileName, "%%% Warning: 由程序自动生成,请不要随意修改!\t\n",[append]), +%% file:write_file(DataFileName, "%%%------------------------------------------------ \t\n",[append]), +%% file:write_file(DataFileName, " \t\n",[append]), +%% ModuleName = lists:concat(["-module(data_box_",Career,")."]), +%% file:write_file(DataFileName, ModuleName,[append]), +%% file:write_file(DataFileName, " \t\n\n",[append]), +%% file:write_file(DataFileName, "-export([get_goods_one/3]).",[append]), +%% file:write_file(DataFileName, " \t\n",[append]), +%% file:write_file(DataFileName, " \t\n",[append]), +%% file:write_file(DataFileName, "get_goods_one(HoleType, Career, RandomCount) ->\n\t",[append]), + Counts = lists:seq(1, 3), + lists:map(fun(Elem) -> handle_data_box_each(Career, Elem) end, Counts), +%% ErCodeend = "GoodsInfo = lists:concat([\"Goods_info_\", HoleType, Elem, \"00\"]), +%% %%注意这里的返回值 +%% {BaseGoodsId} = lists:nth(RandomCount, GoodsInfo), +%% BaseGoodsId.", +%% file:write_file(DataFileName, ErCodeend,[append]), + io:format("~n~n"). +%% handle_data_box_each(Elem, Career) -> +%% Sum = lists:seq(1,5), +%% lists:foldl(fun handle_data_box_each_one/2,{Elem}, Sum). +handle_data_box_each(Career, Elem) -> + DataFileName = lists:concat(["../src/data/data_box_", Career, Elem, ".erl"]), + Bakfile = re:replace( + lists:flatten(lists:concat([DataFileName, "_", time_format(now())])), + "[ :]", "_", [global, {return, list}]), + + file:rename(DataFileName, Bakfile), + file:write_file(DataFileName, ""), + file:write_file(DataFileName, "%%%------------------------------------------------\t\n", [append]), + file:write_file(DataFileName, "%%% File : data_box_XX.erl\t\n", [append]), + file:write_file(DataFileName, "%%% Author : xiaomai\t\n", [append]), + Bytes = list_to_binary(io_lib:format("%%% Created : ~s\t\n", [time_format(now())])), + file:write_file(DataFileName, Bytes, [append]), + file:write_file(DataFileName, "%%% Description: 从mysql表生成的诛邪系统物品概率碰撞表\t\n", [append]), + file:write_file(DataFileName, "%%% Warning: 由程序自动生成,请不要随意修改!\t\n", [append]), + file:write_file(DataFileName, "%%%------------------------------------------------ \t\n", [append]), + file:write_file(DataFileName, " \t\n", [append]), + ModuleName = lists:concat(["-module(data_box_", Career, Elem, ")."]), + file:write_file(DataFileName, ModuleName, [append]), + file:write_file(DataFileName, " \t\n\n", [append]), + file:write_file(DataFileName, "-export([get_goods_one/3]).", [append]), + file:write_file(DataFileName, " \t\n", [append]), + file:write_file(DataFileName, " \t\n", [append]), + file:write_file(DataFileName, "get_goods_one(HoleType, Career, RandomCount) ->\n\t", [append]), + + Sql = + io_lib:format("select a.pro, a.goods_id from `base_box_goods` a, `base_goods` b where hole_type = ~p and b.goods_id = a.goods_id and b.career in (0,~p) order by a.goods_id desc", + [Elem, Career]), + Lists = db_mysqlutil:get_all(Sql), + ElemName = lists:concat(["Goods_info_", Career, Elem, "00 = ["]), + file:write_file(DataFileName, ElemName, [append]), + {_NewCount, _FileName} = lists:foldl(fun make_content_goods/2, {1, DataFileName}, Lists), +%% io:format("the [~p]count is[~p]\n\n\n", [Career, NewCount]), +%% String = lists:concat(Result), +%% file:write_file(?FILENAME, string:substr(String,1, string:len(String)),[append]), + + file:write_file(DataFileName, "],\n\t", [append]), + ErCodeEndOne = " + %%注意这里的返回值 + {BaseGoodsId} = lists:nth(RandomCount, ", + ErCodeEndTwo = "), + BaseGoodsId.", + EndString = lists:concat([ErCodeEndOne, "Goods_info_", Career, Elem, "00", ErCodeEndTwo]), + file:write_file(DataFileName, EndString, [append]). + +make_content_goods(List, AccIn) -> + [Pro, GoodsId] = List, + {Count, DataFileName} = AccIn, + NewPro = Pro * 100000, + NewProInt = tool:to_integer(NewPro), + Sum = lists:seq(1, NewProInt), + {NewCount, _GodosId, Result} = lists:foldl(fun get_content_array/2, {Count, GoodsId, []}, Sum), + String = lists:concat(lists:reverse(Result)), + file:write_file(DataFileName, String, [append]), +%% file:write_file(DataFileName, "\n\t\t\t\t\t\t",[append]), +%% io:format("the elem is {~p,~p,~p,~p}\t\t", [Pro,NewProInt,NewCount,length(Result)]), + {NewCount, DataFileName}. +get_content_array(_Elem, AccIn) -> + {Count, GoodsId, ResultList} = AccIn, + case tool:to_integer(Count) =:= 100000 of + true -> + ResultElem = lists:concat(["{", GoodsId, "}"]); + false -> + ResultElem = lists:concat(["{", GoodsId, "},"]) + end, + {Count + 1, GoodsId, [ResultElem | ResultList]}. +%% ------------------------------------------------------------------------------------------------------ +%% *********************************从mysql表生成的诛邪系统物品概率碰撞表 end **************************** +%% ------------------------------------------------------------------------------------------------------ diff --git a/src/srvNodeMgr/tools/gameWorld/test/tools/task_data_checker.erl b/src/srvNodeMgr/tools/gameWorld/test/tools/task_data_checker.erl new file mode 100644 index 0000000..381edb1 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/tools/task_data_checker.erl @@ -0,0 +1,247 @@ +%% @author Administrator +%% @doc @todo Add description to task_data_checker. + + +-module(task_data_checker). + +%% ==================================================================== +%% API functions +%% ==================================================================== +-export([]). +-compile(export_all). +-include("common.hrl"). +-include("table_to_record.hrl"). +-include("task.hrl"). + +-define(CONFIG_FILE, "../config/gateway.config"). +-define(TMP_TABLE_PATH, "./tmptable/"). +-define(SRC_TABLE_PATH, "../src/table/"). +-define(BEAM_PATH, "./"). + +-define(IOFILE(Str, Args), (fun() -> + Command = io_lib:format(Str, Args), + file:write_file("../logs/data/task_data.log", Command, [append]) + end)()). +-define(IOFILE(Str), (fun() -> + file:write_file("../logs/data/task_data.log", Str, [append]) + end)()). +%% ==================================================================== +%% Internal functions +%% ==================================================================== + +start() -> + case table_to_erlang:get_db_config(?CONFIG_FILE) of + [Host, Port, User, Password, DB, Encode, _Conns] -> + table_to_erlang:start_erlydb(Host, Port, User, Password, DB), + mysql:start_link(?DB_SERVER, Host, Port, User, Password, DB, fun(_, _, _, _) -> ok end, Encode), + mysql:connect(?DB_SERVER, Host, Port, User, Password, DB, Encode, true), + start_check(), + ok; + _ -> mysql_config_fail + end, + halt(), + ok. + +start_check() -> + ?IOFILE("\n[信息]******************任务数据有效性检测开始********************** \n"), + ?IOFILE("[信息]***************日期:~p 时间:~p******************* ~n", [date(), time()]), + F = fun(Task) -> + D = list_to_tuple([tpl_task | Task]), + TaskInfo = D#tpl_task{ + goods_list = util:bitstring_to_term(D#tpl_task.goods_list), + target_property = util:bitstring_to_term(D#tpl_task.target_property), + guild_goods_list = util:bitstring_to_term(D#tpl_task.guild_goods_list) + }, + check_interface(TaskInfo) + end, + Lists = db_esql:get_all("select * from temp_task;"), + lists:foreach(F, Lists), + ?IOFILE("[信息]***************任务数据有效性检测结束******************* \n \n"). + +check_interface(TaskInfo) -> + check_start_npc_scene(TaskInfo#tpl_task.start_npc, TaskInfo#tpl_task.start_scene, TaskInfo#tpl_task.tid), + check_end_npc_scene(TaskInfo#tpl_task.end_npc, TaskInfo#tpl_task.end_scene, TaskInfo#tpl_task.tid), + check_next_task(TaskInfo#tpl_task.next_tid, TaskInfo#tpl_task.tid), + check_pid_task(TaskInfo#tpl_task.pre_tid, TaskInfo#tpl_task.tid), + check_goods_list(TaskInfo#tpl_task.goods_list, TaskInfo#tpl_task.tid, "goods_list"), + check_goods_list(TaskInfo#tpl_task.goods_list, TaskInfo#tpl_task.tid, "guild_goods_list"), + check_target_pro(TaskInfo#tpl_task.target_type, TaskInfo#tpl_task.target_property, TaskInfo#tpl_task.pre_tid). + +check_npc(NpcId, Type, Tid) -> + Sql = io_lib:format("select count(*) from temp_npc where nid = ~p;", [NpcId]), + case db_esql:get_all(Sql) of + [[0]] -> + ?IOFILE("[错误]任务 task id -> ~p 中, ~p npc数据 npc id -> ~p 无效 ~n", [Tid, Type, NpcId]); + _ -> + skip + end. + +check_scene(SceneId, Type, Tid) -> + Sql = io_lib:format("select count(*) from temp_scene where sid = ~p;", [SceneId]), + case db_esql:get_all(Sql) of + [[0]] -> + ?IOFILE("[错误]任务 task id -> ~p 中, ~p场景数据 id -> ~p 无效 ~n", [Tid, Type, SceneId]); + _ -> + skip + end. + +check_start_npc_scene(NpcId, SceneId, Tid) -> + if NpcId =:= 0 -> + skip; + true -> + check_npc(NpcId, "开始", Tid), + check_scene(SceneId, "开始", Tid), + Sql = io_lib:format("select count(*) from temp_npc_layout where scene_id = ~p and npcid = ~p;", [SceneId, NpcId]), + case db_esql:get_all(Sql) of + [[0]] -> + ?IOFILE("[警告]任务 task id -> ~p 中,任务开始场景id -> ~p 与 任务开始npc id -> ~p 不匹配(可能是跨地图任务,同事们自己留意下)~n", [Tid, SceneId, NpcId]); + _ -> skip + end + end. +check_end_npc_scene(NpcId, SceneId, Tid) -> + if NpcId =:= 0 -> + skip; + true -> + check_npc(NpcId, "结束", Tid), + check_scene(SceneId, "结束", Tid), + Sql = io_lib:format("select count(*) from temp_npc_layout where scene_id = ~p and npcid = ~p;", [SceneId, NpcId]), + case db_esql:get_all(Sql) of + [[0]] -> + ?IOFILE("[错误]任务 task id -> ~p 中,任务结束场景id -> ~p 与 任务结束npc id -> ~p 不匹配 ~n", [Tid, SceneId, NpcId]); + _ -> skip + end + end. +check_next_task(NextTid, Tid) -> + if NextTid =:= -1 -> + skip; + true -> + case NextTid =< Tid of + true -> + ?IOFILE("[错误]任务 task id -> ~p 中,后置任务 id -> ~p 必须比本任务id大 ~n", [Tid, NextTid]); + false -> + Sql = io_lib:format("select count(*) from temp_task where tid = ~p;", [NextTid]), + case db_esql:get_all(Sql) of + [[0]] -> + ?IOFILE("[错误]任务 task id -> ~p 中,后置任务 id -> ~p 无效~n", [Tid, NextTid]); + _ -> skip + end + end + end. + +check_pid_task(PerTid, Tid) -> + if PerTid =:= -1 -> + skip; + true -> + case PerTid >= Tid of + true -> + ?IOFILE("[错误]任务 task id -> ~p 中, 前置任务 id -> ~p 必须比本任务id小 ~n", [Tid, PerTid]); + false -> + Sql = io_lib:format("select count(*) from temp_task where tid = ~p;", [PerTid]), + case db_esql:get_all(Sql) of + [[0]] -> + ?IOFILE("[错误]任务 task id -> ~p 中,前置任务 id -> ~p 无效 ~n", [Tid, PerTid]); + _ -> skip + end + end + end. + +check_goods_list(GoodsList, Tid, Type) -> + F = fun({_, _, Gid, _}) -> + Sql = io_lib:format("select count(*) from temp_goods where gtid = ~p;", [Gid]), + case db_esql:get_all(Sql) of + [[0]] -> + ?IOFILE("[错误]任务 task id -> ~p 中,任务奖励物品 id -> ~p 无效 ,具体见字段: ~p ~n", [Tid, Gid, Type]); + _ -> skip + end + end, + lists:foreach(F, GoodsList). + +check_target_pro(?NPC_TALK_EVENT, TaskPro, Tid) -> + case TaskPro of + [{NpcId}] -> + Sql = io_lib:format("select count(*) from temp_npc where nid = ~p;", [NpcId]), + case db_esql:get_all(Sql) of + [[0]] -> + ?IOFILE("[错误]对话任务 task id -> ~p 中,目标npc -> ~p 无效,具体见字段 target_property ~n", [Tid, NpcId]); + _ -> skip + end; + _ -> + ?IOFILE("[错误]任务 task id -> ~p 中,字段 target_property 格式有误 ~n", [Tid]) + end; +check_target_pro(?KILL_EVENT, TaskPro, Tid) -> + case TaskPro of + [{MonId, _, 0}] -> + Sql = io_lib:format("select count(*) from temp_npc where nid = ~p;", [MonId]), + case db_esql:get_all(Sql) of + [[0]] -> + ?IOFILE("[错误]杀怪任务 task id -> ~p 中,怪物数据 monster id -> ~p 无效,具体见字段 target_property ~n", [Tid, MonId]); + _ -> skip + end; + _ -> + ?IOFILE("[错误]任务 task id -> ~p 中,字段 target_property 格式有误 ~n", [Tid]) + end; +check_target_pro(?COLLECT_EVENT, TaskPro, Tid) -> + + case TaskPro of + [{ItemId, _, 0}] -> + Sql = io_lib:format("select count(*) from temp_npc where nid = ~p;", [ItemId]), + case db_esql:get_all(Sql) of + [[0]] -> + ?IOFILE("[错误]采集任务 task id -> ~p 中, 采集物数据 item id -> ~p 无效,具体见字段 target_property ~n", [Tid, ItemId]); + _ -> + skip + end; + _ -> + ?IOFILE("[错误]任务 task id -> ~p 中,字段 target_property 格式有误 ~n", [Tid]) + end; +check_target_pro(?NPC_GOODS_EVENT, TaskPro, Tid) -> + case TaskPro of + [{NpcId, ItemId, _, 0}] -> + do_check_npc_shop_item(ItemId, Tid), + do_check_npc_shop_npc(NpcId, Tid), + do_check_npc_shop_data(NpcId, ItemId, Tid); + _ -> + ?IOFILE("[错误]任务 task id -> ~p 中,字段 target_property 格式有误 ~n", [Tid]) + end; + +check_target_pro(ERR1, Err2, Err3) -> + io:format("[ERROR]param of check_target_pro err ~p ~n", [{ERR1, Err2, Err3}]). + +do_check_npc_shop_item(ItemId, Tid) -> + Sql = io_lib:format("select count(*) from temp_goods where gtid = ~p;", [ItemId]), + case db_esql:get_all(Sql) of + [[0]] -> + ?IOFILE("[错误]npc购物任务 task id -> ~p 中, 商品数据 item id -> ~p 无效,具体见字段 target_property ~n", [Tid, ItemId]); + _ -> + skip + end. +do_check_npc_shop_npc(NpcId, Tid) -> + Sql = io_lib:format("select count(*) from temp_npc where nid = ~p;", [NpcId]), + case db_esql:get_all(Sql) of + [[0]] -> + ?IOFILE("[错误]npc购物任务 task id -> ~p 中, npc数据 npc id -> ~p 无效,具体见字段 target_property ~n", [Tid, NpcId]); + _ -> + skip + end. +do_check_npc_shop_data(NpcId, ItemId, Tid) -> + Sql = io_lib:format("select shop_goods from temp_npc_shop where shop_id = ~p;", [NpcId]), + case db_esql:get_all(Sql) of + [] -> + ?IOFILE("[错误]npc购物任务 task id -> ~p 中, npc数据 npc id -> ~p 没有商品,具体见 temp_npc_shop表 ~n", [Tid, NpcId]); + Data -> + ResultList = lists:map(fun(ShopGoos) -> + do_check_npc_shop_npc_item(ItemId, ShopGoos) + end, Data), + case lists:member(true, ResultList) of + true -> skip; + false -> + ?IOFILE("[错误]npc购物任务 task id -> ~p 中, npc数据 npc id -> ~p 没有商品 item id -> ~p,具体见 temp_npc_shop表 ~n", [Tid, NpcId, ItemId]) + end + end. + +do_check_npc_shop_npc_item(ItemId, ShopGoos) -> + NewShopGoods = util:bitstring_to_term(ShopGoos), + case lists:keyfind(ItemId, 1, NewShopGoods) of + false -> false; + _ -> true + end. diff --git a/src/srvNodeMgr/tools/gameWorld/test/tools/u.erl b/src/srvNodeMgr/tools/gameWorld/test/tools/u.erl new file mode 100644 index 0000000..82cd9d3 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/tools/u.erl @@ -0,0 +1,152 @@ +%%---------------------------------------------------- +%% Erlang模块热更新到所有线路(包括server的回调函数,如果对state有影响时慎用) +%% +%% 检查:u:c() %% 列出前5分钟内编译过的文件 +%% u:c(N) %% 列出前N分钟内编译过的文件 +%% +%% 更新:u:u() %% 更新前5分钟内编译过的文件 +%% u:u(N) %% 更新前N分钟内编译过的文件 +%% u:u([mod_xx, ...]) %% 指定模块(不带后缀名) +%% u:u(m) %% 编译并加载文件 +%% +%% Tips: u - update, c - check +%% +%% @author rolong@vip.qq.com +%%---------------------------------------------------- + +-module(u). +-compile(export_all). +-include_lib("kernel/include/file.hrl"). +-include("common.hrl"). +-include("record.hrl"). + +c() -> + c(5). +c(S) when is_integer(S) -> + c:cd("../ebin"), + case file:list_dir(".") of + {ok, FileList} -> + Files = get_new_file(FileList, S * 60), + info("---------check modules---------~n~w~n=========check modules=========", [Files]); + Any -> info("Error Dir: ~w", [Any]) + end; +c([S]) when is_atom(S) -> + S1 = tool:to_integer(tool:to_list(S)), + case is_integer(S1) of + true -> + c:cd("../ebin"), + case file:list_dir(".") of + {ok, FileList} -> + Files = get_new_file(FileList, S * 60), + info("---------check modules---------~n~w~n=========check modules=========", [Files]); + Any -> info("Error Dir: ~w", [Any]) + end; + _ -> + info("ERROR======> Badarg ~p/~p ~n", [S, S1]) + end; +c(S) -> info("ERROR======> Badarg ~p ~n", [S]). + +admin() -> + spawn(fun() -> u(m) end), + ok. + +u() -> + u(5). +u(m) -> + StartTime = util:unixtime(), + info("----------makes----------", []), + c:cd("../"), + make:all(), + c:cd("ebin"), + EndTime = util:unixtime(), + Time = EndTime - StartTime, + info("Make Time : ~w s", [Time]), + u(Time / 60); +u(S) when is_number(S) -> + case file:list_dir(".") of + {ok, FileList} -> + Files = get_new_file(FileList, util:ceil(S * 60) + 3), + load(Files), + try + AllZone = mod_disperse:server_list(), + info("---------modules---------~n~w~n----------nodes----------", [Files]), + loads(AllZone, Files) + catch + _:_ -> ok + end; + Any -> info("Error Dir: ~w", [Any]) + end; +u(Files) when is_list(Files) -> + AllZone = mod_disperse:server_list(), + info("---------modules---------~n~w~n----------nodes----------", [Files]), + load(Files), + loads(AllZone, Files); +u(_) -> info("ERROR======> Badarg", []). + +%% m(['src/data/*','src/lib/lib_goods.erl']) +m(Files) when is_list(Files) -> + StartTime = util:unixtime(), + info("----------makes----------~n~w~n", [Files]), + c:cd("../"), + Res = make:files(Files, [debug_info, {i, "include"}, {outdir, "ebin"}]), + c:cd("ebin"), + EndTime = util:unixtime(), + Time = EndTime - StartTime, + info("Make Time : ~w s", [Time]), + Res. + +info(V) -> + info(V, []). +info(V, P) -> + io:format(V ++ "~n", P). + +%% 更新到所有线路 +loads([], _Files) -> ok; +loads([H | T], Files) -> + info("[~w]", [H#server.node]), + rpc:cast(H#server.node, u, load, [Files]), + loads(T, Files). + +get_new_file(Files, S) -> + get_new_file(Files, S, []). +get_new_file([], _S, Result) -> Result; +get_new_file([H | T], S, Result) -> + NewResult = case string:tokens(H, ".") of + [Left, Right] when Right =:= "beam" -> + case file:read_file_info(H) of + {ok, FileInfo} -> + Now = calendar:local_time(), + case calendar:time_difference(FileInfo#file_info.mtime, Now) of + {Days, Times} -> + Seconds = calendar:time_to_seconds(Times), + case Days =:= 0 andalso Seconds < S of + true -> + FileName = list_to_atom(Left), + [FileName | Result]; + false -> Result + end; + _ -> Result + end; + _ -> Result + end; + _ -> Result + end, + get_new_file(T, S, NewResult). + +load([]) -> ok; +load([FileName | T]) -> + c:l(FileName), + info("loaded: ~w", [FileName]), + load(T). +% case code:soft_purge(FileName) of +% true -> +% case code:load_file(FileName) of +% {module, _} -> +% info("loaded: ~w", [FileName]), +% ok; +% %% info("loaded: ~w", [FileName]); +% {error, What} -> info("ERROR======> loading: ~w (~w)", [FileName, What]) +% end; +% false -> info("ERROR======> Processes lingering : ~w [zone ~w] ", [FileName, srv_kernel:zone_id()]) +% end, +% load(T). diff --git a/src/srvNodeMgr/tools/gameWorld/test/union_to_emongo.erl b/src/srvNodeMgr/tools/gameWorld/test/union_to_emongo.erl new file mode 100644 index 0000000..77405f6 --- /dev/null +++ b/src/srvNodeMgr/tools/gameWorld/test/union_to_emongo.erl @@ -0,0 +1,869 @@ +%%%-------------------------------------- +%%% @Module : mysql_to_emongo +%%% @Author : csj +%%% @Created : 2011.03.03 +%%% @Description: emongo数据库合服处理模块 +%%%-------------------------------------- +-module(union_to_emongo). +-compile([export_all]). +-include("common.hrl"). + +%%添加服号 +-define(SN, config:get_server_number(gateway)). + +%%添加最大id数字 +-define(Max_id, config:get_max_id(gateway)). + +%%添加服号数据表集合 +-define(SN_List, [user, player, infant_ctrl_byuser]). + +%%删除数据等级限制 +-define(DelLevel, 10). + +%%每次查询或更新记录条数 +-define(PageSize, 100). + +%% monogo数据库连接初始化 +init_mongo(App) -> + try + [PoolId, Host, Port, DB, EmongoSize] = config:get_mongo_config(App), + emongo_sup:start_link(), + emongo_app:initialize_pools([PoolId, Host, Port, DB, EmongoSize]), + misc:write_system_info({self(), mongo}, mongo, {PoolId, Host, Port, DB, EmongoSize}), + {ok, master_mongo} + catch + _:_ -> mongo_config_error + end. + +%% monogo数据库连接初始化 +init_slave_mongo(App) -> + try + [PoolId, Host, Port, DB, EmongoSize] = config:get_slave_mongo_config(App), + emongo_sup:start_link(), + emongo_app:initialize_pools([PoolId, Host, Port, DB, EmongoSize]), + misc:write_system_info({self(), mongo_slave}, mongo_slave, {PoolId, Host, Port, DB, EmongoSize}), + {ok, slave_mongo} + catch + _:_ -> slave_config_error %%没有配置从数据库 + end. + + +%% 启动合并程序 +%%操作顺序 :1.部分表加列sn 2.删除角色数据(可选) 3.更新名字=服号+nickname 4.更新所有id,保证id唯一 5.批处理导入数据 6.最后更新audo_ids的对应的id 7.合服后根据条件删除数据 + +%%在player,user,infant_ctrl_byuser表中添加服号 +start(1) -> + io:format("?SN is ~p~n", [?SN]), + case ?SN > 0 of + false -> + skip; + true -> + F = fun(Table_name) -> + io:format("db.~p.update start...~n", [Table_name]), + db_mongo:update(tool:to_list(Table_name), [{sn, ?SN}], []), + io:format("db.~p.update ok...~n", [Table_name]) + end, + lists:foreach(F, ?SN_List) + end, + io:format("add server number finished!"); + +%%删除等级之下的所有角色 +start(2) -> + IdList = lists:flatten(db_mongo:select_all("player", "id", [{lv, "<=", ?DelLevel}])), + case IdList of + [] -> + io:format("no data!"), + skip; + _ -> + TableList = lib_player_rw:get_all_tables(), + F = fun(Tablename) -> + case Tablename of + arena -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + arena_week -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + box_scene -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + cards -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + carry -> db_mongo:delete(Tablename, [{pid, "in", IdList}]); + consign_player -> db_mongo:delete(Tablename, [{pid, "in", IdList}]); + consign_task -> db_mongo:delete(Tablename, [{pid, "in", IdList}]); + daily_bless -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + exc -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + feedback -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + fst_god -> db_mongo:delete(Tablename, [{uid, "in", IdList}]); + goods -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + goods_attribute -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + goods_buff -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + goods_cd -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + %%帮派不能删除 + %%guild -> db_mongo:delete(Tablename, [{player_id,"in",IdList}]); + guild_apply -> db_mongo:delete(Tablename, [{uid, "in", IdList}]); + guild_invite -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + guild_manor_cd -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + guild_member -> db_mongo:delete(Tablename, [{uid, "in", IdList}]); + log_backout -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_box_open -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_box_player -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_box_throw -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_compose -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_consume -> db_mongo:delete(Tablename, [{pid, "in", IdList}]); + log_dungeon -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_employ -> db_mongo:delete(Tablename, [{pid, "in", IdList}]); + log_exc -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_exc_exp -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_free_pet -> db_mongo:delete(Tablename, [{uid, "in", IdList}]); + log_fst -> db_mongo:delete(Tablename, [{uid, "in", IdList}]); + log_fst_mail -> db_mongo:delete(Tablename, [{uid, "in", IdList}]); + log_hole -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_icompose -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_idecompose -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_identify -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_inlay -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_linggen -> db_mongo:delete(Tablename, [{pid, "in", IdList}]); + log_mail -> db_mongo:delete(Tablename, [{uid, "in", IdList}]); + log_merge -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_meridian -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_pay -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_practise -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_quality_out -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_quality_up -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_refine -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_sale -> db_mongo:delete(Tablename, [{buyer_id, "in", IdList}]), + db_mongo:delete(Tablename, [{sale_id, "in", IdList}]); + log_sale_dir -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_shop -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_stren -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_suitmerge -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_throw -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_trade -> db_mongo:delete(Tablename, [{donor_id, "in", IdList}]), + db_mongo:delete(Tablename, [{gainer_id, "in", IdList}]); + log_uplevel -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_use -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_warehouse_flowdir -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + log_wash -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + mail -> db_mongo:delete(Tablename, [{uid, "in", IdList}]); + master_apprentice -> db_mongo:delete(Tablename, [{apprentenice_id, "in", IdList}]), + db_mongo:delete(Tablename, [{master_id, "in", IdList}]); + master_charts -> db_mongo:delete(Tablename, [{master_id, "in", IdList}]); + meridian -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + mon_drop_analytics -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + offline_award -> db_mongo:delete(Tablename, [{pid, "in", IdList}]); + online_award -> db_mongo:delete(Tablename, [{pid, "in", IdList}]); + online_gift -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + pet -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + player -> db_mongo:delete(Tablename, [{id, "in", IdList}]); + player_buff -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + player_donttalk -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + player_hook_setting -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + player_sys_setting -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + relationship -> db_mongo:delete(Tablename, [{idA, "in", IdList}]), + db_mongo:delete(Tablename, [{idB, "in", IdList}]); + sale_goods -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + skill -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + target_gift -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + task_bag -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + task_consign -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + task_log -> db_mongo:delete(Tablename, [{player_id, "in", IdList}]); + _ -> skip + end + end, + [F(Tablename) || Tablename <- TableList], + io:format("delete data finished!") + end; + +%%更新角色名和帮派名,分别加上服号 +start(3) -> + TableList = lib_player_rw:get_all_tables(), + F = fun(Tablename) -> + case Tablename of + arena -> update_name(Tablename, nickname, id); + arena_week -> update_name(Tablename, nickname, id); + feedback -> update_name(Tablename, player_name, id); + fst_god -> update_name(Tablename, g_name, id), update_name(Tablename, nick, id); + guild -> update_name(Tablename, name, id), update_name(Tablename, chief_name, id), + update_name(Tablename, deputy_chief1_name, id), update_name(Tablename, deputy_chief2_name, id); + guild_invite -> update_name(Tablename, recommander_name, id); + guild_member -> update_name(Tablename, guild_name, id), update_name(Tablename, player_name, id); + log_backout -> update_name(Tablename, nickname, id); + log_box_open -> update_name(Tablename, player_name, id); + log_compose -> update_name(Tablename, nickname, id); + log_guild -> update_name(Tablename, guild_name, id); + log_hole -> update_name(Tablename, nickname, id); + log_icompose -> update_name(Tablename, nickname, id); + log_idecompose -> update_name(Tablename, nickname, id); + log_identify -> update_name(Tablename, nickname, id); + log_inlay -> update_name(Tablename, nickname, id); + log_mail -> update_name(Tablename, sname, id); + log_merge -> update_name(Tablename, nickname, id); + log_pay -> update_name(Tablename, nickname, id); + log_practise -> update_name(Tablename, nickname, id); + log_quality_out -> update_name(Tablename, nickname, id); + log_quality_up -> update_name(Tablename, nickname, id); + log_refine -> update_name(Tablename, nickname, id); + log_sale -> update_name(Tablename, buyer_name, id), update_name(Tablename, saler_name, id); + log_shop -> update_name(Tablename, nickname, id); + log_stren -> update_name(Tablename, nickname, id); + log_suitmerge -> update_name(Tablename, nickname, id); + log_throw -> update_name(Tablename, nickname, id); + log_trade -> update_name(Tablename, donor_name, id), update_name(Tablename, gainer_name, id); + log_use -> update_name(Tablename, nickname, id); + log_wash -> update_name(Tablename, nickname, id); + mail -> update_name(Tablename, sname, id); + master_apprentice -> update_name(Tablename, apprentenice_name, id); + master_charts -> update_name(Tablename, master_name, id); + mon_drop_analytics -> update_name(Tablename, player_name, id); + player -> update_name(Tablename, nickname, id), update_name(Tablename, guild_name, id); + sale_goods -> update_name(Tablename, player_name, id); + _ -> skip + end + end, + [F(Tablename) || Tablename <- TableList], + io:format("change name finished!"); + +%%更新表所有id,保证id唯一 +start(4) -> + %%先查出另服的ID最大值 + %%查询有记录的表及最大主键 + L = search_another_max_id(), + TableList = lib_player_rw:get_all_tables(), + F1 = fun(Tablename) -> + case Tablename of + arena -> update_id(Tablename, [{arena, id}, {player, player_id}], 0, L); + arena_week -> update_id(Tablename, [{arena_week, id}, {player, player_id}], 0, L); + box_scene -> update_id(Tablename, [{box_scene, id}, {player, player_id}], 0, L); + cards -> update_id(Tablename, [{cards, id}, {player, player_id}], 0, L); + carry -> update_id(Tablename, [{carry, id}, {player, pid}], 0, L); + consign_player -> update_id(Tablename, [{consign_player, id}, {player, pid}], 0, L); + consign_task -> update_id(Tablename, [{consign_task, id}, {player, pid}], 0, L); + daily_bless -> update_id(Tablename, [{daily_bless, id}, {player, player_id}], 0, L); + exc -> update_id(Tablename, [{exc, id}, {player, player_id}], 0, L); + feedback -> update_id(Tablename, [{feedback, id}, {player, player_id}], 0, L); + fst_god -> update_id(Tablename, [{fst_god, id}, {player, uid}], 0, L); + goods -> update_id(Tablename, [{goods, id}, {player, player_id}], 0, L); + goods_attribute -> update_id(Tablename, [{goods_attribute, id}, {player, player_id}, {goods, gid}], 0, L); + goods_buff -> update_id(Tablename, [{goods_buff, id}, {player, player_id}], 0, L); + goods_cd -> update_id(Tablename, [{goods_cd, id}, {player, player_id}], 0, L); + guild -> + update_id(Tablename, [{guild, id}, {player, chief_id}, {player, deputy_chief1_id}, {player, deputy_chief2_id}], 1, L); + guild_apply -> update_id(Tablename, [{guild_apply, id}, {guild, guild_id}, {player, player_id}], 0, L); + guild_invite -> + update_id(Tablename, [{guild_invite, id}, {guild, guild_id}, {player, player_id}, {player, recommander_id}], 1, L); + guild_manor_cd -> update_id(Tablename, [{guild_manor_cd, id}, {player, player_id}], 0, L); + guild_member -> update_id(Tablename, [{guild_member, id}, {guild, guild_id}, {player, player_id}], 0, L); + guild_skills_attribute -> update_id(Tablename, [{guild_skills_attribute, id}, {guild, guild_id}], 0, L); + infant_ctrl_byuser -> update_id(Tablename, [{infant_ctrl_byuser, id}], 0, L); + log_backout -> update_id(Tablename, [{log_backout, id}, {player, player_id}, {goods, gid}], 0, L); + log_box_open -> update_id(Tablename, [{log_box_open, id}, {player, player_id}, {goods, gid}], 1, L); + log_box_player -> update_id(Tablename, [{log_box_player, id}, {player, player_id}], 0, L); + log_box_throw -> update_id(Tablename, [{log_box_throw, id}, {player, player_id}, {goods, gid}], 0, L); + log_compose -> update_id(Tablename, [{log_compose, id}, {player, player_id}], 0, L); + log_consume -> update_id(Tablename, [{log_consume, id}, {player, pid}], 0, L); + log_dungeon -> update_id(Tablename, [{log_dungeon, id}, {player, player_id}], 0, L); + log_employ -> update_id(Tablename, [{log_employ, id}, {player, pid}], 0, L); + log_exc -> update_id(Tablename, [{log_exc, id}, {player, player_id}], 0, L); + log_exc_exp -> update_id(Tablename, [{log_exc_exp, id}, {player, player_id}], 0, L); + log_free_pet -> update_id(Tablename, [{log_free_pet, id}, {player, uid}], 0, L); + log_fst -> update_id(Tablename, [{log_fst, id}, {player, uid}], 0, L); + log_fst_mail -> update_id(Tablename, [{log_fst_mail, id}, {player, uid}], 0, L); + log_guild -> update_id(Tablename, [{log_guild, id}, {guild, guild_id}], 0, L); + log_hole -> update_id(Tablename, [{log_hole, id}, {player, player_id}, {goods, gid}], 0, L); + log_icompose -> update_id(Tablename, [{log_icompose, id}, {player, player_id}], 0, L); + log_idecompose -> update_id(Tablename, [{log_idecompose, id}, {player, player_id}, {goods, gid}], 0, L); + log_identify -> update_id(Tablename, [{log_identify, id}, {player, player_id}, {goods, gid}], 0, L); + log_inlay -> update_id(Tablename, [{log_inlay, id}, {player, player_id}, {goods, gid}], 0, L); + log_linggen -> update_id(Tablename, [{log_linggen, id}, {player, pid}], 0, L); + log_mail -> update_id(Tablename, [{log_mail, id}, {player, uid}, {goods, gid}], 1, L); + log_merge -> + update_id(Tablename, [{log_merge, id}, {player, player_id}, {goods, gid_1}, {goods, gid_2}], 0, L); + log_meridian -> update_id(Tablename, [{log_meridian, id}, {player, player_id}], 0, L); + log_pay -> update_id(Tablename, [{log_pay, id}, {player, player_id}], 0, L); + log_practise -> update_id(Tablename, [{log_practise, id}, {player, player_id}, {goods, gid}], 0, L); + log_quality_out -> update_id(Tablename, [{log_quality_out, id}, {player, player_id}, {goods, gid}], 0, L); + log_quality_up -> update_id(Tablename, [{log_quality_up, id}, {player, player_id}, {goods, gid}], 0, L); + log_refine -> update_id(Tablename, [{log_refine, id}, {player, player_id}, {goods, gid}], 0, L); + log_sale -> + update_id(Tablename, [{log_sale, id}, {sale_goods, sale_id}, {player, player_id}, {goods, gid}], 1, L); + log_sale_dir -> + update_id(Tablename, [{log_sale_dir, id}, {sale_goods, sale_id}, {player, player_id}, {goods, gid}], 1, L); + log_shop -> update_id(Tablename, [{log_shop, id}, {player, player_id}], 0, L); + log_stren -> update_id(Tablename, [{log_stren, id}, {player, player_id}, {goods, gid}], 0, L); + log_suitmerge -> + update_id(Tablename, [{log_suitmerge, id}, {player, player_id}, {goods, gid1}, {goods, gid2}, {goods, gid3}], 0, L); + log_throw -> update_id(Tablename, [{log_throw, id}, {player, player_id}, {goods, gid}], 0, L); + log_trade -> + update_id(Tablename, [{log_trade, id}, {player, donor_id}, {player, gainer_id}, {goods, gid}], 1, L); + log_uplevel -> update_id(Tablename, [{log_uplevel, id}, {player, player_id}], 0, L); + log_use -> update_id(Tablename, [{log_use, id}, {player, player_id}, {goods, gid}], 0, L); + log_warehouse_flowdir -> + update_id(Tablename, [{log_warehouse_flowdir, id}, {player, player_id}, {goods, gid}], 0, L); + log_wash -> update_id(Tablename, [{log_wash, id}, {player, player_id}, {goods, gid}], 0, L); + login_prize -> update_id(Tablename, [{login_prize, id}], 0, L); + mail -> update_id(Tablename, [{mail, id}, {player, uid}, {goods, gid}], 1, L); + master_apprentice -> + update_id(Tablename, [{master_apprentice, id}, {player, apprentenice_id}, {player, master_id}], 1, L); + master_charts -> update_id(Tablename, [{master_charts, id}, {player, master_id}], 0, L); + meridian -> update_id(Tablename, [{meridian, id}, {player, player_id}], 0, L); + mon_drop_analytics -> update_id(Tablename, [{mon_drop_analytics, id}, {player, player_id}], 0, L); + offline_award -> update_id(Tablename, [{offline_award, id}, {player, pid}], 0, L); + online_award -> update_id(Tablename, [{online_award, id}, {player, pid}], 0, L); + online_gift -> update_id(Tablename, [{online_gift, id}, {player, player_id}], 0, L); + pet -> update_id(Tablename, [{pet, id}, {player, player_id}], 0, L); + player -> update_id(Tablename, [{player, id}, {guild, guild_id}], 1, L); + player_buff -> update_id(Tablename, [{player_buff, id}, {player, player_id}], 0, L); + player_donttalk -> update_id(Tablename, [{player, player_id}], 0, L); + player_hook_setting -> update_id(Tablename, [{player_hook_setting, id}, {player, player_id}], 0, L); + player_sys_setting -> update_id(Tablename, [{player_sys_setting, id}, {player, player_id}], 0, L); + relationship -> update_id(Tablename, [{relationship, id}, {player, idA}, {player, idB}], 0, L); + sale_goods -> update_id(Tablename, [{sale_goods, id}, {goods, gid}, {player, player_id}], 0, L); + skill -> update_id(Tablename, [{skill, id}, {player, player_id}], 0, L); + target_gift -> update_id(Tablename, [{target_gift, id}, {player, player_id}], 0, L); + task_bag -> update_id(Tablename, [{task_bag, id}, {player, player_id}], 0, L); + task_consign -> update_id(Tablename, [{task_consign, id}, {player, player_id}], 0, L); + task_log -> update_id(Tablename, [{task_log, id}, {player, player_id}], 0, L); + user -> update_id(Tablename, [{user, id}], 0, L); + _ -> skip + end + end, + [F1(Tablename) || Tablename <- TableList], + io:format("change id finished!"); + + +%%批处理导入数据 +start(5) -> + Master_mongo1 = + case init_mongo(gateway) of + {ok, Master_mongo} -> Master_mongo; + _ -> [] + end, + Slave_mongo1 = + case init_slave_mongo(gateway) of + {ok, Slave_mongo} -> Slave_mongo; + _ -> [] + end, + if (Master_mongo1 =/= [] andalso Slave_mongo1 =/= []) -> +%% TableList = lib_player_rw:get_all_tables(), + TableList = + [ + arena, + arena_week, + box_scene, + cards, + carry, + consign_player, + consign_task, + daily_bless, + exc, + feedback, + fst_god, + goods, + goods_attribute, + goods_buff, + goods_cd, + guild, + guild_apply, + guild_invite, + guild_manor_cd, + guild_member, + guild_skills_attribute, + infant_ctrl_byuser, + log_backout, + log_box_open, + log_box_player, + log_box_throw, + log_compose, + log_consume, + log_dungeon, + log_employ, + log_exc, + log_exc_exp, + log_free_pet, + log_fst, + log_fst_mail, + log_guild, + log_hole, + log_icompose, + log_idecompose, + log_identify, + log_inlay, + log_linggen, + log_mail, + log_merge, + log_meridian, + log_pay, + log_practise, + log_quality_out, + log_quality_up, + log_refine, + log_sale, + log_sale_dir, + log_shop, + log_stren, + log_suitmerge, + log_throw, + log_trade, + log_uplevel, + log_use, + log_warehouse_flowdir, + log_wash, + login_prize, + mail, + master_apprentice, + master_charts, + meridian, + mon_drop_analytics, + offline_award, + online_award, + online_gift, + pet, + player, + player_buff, + player_donttalk, + player_hook_setting, + player_sys_setting, + relationship, + sale_goods, + skill, + target_gift, + task_bag, + task_consign, + task_log, + user + ], + F = fun(Tablename) -> + ResultList = emongo:find_all(tool:to_list(Slave_mongo1), tool:to_list(Tablename), [], []), + F = fun(R) -> + R1 = [({Key, Value}) || {Key, Value} <- R, Key =/= <<"_id">>], + Opertion = db_mongoutil:make_insert_opertion(R1), + emongo:insert(tool:to_list(Master_mongo1), tool:to_list(Tablename), Opertion) + end, + io:format("Tablename is ~p union data finish ~n", [Tablename]), + [F(R) || R <- ResultList] + end, + [F(Tablename) || Tablename <- lists:reverse(TableList)], + io:format("mongo and slave config ok"); + true -> + io:format("mongo and slave config error") + end; + + +%%最后更新audo_ids的对应的id +start(6) -> + update_ids(), + io:format("change auto_ids finished!"); + +%%合服后根据条件删除数据 +start(7) -> + + ok. + + +update_name(Tablename, Field, WhereField) -> + io:format("start update_name ~p~n ", [Tablename]), + [Size] = db_mongo:select_count(Tablename, []), + TotalPage = + if (Size div ?PageSize == 0) -> + Size div ?PageSize; + true -> + Size div ?PageSize + 1 + end, + io:format("Size is ~p~n", [Size]), + if (TotalPage =< 1) -> + NameList = db_mongo:select_all(Tablename, tool:to_list(WhereField) ++ "," ++ tool:to_list(Field)), + F = fun(Name) -> + Name1 = tool:to_list(lists:nth(2, Name)), + case length(Name1) > 0 andalso Name1 =/= "[]" of + false -> skip; + true -> + Id1 = lists:nth(1, Name), + NewName = "【" ++ integer_to_list(?SN) ++ "】" ++ Name1, + db_mongo:update(Tablename, [{Field, NewName}], [{WhereField, Id1}]) + end + end, + io:format("end update_name ~p ", [Tablename]), + [F(Name) || Name <- NameList]; + true -> + F = fun(Page) -> + io:format("Page is ~p~n", [Page]), + Result = db_mongo:select_all(Tablename, tool:to_list(WhereField) ++ "," ++ tool:to_list(Field), [], [{tool:to_list(WhereField), asc}, {tool:to_list(Field), asc}], [?PageSize, (Page - 1) * ?PageSize]), + io:format("Result size is ~p~n", [length(Result)]), + F = fun(Name) -> + Name1 = tool:to_list(lists:nth(2, Name)), + case length(Name1) > 0 andalso Name1 =/= "[]" of + false -> skip; + true -> + Id1 = lists:nth(1, Name), + NewName = "【" ++ integer_to_list(?SN) ++ "】" ++ Name1, + db_mongo:update(Tablename, [{Field, NewName}], [{WhereField, Id1}]) + end + end, + io:format("end update_name ~p ", [Tablename]), + [F(Name) || Name <- Result] + end, + lists:foreach(F, lists:seq(1, TotalPage)) + end. + + +search_another_max_id() -> + %%先查出另服的ID最大值 + TableList = lib_player_rw:get_all_tables(), + F = fun(Tablename) -> + case Tablename of + arena -> search_id(Tablename, [id]); + arena_week -> search_id(Tablename, [id]); + box_scene -> search_id(Tablename, [id]); + cards -> search_id(Tablename, [id]); + carry -> search_id(Tablename, [id]); + consign_player -> search_id(Tablename, [id]); + consign_task -> search_id(Tablename, [id]); + daily_bless -> search_id(Tablename, [id]); + exc -> search_id(Tablename, [id]); + feedback -> search_id(Tablename, [id]); + fst_god -> search_id(Tablename, [id]); + goods -> search_id(Tablename, [id]); + goods_attribute -> search_id(Tablename, [id]); + goods_buff -> search_id(Tablename, [id]); + goods_cd -> search_id(Tablename, [id]); + guild -> search_id(Tablename, [id]); + guild_apply -> search_id(Tablename, [id]); + guild_invite -> search_id(Tablename, [id]); + guild_manor_cd -> search_id(Tablename, [id]); + guild_member -> search_id(Tablename, [id]); + guild_skills_attribute -> search_id(Tablename, [id]); + infant_ctrl_byuser -> search_id(Tablename, [id]); + log_backout -> search_id(Tablename, [id]); + log_box_open -> search_id(Tablename, [id]); + log_box_player -> search_id(Tablename, [id]); + log_box_throw -> search_id(Tablename, [id]); + log_compose -> search_id(Tablename, [id]); + log_consume -> search_id(Tablename, [id]); + log_dungeon -> search_id(Tablename, [id]); + log_employ -> search_id(Tablename, [id]); + log_exc -> search_id(Tablename, [id]); + log_exc_exp -> search_id(Tablename, [id]); + log_free_pet -> search_id(Tablename, [id]); + log_fst -> search_id(Tablename, [id]); + log_fst_mail -> search_id(Tablename, [id]); + log_guild -> search_id(Tablename, [id]); + log_hole -> search_id(Tablename, [id]); + log_icompose -> search_id(Tablename, [id]); + log_idecompose -> search_id(Tablename, [id]); + log_identify -> search_id(Tablename, [id]); + log_inlay -> search_id(Tablename, [id]); + log_linggen -> search_id(Tablename, [id]); + log_mail -> search_id(Tablename, [id]); + log_merge -> search_id(Tablename, [id]); + log_meridian -> search_id(Tablename, [id]); + log_pay -> search_id(Tablename, [id]); + log_practise -> search_id(Tablename, [id]); + log_quality_out -> search_id(Tablename, [id]); + log_quality_up -> search_id(Tablename, [id]); + log_refine -> search_id(Tablename, [id]); + log_sale -> search_id(Tablename, [id]); + log_sale_dir -> search_id(Tablename, [id]); + log_shop -> search_id(Tablename, [id]); + log_stren -> search_id(Tablename, [id]); + log_suitmerge -> search_id(Tablename, [id]); + log_throw -> search_id(Tablename, [id]); + log_trade -> search_id(Tablename, [id]); + log_uplevel -> search_id(Tablename, [id]); + log_use -> search_id(Tablename, [id]); + log_warehouse_flowdir -> search_id(Tablename, [id]); + log_wash -> search_id(Tablename, [id]); + login_prize -> search_id(Tablename, [id]); + mail -> search_id(Tablename, [id]); + master_apprentice -> search_id(Tablename, [id]); + master_charts -> search_id(Tablename, [id]); + meridian -> search_id(Tablename, [id]); + mon_drop_analytics -> search_id(Tablename, [id]); + offline_award -> search_id(Tablename, [id]); + online_award -> search_id(Tablename, [id]); + online_gift -> search_id(Tablename, [id]); + pet -> search_id(Tablename, [id]); + player -> search_id(Tablename, [id]); + player_buff -> search_id(Tablename, [id]); + player_donttalk -> search_id(Tablename, [player_id]); + player_hook_setting -> search_id(Tablename, [id]); + player_sys_setting -> search_id(Tablename, [id]); + relationship -> search_id(Tablename, [id]); + sale_goods -> search_id(Tablename, [id]); + skill -> search_id(Tablename, [id]); + target_gift -> search_id(Tablename, [id]); + task_bag -> search_id(Tablename, [id]); + task_consign -> search_id(Tablename, [id]); + task_log -> search_id(Tablename, [id]); + user -> search_id(Tablename, [id]); + _ -> search_id([], []) + end + end, + L = [F(Tablename) || Tablename <- TableList], + %% 查询有记录的表及主键 + [R || R <- L, R =/= {}]. + +%%查询表最大的主键 +search_id(Tablename, FieldList) -> + case Tablename =/= [] of + false -> {}; + _ -> + io:format("search_id ~p~n", [Tablename]), + FieldString = util:list_to_string(FieldList), + MaxId = db_mongo:select_one_new(tool:to_list(?SLAVE_POOLID), Tablename, FieldString, [], [{FieldString, desc}], [1]), + case MaxId of + undefined -> {}; + null -> {}; + _ -> {Tablename, MaxId + 1} + end + end. + +update_id(Tablename, FieldList, CheckExist, TablesMaxIdList) -> + io:format("update_id ~p~n", [Tablename]), +%% FieldString = util:list_to_string(FieldList), + case CheckExist of + 0 -> + F = fun(AnotherTable, Field) -> + case lists:keysearch(AnotherTable, 1, TablesMaxIdList) of + false -> {}; + {value, {AnotherTable, MaxId}} -> + {Field, MaxId, add} + end + end, + FieldList1 = [F(AnotherTable, Field) || {AnotherTable, Field} <- FieldList], + FieldList2 = [FieldValue || FieldValue <- FieldList1, FieldValue =/= {}], +%% emongo:update(tool:to_list(?MASTER_POOLID),tool:to_list(Tablename), [], [{"$inc",FieldList1}]); + db_mongo:update(Tablename, FieldList2, []); + 1 -> + FieldList1 = [(Field) || {_AnotherTable, Field} <- FieldList], + FieldList2 = util:list_to_string(FieldList1), + ResultList = db_mongo:select_all(Tablename, FieldList2), + F = fun(Record) -> + FieldTh = [N1 || N1 <- lists:seq(1, length(Record)), lists:nth(N1, Record) > 0, lists:nth(N1, Record) =/= undefined], + F1 = fun(N2) -> + OldValue1 = lists:nth(N2, Record), + Field1 = lists:nth(N2, FieldList1), + F2 = fun() -> + [AnotherTableName2] = [AnotherTable2 || {AnotherTable2, Field2} <- FieldList, Field1 == Field2], + case lists:keyfind(AnotherTableName2, 1, TablesMaxIdList) of + false -> 0; + {_, MaxId} -> MaxId + end + end, + AnotherValue = F2, + {Field1, AnotherValue + OldValue1} + end, + FieldString1 = [F1(N2) || N2 <- FieldTh], + Where1 = [{lists:nth(1, FieldList1), lists:nth(1, Record)}], + db_mongo:update(Tablename, FieldString1, Where1) + end, + [F(Record) || Record <- ResultList]; + _ -> skip + end. + +update_ids() -> + AutoIdsList = emongo:find_all(tool:to_list(?MASTER_POOLID), tool:to_list(auto_ids), [], ["id,name,mid,counter,kid,gid,uid,num,level"]), + io:format("AutoIdsList is ~p~n", [AutoIdsList]), + F = fun(Result) -> + {_E1, Value1} = lists:nth(1, Result), + {_E2, Value2} = lists:nth(2, Result), + case tool:to_atom(tool:to_list(Value1)) of + master_apprentice -> update_ids(master_apprentice, [id]); + mon_drop_analytics -> update_ids(mon_drop_analytics, [id]); + online_gift -> update_ids(online_gift, [id]); + player_buff -> update_ids(player_buff, [id]); + player_hook_setting -> update_ids(player_hook_setting, [id]); + relationship -> update_ids(relationship, [id]); + sale_goods -> update_ids(sale_goods, [id]); + stc_create_page -> update_ids(stc_create_page, [id]); + system_config -> update_ids(system_config, [id]); + target_gift -> update_ids(target_gift, [id]); + user -> update_ids(user, [id]); + task_bag -> update_ids(task_bag, [id]); + task_log -> update_ids(task_log, [id]); + skill -> update_ids(skill, [id]); + player_sys_setting -> update_ids(player_sys_setting, [id]); + realm_1 -> update_realm(player, [1]); + realm_2 -> update_realm(player, [2]); + realm_3 -> update_realm(player, [3]); + log_box_player -> update_ids(log_box_player, [id]); + dungeon_id -> update_dungeon_id(log_dungeon, [dungeon_id]); + log_dungeon -> update_ids(log_dungeon, [id]); + exc -> update_ids(exc, [id]); + daily_bless -> update_ids(daily_bless, [id]); + log_exc_exp -> update_ids(log_exc_exp, [id]); + arena -> update_ids(arena, [id]); + cards -> update_ids(cards, [id]); + feedback -> update_ids(feedback, [id]); + goods -> update_ids(goods, [id]); + goods_attribute -> update_ids(goods_attribute, [id]); + goods_buff -> update_ids(goods_buff, [id]); + goods_cd -> update_ids(goods_cd, [id]); + guild -> update_ids(guild, [id]); + guild_apply -> update_ids(guild_apply, [id]); + guild_invite -> update_ids(guild_invite, [id]); + guild_member -> update_ids(guild_member, [id]); + guild_skills_attribute -> update_ids(guild_skills_attribute, [id]); + log_backout -> update_ids(log_backout, [id]); + log_box_open -> update_ids(log_box_open, [id]); + log_compose -> update_ids(log_compose, [id]); + log_consume -> update_ids(log_consume, [id]); + log_exc -> update_ids(log_exc, [id]); + log_guild -> update_ids(log_guild, [id]); + log_hole -> update_ids(log_hole, [id]); + log_identify -> update_ids(log_identify, [id]); + log_inlay -> update_ids(log_inlay, [id]); + log_merge -> update_ids(log_merge, [id]); + log_meridian -> update_ids(log_meridian, [id]); + log_pay -> update_ids(log_pay, [id]); + log_practise -> update_ids(log_practise, [id]); + log_quality_out -> update_ids(log_quality_out, [id]); + log_quality_up -> update_ids(log_quality_up, [id]); + log_sale -> update_ids(log_sale, [id]); + log_shop -> update_ids(log_shop, [id]); + log_stren -> update_ids(log_stren, [id]); + log_trade -> update_ids(log_trade, [id]); + log_uplevel -> update_ids(log_uplevel, [id]); + log_use -> update_ids(log_use, [id]); + log_wash -> update_ids(log_wash, [id]); + login_prize -> update_ids(login_prize, [id]); + mail -> update_ids(mail, [id]); + master_charts -> update_ids(master_charts, [id]); + meridian -> update_ids(meridian, [id]); + pet -> update_ids(pet, [id]); + player -> update_ids(player, [id]); + stc_min -> update_ids(stc_min, [id]); + sys_acm -> update_ids(sys_acm, [id]); + test -> update_ids(test, [id]); + log_suitmerge -> update_ids(log_suitmerge, [id]); + infant_ctrl_byuser -> update_ids(infant_ctrl_byuser, [id]); + log_mail -> update_ids(log_mail, [id]); + log_throw -> update_ids(log_throw, [id]); + task_consign -> update_ids(task_consign, [id]); + log_free_pet -> update_ids(log_free_pet, [id]); + guild_manor_cd -> update_ids(guild_manor_cd, [id]); + log_sale_dir -> update_ids(log_sale_dir, [id]); + log_warehouse_flowdir -> update_ids(log_warehouse_flowdir, [id]); + arena_week -> update_ids(arena_week, [id]); + carry -> update_ids(carry, [id]); + consign_player -> update_ids(consign_player, [id]); + log_linggen -> update_ids(log_linggen, [id]); + _ -> skip + end, + case tool:to_atom(tool:to_list(Value2)) of + master_apprentice -> update_ids(master_apprentice, [id]); + mon_drop_analytics -> update_ids(mon_drop_analytics, [id]); + online_gift -> update_ids(online_gift, [id]); + player_buff -> update_ids(player_buff, [id]); + player_hook_setting -> update_ids(player_hook_setting, [id]); + relationship -> update_ids(relationship, [id]); + sale_goods -> update_ids(sale_goods, [id]); + stc_create_page -> update_ids(stc_create_page, [id]); + system_config -> update_ids(system_config, [id]); + target_gift -> update_ids(target_gift, [id]); + user -> update_ids(user, [id]); + task_bag -> update_ids(task_bag, [id]); + task_log -> update_ids(task_log, [id]); + skill -> update_ids(skill, [id]); + player_sys_setting -> update_ids(player_sys_setting, [id]); + realm_1 -> update_realm(player, [1]); + realm_2 -> update_realm(player, [2]); + realm_3 -> update_realm(player, [3]); + log_box_player -> update_ids(log_box_player, [id]); + dungeon_id -> update_dungeon_id(log_dungeon, [dungeon_id]); + log_dungeon -> update_ids(log_dungeon, [id]); + exc -> update_ids(exc, [id]); + daily_bless -> update_ids(daily_bless, [id]); + log_exc_exp -> update_ids(log_exc_exp, [id]); + arena -> update_ids(arena, [id]); + cards -> update_ids(cards, [id]); + feedback -> update_ids(feedback, [id]); + goods -> update_ids(goods, [id]); + goods_attribute -> update_ids(goods_attribute, [id]); + goods_buff -> update_ids(goods_buff, [id]); + goods_cd -> update_ids(goods_cd, [id]); + guild -> update_ids(guild, [id]); + guild_apply -> update_ids(guild_apply, [id]); + guild_invite -> update_ids(guild_invite, [id]); + guild_member -> update_ids(guild_member, [id]); + guild_skills_attribute -> update_ids(guild_skills_attribute, [id]); + log_backout -> update_ids(log_backout, [id]); + log_box_open -> update_ids(log_box_open, [id]); + log_compose -> update_ids(log_compose, [id]); + log_consume -> update_ids(log_consume, [id]); + log_exc -> update_ids(log_exc, [id]); + log_guild -> update_ids(log_guild, [id]); + log_hole -> update_ids(log_hole, [id]); + log_identify -> update_ids(log_identify, [id]); + log_inlay -> update_ids(log_inlay, [id]); + log_merge -> update_ids(log_merge, [id]); + log_meridian -> update_ids(log_meridian, [id]); + log_pay -> update_ids(log_pay, [id]); + log_practise -> update_ids(log_practise, [id]); + log_quality_out -> update_ids(log_quality_out, [id]); + log_quality_up -> update_ids(log_quality_up, [id]); + log_sale -> update_ids(log_sale, [id]); + log_shop -> update_ids(log_shop, [id]); + log_stren -> update_ids(log_stren, [id]); + log_trade -> update_ids(log_trade, [id]); + log_uplevel -> update_ids(log_uplevel, [id]); + log_use -> update_ids(log_use, [id]); + log_wash -> update_ids(log_wash, [id]); + login_prize -> update_ids(login_prize, [id]); + mail -> update_ids(mail, [id]); + master_charts -> update_ids(master_charts, [id]); + meridian -> update_ids(meridian, [id]); + pet -> update_ids(pet, [id]); + player -> update_ids(player, [id]); + stc_min -> update_ids(stc_min, [id]); + sys_acm -> update_ids(sys_acm, [id]); + test -> update_ids(test, [id]); + log_suitmerge -> update_ids(log_suitmerge, [id]); + infant_ctrl_byuser -> update_ids(infant_ctrl_byuser, [id]); + log_mail -> update_ids(log_mail, [id]); + log_throw -> update_ids(log_throw, [id]); + task_consign -> update_ids(task_consign, [id]); + log_free_pet -> update_ids(log_free_pet, [id]); + guild_manor_cd -> update_ids(guild_manor_cd, [id]); + log_sale_dir -> update_ids(log_sale_dir, [id]); + log_warehouse_flowdir -> update_ids(log_warehouse_flowdir, [id]); + arena_week -> update_ids(arena_week, [id]); + carry -> update_ids(carry, [id]); + consign_player -> update_ids(consign_player, [id]); + log_linggen -> update_ids(log_linggen, [id]); + _ -> skip + end + end, + [F(lists:nthtail(1, AutoIds)) || AutoIds <- AutoIdsList]. + +update_ids(Tablename, FieldList) -> + io:format("update_ids ~p~n", [Tablename]), + FieldString = util:list_to_string(FieldList), + MaxId = db_mongo:select_one(Tablename, FieldString, [], [{FieldString, desc}], [1]), + MaxId1 = + case MaxId of + null -> 0; + _ -> MaxId + end, + io:format("MaxId1 is ~p~n", [MaxId1]), + MaxId2 = + case Tablename of + user -> + [UserCount] = db_mongo:select_count(Tablename, []), + if UserCount > MaxId1 -> + UserCount; + true -> + MaxId1 + end; + _ -> + MaxId1 + end, + io:format("MaxId2 is ~p~n", [MaxId2]), + db_mongo:update("auto_ids", [{FieldString, MaxId2}], [{name, Tablename}]). + +update_realm(Tablename, NumList) -> + Realm = lists:nth(1, NumList), + [Total] = db_mongo:select_count(Tablename, [{realm, Realm}]), + Realm_Num = lists:concat(["realm_", Realm]), + db_mongo:update("auto_ids", [{num, Total}], [{name, Realm_Num}]). + +update_dungeon_id(Tablename, FieldList) -> + io:format("update_dungeon_id ~p~n", [Tablename]), + FieldString = util:list_to_string(FieldList), + Total = db_agent:sum(log_dungeon, "dungeon_counter", []), + db_mongo:update("auto_ids", [{counter, Total}], [{name, FieldString}]). + diff --git a/src/srvNodeMgr/tools_cq/h.erl b/src/srvNodeMgr/tools_cq/h.erl new file mode 100644 index 0000000..0af444f --- /dev/null +++ b/src/srvNodeMgr/tools_cq/h.erl @@ -0,0 +1,97 @@ +-module(h). +-include_lib("kernel/include/file.hrl"). + +-export([ + h/0, % 热更修改过的erl文件 + hh/0, % 热更修改过的hrl头文件 + kf_load_beam_all_server_in_local_node/1, + kf_load_beam_all_server_in_kf_center/1, + nodes_load/1, + load/1 +]). + +% 热更 +% ==== u:h() % 编译加载有改动过的erl文件(注意:修改了头文件的话请用下面一条) +% u:hh() +h() -> + ErlFileList = filelib:wildcard("../src/**/*.erl"), + F = fun([$., $., $/ | ErlFile__] = ErlFile) -> + case file:read_file_info(ErlFile) of + {ok, #file_info{mtime = ErlTime}} -> + BeamName = lists:concat(["../ebin/", filename:basename(ErlFile, ".erl"), code:objfile_extension()]), + case file:read_file_info(BeamName) of + {ok, #file_info{mtime = BeamTime}} when ErlTime > BeamTime -> + ErlFile__; + {ok, _} -> + skip; + _E -> + ErlFile__ + end; + _ -> + skip + end + end, + CompileFiles = [Y || Y <- [F(X) || X <- ErlFileList], Y =/= skip], + + StartTime = unixtime(), + io:format("----------makes----------~n~p~n", [CompileFiles]), + c:cd("../"), + Res = make:files(CompileFiles, [netload]), + c:cd("ebin"), + EndTime = unixtime(), + io:format("Make result = ~p~nMake Time : ~p s", [Res, EndTime - StartTime]), + % 其他服加载代码(cast到某条线调用,通过跨服中心让所有服务器加载这些文件) + catch kf_load_beam(CompileFiles), + ok. + +hh() -> + StartTime = unixtime(), + c:l(mmake), + io:format("----------makes----------~n", []), + c:cd("../"), + Res = (catch mmake:all(8, [netload])), + c:cd("ebin"), + EndTime = unixtime(), + io:format("MMake result = ~p~nMMake Time : ~p s", [Res, EndTime - StartTime]), + ok. + +%% 取得当前的unix时间戳 +%% 单位:秒 +unixtime() -> + {M, S, _} = os:timestamp(), + M * 1000000 + S. + +%% 跨服加载代码(跨服加载,通知所有服务器加载) +kf_load_beam([]) -> skip; +kf_load_beam(Files) -> + Modules = [list_to_atom(filename:basename(File, ".erl")) || File <- Files], + case nodes(connected) of + [Node | _] -> + rpc:cast(Node, ?MODULE, kf_load_beam_all_server_in_local_node, [Modules]); + _ -> + skip + end, + ok. + +% 本地某节点运行 +kf_load_beam_all_server_in_local_node(Modules) -> + lib_kfclient:apply_cast(?MODULE, kf_load_beam_all_server_in_kf_center, [Modules]). + +% 跨服中心运行 +kf_load_beam_all_server_in_kf_center(Modules) -> + % 跨服中心加载代码 + nodes_load(Modules), + % 连接的其他服加载代码 + lib_kfcenter:apply_to_all_node(?MODULE, nodes_load, [Modules]), + ok. + +% 加载代码 +nodes_load(Modules) -> + % 本节点加载代码 + catch load(Modules), + % 连接的其他节点加载代码 + ContenctedNodes = nodes(connected), + [rpc:cast(Node, ?MODULE, load, [Modules]) || Node <- ContenctedNodes]. + +load(Modules) -> [c:nl(M) || M <- Modules]. + diff --git a/src/srvNodeMgr/tools_cq/hot_swap.erl b/src/srvNodeMgr/tools_cq/hot_swap.erl new file mode 100644 index 0000000..947aa81 --- /dev/null +++ b/src/srvNodeMgr/tools_cq/hot_swap.erl @@ -0,0 +1,36 @@ +-module(hot_swap). + +-export([ + load/1, + network_load/1, + network_load/2 +]). + + +%% 当前节点热部署: +%% @param ModList 模块列表(原子列表) +load(ModList) -> + lists:foreach(fun(Module) -> + code:purge(Module), + code:load_file(Module) + end, ModList). + +%% 节点集热更: +%% @param ModList 模块列表(原子列表) +network_load(ModList) -> + lists:foreach(fun(Module) -> + [begin rpc:call(Node, code, purge, [Module]), rpc:call(Node, code, load_file, [Module]) end || Node <- (nodes() ++ [node()])] + end, ModList). + +%% 指定单节点热更: +%% @param NodeList 节点集合(原子列表) +%% @param ModList 模块列表(原子列表) +network_load(NodeList, ModList) -> + lists:foreach(fun(Node) -> + lists:foreach(fun(Module) -> + rpc:call(Node, code, purge, [Module]), + rpc:call(Node, code, load_file, [Module]) + end, ModList) + end, NodeList). + + diff --git a/src/srvNodeMgr/tools_cq/memory_show.erl b/src/srvNodeMgr/tools_cq/memory_show.erl new file mode 100644 index 0000000..05a9633 --- /dev/null +++ b/src/srvNodeMgr/tools_cq/memory_show.erl @@ -0,0 +1,67 @@ +-module(memory_show). +-compile(export_all). + +show(N) -> + F = fun(P) -> + case catch process_info(P, [memory, dictionary]) of + [{_, Memory}, {_, Dict}] -> + InitStart = util:prop_get_value('$initial_call', Dict, null), + {InitStart, Memory}; + _ -> {null, 0} + end + end, + Infos1 = lists:map(F, processes()), + Infos2 = [{Name, M} || {Name, M} <- Infos1, Name =/= null], + SortFun = fun({_, M1}, {_, M2}) -> M1 > M2 end, + Infos3 = lists:sort(SortFun, Infos2), + Infos4 = lists:sublist(Infos3, N), + [io:format("~p : ~p ~n", [Name, M]) || {Name, M} <- Infos4], + ok. + +show(N, SkipNames) -> + F = fun(P) -> + case catch process_info(P, [memory, dictionary]) of + [{_, Memory}, {_, Dict}] -> + InitStart = util:prop_get_value('$initial_call', Dict, null), + case catch tuple_to_list(InitStart) of + [Name | _] -> + case lists:member(Name, SkipNames) of + true -> {null, 0}; + false -> {InitStart, Memory} + end; + _ -> {null, 0} + end; + _ -> {null, 0} + end + end, + Infos1 = lists:map(F, processes()), + Infos2 = [{Name, M} || {Name, M} <- Infos1, Name =/= null], + SortFun = fun({_, M1}, {_, M2}) -> M1 > M2 end, + Infos3 = lists:sort(SortFun, Infos2), + Infos4 = lists:sublist(Infos3, N), + [io:format("~p : ~p ~n", [Name, M]) || {Name, M} <- Infos4], + ok. + +show1(N) -> + F = fun(P, Acc) -> + case catch process_info(P, [memory, dictionary]) of + [{_, Memory}, {_, Dict}] -> + InitStart = util:prop_get_value('$initial_call', Dict, null), + case lists:keyfind(InitStart, 1, Acc) of + false -> [{InitStart, Memory, 1} | Acc]; + {InitStart, Memory1, Num} -> lists:keystore(InitStart, 1, Acc, {InitStart, Memory + Memory1, Num + 1}) + end; + _ -> Acc + end + end, + Infos1 = lists:foldl(F, [], processes()), + Infos2 = [{Name, M, Num} || {Name, M, Num} <- Infos1, Name =/= null], + SortFun = fun({_, M1, _}, {_, M2, _}) -> M1 > M2 end, + Infos3 = lists:sort(SortFun, Infos2), + Infos4 = lists:sublist(Infos3, N), + [io:format("~p : per_memory=~p process_num=~p ~n", [Name, (M div Num), Num]) || {Name, M, Num} <- Infos4], + ok. + + + + diff --git a/src/srvNodeMgr/tools_cq/mmake.erl b/src/srvNodeMgr/tools_cq/mmake.erl new file mode 100644 index 0000000..d1d4c1e --- /dev/null +++ b/src/srvNodeMgr/tools_cq/mmake.erl @@ -0,0 +1,409 @@ +%% 多进程编译,修改自otp/lib/tools/src/make.erl +%% 解析Emakefile,根据获取{mods, options}列表, +%% 按照次序编译每项(解决编译顺序的问题) +%% 其中mods也可以包含多个模块,当大于1个时, +%% 可以启动多个process进行编译,从而提高编译速度. + +-module(mmake). +-include_lib("kernel/include/file.hrl"). + +-export([ + all/1, + all/2, + files/2, + files/3 +]). + +-define(MakeOpts, [noexec, load, netload, noload]). + +all(Worker) when is_integer(Worker) -> + all(Worker, []). + +all(Worker, Options) when is_integer(Worker) -> + %% io:format("make all Options:~w", [Options]), + {MakeOpts, CompileOpts} = sort_options(Options, [], []), + case read_emakefile('Emakefile', CompileOpts) of + Files when is_list(Files) -> + do_make_files(Worker, Files, MakeOpts); + error -> + error + end. + +files(Worker, Fs) -> + files(Worker, Fs, []). + +files(Worker, Fs0, Options) -> + Fs = [filename:rootname(F, ".erl") || F <- Fs0], + {MakeOpts, CompileOpts} = sort_options(Options, [], []), + case get_opts_from_emakefile(Fs, 'Emakefile', CompileOpts) of + Files when is_list(Files) -> + do_make_files(Worker, Files, MakeOpts); + error -> error + end. + +do_make_files(Worker, Fs, Opts) -> + %io:format("worker:~p~nfs:~p~nopts:~p~n", [Worker, Fs, Opts]), + process(Fs, Worker, lists:member(noexec, Opts), load_opt(Opts)). + +sort_options([H | T], Make, Comp) -> + case lists:member(H, ?MakeOpts) of + true -> + sort_options(T, [H | Make], Comp); + false -> + sort_options(T, Make, [H | Comp]) + end; +sort_options([], Make, Comp) -> + {Make, lists:reverse(Comp)}. + +%%% Reads the given Emakefile and returns a list of tuples: {Mods,Opts} +%%% Mods is a list of module names (strings) +%%% Opts is a list of options to be used when compiling Mods +%%% +%%% Emakefile can contain elements like this: +%%% Mod. +%%% {Mod,Opts}. +%%% Mod is a module name which might include '*' as wildcard +%%% or a list of such module names +%%% +%%% These elements are converted to [{ModList,OptList},...] +%%% ModList is a list of modulenames (strings) +read_emakefile(Emakefile, Opts) -> + case file:consult(Emakefile) of + {ok, Emake} -> + transform(Emake, Opts, [], []); + {error, enoent} -> + %% No Emakefile found - return all modules in current + %% directory and the options given at command line + Mods = [filename:rootname(F) || F <- filelib:wildcard("*.erl")], + [{Mods, Opts}]; + {error, Other} -> + io:format("make: Trouble reading 'Emakefile':~n~p~n", [Other]), + error + end. + +transform([{Mod, ModOpts} | Emake], Opts, Files, Already) -> + case expand(Mod, Already) of + [] -> + transform(Emake, Opts, Files, Already); + Mods -> + transform(Emake, Opts, [{Mods, ModOpts ++ Opts} | Files], Mods ++ Already) + end; +transform([Mod | Emake], Opts, Files, Already) -> + case expand(Mod, Already) of + [] -> + transform(Emake, Opts, Files, Already); + Mods -> + transform(Emake, Opts, [{Mods, Opts} | Files], Mods ++ Already) + end; +transform([], _Opts, Files, _Already) -> + lists:reverse(Files). + +expand(Mod, Already) when is_atom(Mod) -> + expand(atom_to_list(Mod), Already); +expand(Mods, Already) when is_list(Mods), not is_integer(hd(Mods)) -> + lists:concat([expand(Mod, Already) || Mod <- Mods]); +expand(Mod, Already) -> + case lists:member($*, Mod) of + true -> + Fun = fun(F, Acc) -> + M = filename:rootname(F), + case lists:member(M, Already) of + true -> Acc; + false -> [M | Acc] + end + end, + lists:foldl(Fun, [], filelib:wildcard(Mod ++ ".erl")); + false -> + Mod2 = filename:rootname(Mod, ".erl"), + case lists:member(Mod2, Already) of + true -> []; + false -> [Mod2] + end + end. + +%%% Reads the given Emakefile to see if there are any specific compile +%%% options given for the modules. +get_opts_from_emakefile(Mods, Emakefile, Opts) -> + case file:consult(Emakefile) of + {ok, Emake} -> + Modsandopts = transform(Emake, Opts, [], []), + ModStrings = [coerce_2_list(M) || M <- Mods], + get_opts_from_emakefile2(Modsandopts, ModStrings, Opts, []); + {error, enoent} -> + [{Mods, Opts}]; + {error, Other} -> + io:format("make: Trouble reading 'Emakefile':~n~p~n", [Other]), + error + end. + +get_opts_from_emakefile2([{MakefileMods, O} | Rest], Mods, Opts, Result) -> + case members(Mods, MakefileMods, [], Mods) of + {[], _} -> + get_opts_from_emakefile2(Rest, Mods, Opts, Result); + {I, RestOfMods} -> + get_opts_from_emakefile2(Rest, RestOfMods, Opts, [{I, O} | Result]) + end; +get_opts_from_emakefile2([], [], _Opts, Result) -> + Result; +get_opts_from_emakefile2([], RestOfMods, Opts, Result) -> + [{RestOfMods, Opts} | Result]. + +members([H | T], MakefileMods, I, Rest) -> + case lists:member(H, MakefileMods) of + true -> + members(T, MakefileMods, [H | I], lists:delete(H, Rest)); + false -> + members(T, MakefileMods, I, Rest) + end; +members([], _MakefileMods, I, Rest) -> + {I, Rest}. + + +%% Any flags that are not recognixed as make flags are passed directly +%% to the compiler. +%% So for example make:all([load,debug_info]) will make everything +%% with the debug_info flag and load it. +load_opt(Opts) -> + case lists:member(netload, Opts) of + true -> + netload; + false -> + case lists:member(load, Opts) of + true -> + load; + _ -> + noload + end + end. + +%% 处理 +process([{[], _Opts} | Rest], Worker, NoExec, Load) -> + process(Rest, Worker, NoExec, Load); +process([{L, Opts} | Rest], Worker, NoExec, Load) -> + Len = length(L), + Worker2 = erlang:min(Len, Worker), + case catch do_worker(L, Opts, NoExec, Load, Worker2) of + error -> + error; + ok -> + process(Rest, Worker, NoExec, Load) + end; +process([], _Worker, _NoExec, _Load) -> + up_to_date. + +%% worker进行编译 +%do_worker(L, Opts, NoExec, Load, Worker) -> +% WorkerList = do_split_list(L, Worker), +% io:format("worker:~p worker list(~p)~n,WorkerList:~p~n", [Worker, length(WorkerList)]), +% % 启动进程 +% Ref = make_ref(), +% Pids = +% [begin +% start_worker(E, Opts, NoExec, Load, self(), Ref) +% end || E <- WorkerList], +% do_wait_worker(length(Pids), Ref). +% +%%% 等待结果 +%do_wait_worker(0, _Ref) -> +% ok; +%do_wait_worker(N, Ref) -> +% receive +% {ack, Ref} -> +% do_wait_worker(N - 1, Ref); +% {error, Ref} -> +% throw(error); +% {'EXIT', _P, _Reason} -> +% do_wait_worker(N, Ref); +% _Other -> +% io:format("receive unknown msg:~p~n", [_Other]), +% do_wait_worker(N, Ref) +% end. + +do_worker(L, Opts, NoExec, Load, Worker) -> + + %% 先开启worker个编译进程 + SplitNum = min(length(L), Worker), + {L1, L2} = lists:split(SplitNum, L), + + % 启动进程 + Ref = make_ref(), + Pids = + [begin + start_worker([E], Opts, NoExec, Load, self(), Ref) + end || E <- L1], + do_wait_worker(length(Pids), L2, Opts, NoExec, Load, Ref). + +%% @doc 一个文件编译完成后,立即进行下一个文件编译,不用等待 +do_wait_worker(0, [], _Opts, _NoExec, _Load, _Ref) -> + ok; +do_wait_worker(N, L, Opts, NoExec, Load, Ref) -> + receive + {ack, Ref} -> + case L of + [H | T] -> + %% 未编译完成,编译后面的文件 + start_worker(H, Opts, NoExec, Load, self(), Ref), + do_wait_worker(N, T, Opts, NoExec, Load, Ref); + [] -> + %% 等待所有结果返回 + do_wait_worker(N - 1, [], Opts, NoExec, Load, Ref) + end; + {error, Ref} -> + throw(error); + {'EXIT', _P, _Reason} -> + do_wait_worker(N, L, Opts, NoExec, Load, Ref); + _Other -> + io:format("receive unknown msg:~p~n", [_Other]), + do_wait_worker(N, L, Opts, NoExec, Load, Ref) + end. + +%% 将L分割成最多包含N个子列表的列表 +%do_split_list(L, N) -> +% Len = length(L), +% % 每个列表的元素数 +% LLen = (Len + N - 1) div N, +% do_split_list(L, LLen, []). +% +%do_split_list([], _N, Acc) -> +% lists:reverse(Acc); +%do_split_list(L, N, Acc) -> +% {L2, L3} = lists:split(erlang:min(length(L), N), L), +% do_split_list(L3, N, [L2 | Acc]). +% +%%% 启动worker进程 +%start_worker(L, Opts, NoExec, Load, Parent, Ref) -> +% Fun = +% fun() -> +% [begin +% case recompilep(coerce_2_list(F), NoExec, Load, Opts) of +% error -> +% Parent ! {error, Ref}, +% exit(error); +% _ -> +% ok +% end +% end || F <- L], +% Parent ! {ack, Ref} +% end, +% spawn_link(Fun). +%% 启动worker进程 +start_worker(F, Opts, NoExec, Load, Parent, Ref) -> + Fun = + fun() -> + case recompilep(coerce_2_list(F), NoExec, Load, Opts) of + error -> + Parent ! {error, Ref}, + exit(error); + _ -> + ok + end, + Parent ! {ack, Ref} + end, + spawn_link(Fun). + +recompilep(File, NoExec, Load, Opts) -> + ObjName = lists:append(filename:basename(File), + code:objfile_extension()), + ObjFile = case lists:keysearch(outdir, 1, Opts) of + {value, {outdir, OutDir}} -> + filename:join(coerce_2_list(OutDir), ObjName); + false -> + ObjName + end, + case exists(ObjFile) of + true -> + recompilep1(File, NoExec, Load, Opts, ObjFile); + false -> + recompile(File, NoExec, Load, Opts) + end. + +recompilep1(File, NoExec, Load, Opts, ObjFile) -> + {ok, Erl} = file:read_file_info(lists:append(File, ".erl")), + {ok, Obj} = file:read_file_info(ObjFile), + recompilep1(Erl, Obj, File, NoExec, Load, Opts). + +recompilep1(#file_info{mtime = Te}, + #file_info{mtime = To}, File, NoExec, Load, Opts) when Te > To -> + recompile(File, NoExec, Load, Opts); +recompilep1(_Erl, #file_info{mtime = To}, File, NoExec, Load, Opts) -> + recompile2(To, File, NoExec, Load, Opts). + +%% recompile2(ObjMTime, File, NoExec, Load, Opts) +%% Check if file is of a later date than include files. +recompile2(ObjMTime, File, NoExec, Load, Opts) -> + IncludePath = include_opt(Opts), + case check_includes(lists:append(File, ".erl"), IncludePath, ObjMTime) of + true -> + recompile(File, NoExec, Load, Opts); + false -> + false + end. + +include_opt([{i, Path} | Rest]) -> + [Path | include_opt(Rest)]; +include_opt([_First | Rest]) -> + include_opt(Rest); +include_opt([]) -> + []. + +%% recompile(File, NoExec, Load, Opts) +%% Actually recompile and load the file, depending on the flags. +%% Where load can be netload | load | noload + +recompile(File, true, _Load, _Opts) -> + io:format("Out of date: ~s\n", [File]); +recompile(File, false, noload, Opts) -> + io:format("Recompile: ~s\n", [File]), + compile:file(File, [report_errors, report_warnings, error_summary | Opts]); +recompile(File, false, load, Opts) -> + io:format("Recompile: ~s\n", [File]), + c:c(File, Opts); +recompile(File, false, netload, Opts) -> + io:format("Recompile: ~s\n", [File]), + c:nc(File, Opts). + +exists(File) -> + case file:read_file_info(File) of + {ok, _} -> + true; + _ -> + false + end. + +coerce_2_list(X) when is_atom(X) -> + atom_to_list(X); +coerce_2_list(X) -> + X. + +%%% If you an include file is found with a modification +%%% time larger than the modification time of the object +%%% file, return true. Otherwise return false. +check_includes(File, IncludePath, ObjMTime) -> + Path = [filename:dirname(File) | IncludePath], + case epp:open(File, Path, []) of + {ok, Epp} -> + check_includes2(Epp, File, ObjMTime); + _Error -> + false + end. + +check_includes2(Epp, File, ObjMTime) -> + case epp:parse_erl_form(Epp) of + {ok, {attribute, 1, file, {File, 1}}} -> + check_includes2(Epp, File, ObjMTime); + {ok, {attribute, 1, file, {IncFile, 1}}} -> + case file:read_file_info(IncFile) of + {ok, #file_info{mtime = MTime}} when MTime > ObjMTime -> + epp:close(Epp), + true; + _ -> + check_includes2(Epp, File, ObjMTime) + end; + {ok, _} -> + check_includes2(Epp, File, ObjMTime); + {eof, _} -> + epp:close(Epp), + false; + {error, _Error} -> + check_includes2(Epp, File, ObjMTime) + end. \ No newline at end of file diff --git a/src/srvNodeMgr/tools_cq/recon/recon.erl b/src/srvNodeMgr/tools_cq/recon/recon.erl new file mode 100644 index 0000000..478e2e2 --- /dev/null +++ b/src/srvNodeMgr/tools_cq/recon/recon.erl @@ -0,0 +1,704 @@ +%%% @author Fred Hebert +%%% [http://ferd.ca/] +%%% @doc Recon, as a module, provides access to the high-level functionality +%%% contained in the Recon application. +%%% +%%% It has functions in five main categories: +%%% +%%%
+%%%
1. State information
+%%%
Process information is everything that has to do with the +%%% general state of the node. Functions such as {@link info/1} +%%% and {@link info/3} are wrappers to provide more details than +%%% `erlang:process_info/1', while providing it in a production-safe +%%% manner. They have equivalents to `erlang:process_info/2' in +%%% the functions {@link info/2} and {@link info/4}, respectively.
+%%%
{@link proc_count/2} and {@link proc_window/3} are to be used +%%% when you require information about processes in a larger sense: +%%% biggest consumers of given process information (say memory or +%%% reductions), either absolutely or over a sliding time window, +%%% respectively.
+%%%
{@link bin_leak/1} is a function that can be used to try and +%%% see if your Erlang node is leaking refc binaries. See the function +%%% itself for more details.
+%%%
Functions to access node statistics, in a manner somewhat similar +%%% to what vmstats +%%% provides as a library. There are 3 of them: +%%% {@link node_stats_print/2}, which displays them, +%%% {@link node_stats_list/2}, which returns them in a list, and +%%% {@link node_stats/4}, which provides a fold-like interface +%%% for stats gathering. For CPU usage specifically, see +%%% {@link scheduler_usage/1}.
+%%% +%%%
2. OTP tools
+%%%
This category provides tools to interact with pieces of OTP +%%% more easily. At this point, the only function included is +%%% {@link get_state/1}, which works as a wrapper around +%%% {@link get_state/2}, which works as a wrapper around +%%% `sys:get_state/1' in R16B01, and provides the required +%%% functionality for older versions of Erlang.
+%%% +%%%
3. Code Handling
+%%%
Specific functions are in `recon' for the sole purpose +%%% of interacting with source and compiled code. +%%% {@link remote_load/1} and {@link remote_load/2} will allow +%%% to take a local module, and load it remotely (in a diskless +%%% manner) on another Erlang node you're connected to.
+%%%
{@link source/1} allows to print the source of a loaded module, +%%% in case it's not available in the currently running node.
+%%% +%%%
4. Ports and Sockets
+%%%
To make it simpler to debug some network-related issues, +%%% recon contains functions to deal with Erlang ports (raw, file +%%% handles, or inet). Functions {@link tcp/0}, {@link udp/0}, +%%% {@link sctp/0}, {@link files/0}, and {@link port_types/0} will +%%% list all the Erlang ports of a given type. The latter function +%%% prints counts of all individual types.
+%%%
Port state information can be useful to figure out why certain +%%% parts of the system misbehave. Functions such as +%%% {@link port_info/1} and {@link port_info/2} are wrappers to provide +%%% more similar or more details than `erlang:port_info/1-2', and, for +%%% inet ports, statistics and options for each socket.
+%%%
Finally, the functions {@link inet_count/2} and {@link inet_window/3} +%%% provide the absolute or sliding window functionality of +%%% {@link proc_count/2} and {@link proc_count/3} to inet ports +%%% and connections currently on the node.
+%%% +%%%
5. RPC
+%%%
These are wrappers to make RPC work simpler with clusters of +%%% Erlang nodes. Default RPC mechanisms (from the `rpc' module) +%%% make it somewhat painful to call shell-defined funs over node +%%% boundaries. The functions {@link rpc/1}, {@link rpc/2}, and +%%% {@link rpc/3} will do it with a simpler interface.
+%%%
Additionally, when you're running diagnostic code on remote +%%% nodes and want to know which node evaluated what result, using +%%% {@link named_rpc/1}, {@link named_rpc/2}, and {@link named_rpc/3} +%%% will wrap the results in a tuple that tells you which node it's +%%% coming from, making it easier to identify bad nodes.
+%%%
+%%% @end +-module(recon). +-export([info/1, info/2, info/3, info/4, + proc_count/2, proc_window/3, + bin_leak/1, + node_stats_print/2, node_stats_list/2, node_stats/4, + scheduler_usage/1]). +-export([get_state/1, get_state/2]). +-export([remote_load/1, remote_load/2, + source/1]). +-export([tcp/0, udp/0, sctp/0, files/0, port_types/0, + inet_count/2, inet_window/3, + port_info/1, port_info/2]). +-export([rpc/1, rpc/2, rpc/3, + named_rpc/1, named_rpc/2, named_rpc/3]). + +%%%%%%%%%%%%% +%%% TYPES %%% +%%%%%%%%%%%%% +-type proc_attrs() :: {pid(), + Attr :: _, + [Name :: atom() + |{current_function, mfa()} + |{initial_call, mfa()}, ...]}. + +-type inet_attrs() :: {port(), + Attr :: _, + [{atom(), term()}]}. + +-type pid_term() :: pid() | atom() | string() +| {global, term()} | {via, module(), term()} +| {non_neg_integer(), non_neg_integer(), non_neg_integer()}. + +-type info_type() :: meta | signals | location | memory_used | work. + +-type info_meta_key() :: registered_name | dictionary | group_leader | status. +-type info_signals_key() :: links | monitors | monitored_by | trap_exit. +-type info_location_key() :: initial_call | current_stacktrace. +-type info_memory_key() :: memory | message_queue_len | heap_size +| total_heap_size | garbage_collection. +-type info_work_key() :: reductions. + +-type info_key() :: info_meta_key() | info_signals_key() | info_location_key() +| info_memory_key() | info_work_key(). + +-type port_term() :: port() | string() | atom() | pos_integer(). + +-type port_info_type() :: meta | signals | io | memory_used | specific. + +-type port_info_meta_key() :: registered_name | id | name | os_pid. +-type port_info_signals_key() :: connected | links | monitors. +-type port_info_io_key() :: input | output. +-type port_info_memory_key() :: memory | queue_size. +-type port_info_specific_key() :: atom(). + +-type port_info_key() :: port_info_meta_key() | port_info_signals_key() +| port_info_io_key() | port_info_memory_key() +| port_info_specific_key(). + +-export_type([proc_attrs/0, inet_attrs/0, pid_term/0, port_term/0]). +-export_type([info_type/0, info_key/0, + info_meta_key/0, info_signals_key/0, info_location_key/0, + info_memory_key/0, info_work_key/0]). +-export_type([port_info_type/0, port_info_key/0, + port_info_meta_key/0, port_info_signals_key/0, port_info_io_key/0, + port_info_memory_key/0, port_info_specific_key/0]). + +%%%%%%%%%%%%%%%%%% +%%% PUBLIC API %%% +%%%%%%%%%%%%%%%%%% + +%%% Process Info %%% + +%% @doc Equivalent to `info()' where `A', `B', and `C' are integers part +%% of a pid +-spec info(N, N, N) -> [{info_type(), [{info_key(), term()}]}, ...] when + N :: non_neg_integer(). +info(A, B, C) -> info(recon_lib:triple_to_pid(A, B, C)). + +%% @doc Equivalent to `info(, Key)' where `A', `B', and `C' are integers part +%% of a pid +-spec info(N, N, N, Key) -> term() when + N :: non_neg_integer(), + Key :: info_type() | [atom()] | atom(). +info(A, B, C, Key) -> info(recon_lib:triple_to_pid(A, B, C), Key). + + +%% @doc Allows to be similar to `erlang:process_info/1', but excludes fields +%% such as the mailbox, which have a tendency to grow and be unsafe when called +%% in production systems. Also includes a few more fields than what is usually +%% given (`monitors', `monitored_by', etc.), and separates the fields in a more +%% readable format based on the type of information contained. +%% +%% Moreover, it will fetch and read information on local processes that were +%% registered locally (an atom), globally (`{global, Name}'), or through +%% another registry supported in the `{via, Module, Name}' syntax (must have a +%% `Module:whereis_name/1' function). Pids can also be passed in as a string +%% (`"<0.39.0>"') or a triple (`{0,39,0}') and will be converted to be used. +-spec info(pid_term()) -> [{info_type(), [{info_key(), Value}]}, ...] when + Value :: term(). +info(PidTerm) -> + Pid = recon_lib:term_to_pid(PidTerm), + [info(Pid, Type) || Type <- [meta, signals, location, memory_used, work]]. + +%% @doc Allows to be similar to `erlang:process_info/2', but allows to +%% sort fields by safe categories and pre-selections, avoiding items such +%% as the mailbox, which may have a tendency to grow and be unsafe when +%% called in production systems. +%% +%% Moreover, it will fetch and read information on local processes that were +%% registered locally (an atom), globally (`{global, Name}'), or through +%% another registry supported in the `{via, Module, Name}' syntax (must have a +%% `Module:whereis_name/1' function). Pids can also be passed in as a string +%% (`"<0.39.0>"') or a triple (`{0,39,0}') and will be converted to be used. +%% +%% Although the type signature doesn't show it in generated documentation, +%% a list of arguments or individual arguments accepted by +%% `erlang:process_info/2' and return them as that function would. +%% +%% A fake attribute `binary_memory' is also available to return the +%% amount of memory used by refc binaries for a process. +-spec info(pid_term(), info_type()) -> {info_type(), [{info_key(), term()}]} +; (pid_term(), [atom()]) -> [{atom(), term()}] +; (pid_term(), atom()) -> {atom(), term()}. +info(PidTerm, meta) -> + info_type(PidTerm, meta, [registered_name, dictionary, group_leader, + status]); +info(PidTerm, signals) -> + info_type(PidTerm, signals, [links, monitors, monitored_by, trap_exit]); +info(PidTerm, location) -> + info_type(PidTerm, location, [initial_call, current_stacktrace]); +info(PidTerm, memory_used) -> + info_type(PidTerm, memory_used, [memory, message_queue_len, heap_size, + total_heap_size, garbage_collection]); +info(PidTerm, work) -> + info_type(PidTerm, work, [reductions]); +info(PidTerm, Keys) -> + proc_info(recon_lib:term_to_pid(PidTerm), Keys). + +%% @private makes access to `info_type()' calls simpler. +-spec info_type(pid_term(), info_type(), [info_key()]) -> + {info_type(), [{info_key(), term()}]}. +info_type(PidTerm, Type, Keys) -> + Pid = recon_lib:term_to_pid(PidTerm), + {Type, proc_info(Pid, Keys)}. + +%% @private wrapper around `erlang:process_info/2' that allows special +%% attribute handling for items like `binary_memory'. +proc_info(Pid, binary_memory) -> + {binary, Bins} = erlang:process_info(Pid, binary), + {binary_memory, recon_lib:binary_memory(Bins)}; +proc_info(Pid, Term) when is_atom(Term) -> + erlang:process_info(Pid, Term); +proc_info(Pid, List) when is_list(List) -> + case lists:member(binary_memory, List) of + false -> + erlang:process_info(Pid, List); + true -> + Res = erlang:process_info(Pid, replace(binary_memory, binary, List)), + proc_fake(List, Res) + end. + +%% @private Replace keys around +replace(_, _, []) -> []; +replace(H, Val, [H | T]) -> [Val | replace(H, Val, T)]; +replace(R, Val, [H | T]) -> [H | replace(R, Val, T)]. + +proc_fake([], []) -> + []; +proc_fake([binary_memory | T1], [{binary, Bins} | T2]) -> + [{binary_memory, recon_lib:binary_memory(Bins)} + | proc_fake(T1, T2)]; +proc_fake([_ | T1], [H | T2]) -> + [H | proc_fake(T1, T2)]. + +%% @doc Fetches a given attribute from all processes (except the +%% caller) and returns the biggest `Num' consumers. +%% @todo Implement this function so it only stores `Num' entries in +%% memory at any given time, instead of as many as there are +%% processes. +-spec proc_count(AttributeName, Num) -> [proc_attrs()] when + AttributeName :: atom(), + Num :: non_neg_integer(). +proc_count(AttrName, Num) -> + recon_lib:sublist_top_n_attrs(recon_lib:proc_attrs(AttrName), Num). + +%% @doc Fetches a given attribute from all processes (except the +%% caller) and returns the biggest entries, over a sliding time window. +%% +%% This function is particularly useful when processes on the node +%% are mostly short-lived, usually too short to inspect through other +%% tools, in order to figure out what kind of processes are eating +%% through a lot resources on a given node. +%% +%% It is important to see this function as a snapshot over a sliding +%% window. A program's timeline during sampling might look like this: +%% +%% `--w---- [Sample1] ---x-------------y----- [Sample2] ---z--->' +%% +%% Some processes will live between `w' and die at `x', some between `y' and +%% `z', and some between `x' and `y'. These samples will not be too significant +%% as they're incomplete. If the majority of your processes run between a time +%% interval `x'...`y' (in absolute terms), you should make sure that your +%% sampling time is smaller than this so that for many processes, their +%% lifetime spans the equivalent of `w' and `z'. Not doing this can skew the +%% results: long-lived processes, that have 10 times the time to accumulate +%% data (say reductions) will look like bottlenecks when they're not one. +%% +%% Warning: this function depends on data gathered at two snapshots, and then +%% building a dictionary with entries to differentiate them. This can take a +%% heavy toll on memory when you have many dozens of thousands of processes. +-spec proc_window(AttributeName, Num, Milliseconds) -> [proc_attrs()] when + AttributeName :: atom(), + Num :: non_neg_integer(), + Milliseconds :: pos_integer(). +proc_window(AttrName, Num, Time) -> + Sample = fun() -> recon_lib:proc_attrs(AttrName) end, + {First, Last} = recon_lib:sample(Time, Sample), + recon_lib:sublist_top_n_attrs(recon_lib:sliding_window(First, Last), Num). + +%% @doc Refc binaries can be leaking when barely-busy processes route them +%% around and do little else, or when extremely busy processes reach a stable +%% amount of memory allocated and do the vast majority of their work with refc +%% binaries. When this happens, it may take a very long while before references +%% get deallocated and refc binaries get to be garbage collected, leading to +%% Out Of Memory crashes. +%% This function fetches the number of refc binary references in each process +%% of the node, garbage collects them, and compares the resulting number of +%% references in each of them. The function then returns the `N' processes +%% that freed the biggest amount of binaries, potentially highlighting leaks. +%% +%% See The efficiency guide +%% for more details on refc binaries +-spec bin_leak(pos_integer()) -> [proc_attrs()]. +bin_leak(N) -> + Procs = recon_lib:sublist_top_n_attrs([ + try + {ok, {_, Pre, Id}} = recon_lib:proc_attrs(binary, Pid), + erlang:garbage_collect(Pid), + {ok, {_, Post, _}} = recon_lib:proc_attrs(binary, Pid), + {Pid, length(Pre) - length(Post), Id} + catch + _:_ -> {Pid, 0, []} + end || Pid <- processes() + ], N), + [{Pid, -Val, Id} || {Pid, Val, Id} <- Procs]. + +%% @doc Shorthand for `node_stats(N, Interval, fun(X,_) -> io:format("~p~n",[X]) end, nostate)'. +-spec node_stats_print(Repeat, Interval) -> term() when + Repeat :: non_neg_integer(), + Interval :: pos_integer(). +node_stats_print(N, Interval) -> + node_stats(N, Interval, fun(X, _) -> io:format("~p~n", [X]) end, ok). + +%% @doc Because Erlang CPU usage as reported from `top' isn't the most +%% reliable value (due to schedulers doing idle spinning to avoid going +%% to sleep and impacting latency), a metric exists that is based on +%% scheduler wall time. +%% +%% For any time interval, Scheduler wall time can be used as a measure +%% of how 'busy' a scheduler is. A scheduler is busy when: +%% +%%
    +%%
  • executing process code
  • +%%
  • executing driver code
  • +%%
  • executing NIF code
  • +%%
  • executing BIFs
  • +%%
  • garbage collecting
  • +%%
  • doing memory management
  • +%%
+%% +%% A scheduler isn't busy when doing anything else. +-spec scheduler_usage(Millisecs) -> [{SchedulerId, Usage}] when + Millisecs :: non_neg_integer(), + SchedulerId :: pos_integer(), + Usage :: number(). +scheduler_usage(Interval) when is_integer(Interval) -> + %% We start and stop the scheduler_wall_time system flag if + %% it wasn't in place already. Usually setting the flag should + %% have a CPU impact (making it higher) only when under low usage. + FormerFlag = erlang:system_flag(scheduler_wall_time, true), + First = erlang:statistics(scheduler_wall_time), + timer:sleep(Interval), + Last = erlang:statistics(scheduler_wall_time), + erlang:system_flag(scheduler_wall_time, FormerFlag), + recon_lib:scheduler_usage_diff(First, Last). + +%% @doc Shorthand for `node_stats(N, Interval, fun(X,Acc) -> [X|Acc] end, [])' +%% with the results reversed to be in the right temporal order. +-spec node_stats_list(Repeat, Interval) -> [Stats] when + Repeat :: non_neg_integer(), + Interval :: pos_integer(), + Stats :: {[Absolutes :: {atom(), term()}], + [Increments :: {atom(), term()}]}. +node_stats_list(N, Interval) -> + lists:reverse(node_stats(N, Interval, fun(X, Acc) -> [X | Acc] end, [])). + +%% @doc Gathers statistics `N' time, waiting `Interval' milliseconds between +%% each run, and accumulates results using a folding function `FoldFun'. +%% The function will gather statistics in two forms: Absolutes and Increments. +%% +%% Absolutes are values that keep changing with time, and are useful to know +%% about as a datapoint: process count, size of the run queue, error_logger +%% queue length, and the memory of the node (total, processes, atoms, binaries, +%% and ets tables). +%% +%% Increments are values that are mostly useful when compared to a previous +%% one to have an idea what they're doing, because otherwise they'd never +%% stop increasing: bytes in and out of the node, number of garbage colelctor +%% runs, words of memory that were garbage collected, and the global reductions +%% count for the node. +-spec node_stats(N, Interval, FoldFun, Acc) -> Acc when + N :: non_neg_integer(), + Interval :: pos_integer(), + FoldFun :: fun((Stats, Acc) -> Acc), + Acc :: term(), + Stats :: {[Absolutes :: {atom(), term()}], + [Increments :: {atom(), term()}]}. +node_stats(N, Interval, FoldFun, Init) -> + %% Turn on scheduler wall time if it wasn't there already + FormerFlag = erlang:system_flag(scheduler_wall_time, true), + %% Stats is an ugly fun, but it does its thing. + Stats = fun({{OldIn, OldOut}, {OldGCs, OldWords, _}, SchedWall}) -> + %% Absolutes + ProcC = erlang:system_info(process_count), + RunQ = erlang:statistics(run_queue), + {_, LogQ} = process_info(whereis(error_logger), message_queue_len), + %% Mem (Absolutes) + Mem = erlang:memory(), + Tot = proplists:get_value(total, Mem), + ProcM = proplists:get_value(processes_used, Mem), + Atom = proplists:get_value(atom_used, Mem), + Bin = proplists:get_value(binary, Mem), + Ets = proplists:get_value(ets, Mem), + %% Incremental + {{input, In}, {output, Out}} = erlang:statistics(io), + GC = {GCs, Words, _} = erlang:statistics(garbage_collection), + BytesIn = In - OldIn, + BytesOut = Out - OldOut, + GCCount = GCs - OldGCs, + GCWords = Words - OldWords, + {_, Reds} = erlang:statistics(reductions), + SchedWallNew = erlang:statistics(scheduler_wall_time), + SchedUsage = recon_lib:scheduler_usage_diff(SchedWall, SchedWallNew), + %% Stats Results + {{[{process_count, ProcC}, {run_queue, RunQ}, + {error_logger_queue_len, LogQ}, {memory_total, Tot}, + {memory_procs, ProcM}, {memory_atoms, Atom}, + {memory_bin, Bin}, {memory_ets, Ets}], + [{bytes_in, BytesIn}, {bytes_out, BytesOut}, + {gc_count, GCCount}, {gc_words_reclaimed, GCWords}, + {reductions, Reds}, {scheduler_usage, SchedUsage}]}, + %% New State + {{In, Out}, GC, SchedWallNew}} + end, + {{input, In}, {output, Out}} = erlang:statistics(io), + Gc = erlang:statistics(garbage_collection), + SchedWall = erlang:statistics(scheduler_wall_time), + Result = recon_lib:time_fold( + N, Interval, Stats, + {{In, Out}, Gc, SchedWall}, + FoldFun, Init), + %% Set scheduler wall time back to what it was + erlang:system_flag(scheduler_wall_time, FormerFlag), + Result. + +%%% OTP & Manipulations %%% + + +%% @doc Shorthand call to `recon:get_state(PidTerm, 5000)' +-spec get_state(pid_term()) -> term(). +get_state(PidTerm) -> get_state(PidTerm, 5000). + +%% @doc Fetch the internal state of an OTP process. +%% Calls `sys:get_state/2' directly in R16B01+, and fetches +%% it dynamically on older versions of OTP. +-spec get_state(pid_term(), Ms :: non_neg_integer() | 'infinity') -> term(). +get_state(PidTerm, Timeout) -> + Proc = recon_lib:term_to_pid(PidTerm), + try + sys:get_state(Proc, Timeout) + catch + error:undef -> + case sys:get_status(Proc, Timeout) of + {status, _Pid, {module, gen_server}, Data} -> + {data, Props} = lists:last(lists:nth(5, Data)), + proplists:get_value("State", Props); + {status, _Pod, {module, gen_fsm}, Data} -> + {data, Props} = lists:last(lists:nth(5, Data)), + proplists:get_value("StateData", Props) + end + end. + +%%% Code & Stuff %%% + +%% @equiv remote_load(nodes(), Mod) +-spec remote_load(module()) -> term(). +remote_load(Mod) -> remote_load(nodes(), Mod). + +%% @doc Loads one or more modules remotely, in a diskless manner. Allows to +%% share code loaded locally with a remote node that doesn't have it +-spec remote_load(Nodes, module()) -> term() when + Nodes :: [node(), ...] | node(). +remote_load(Nodes = [_ | _], Mod) when is_atom(Mod) -> + {Mod, Bin, File} = code:get_object_code(Mod), + rpc:multicall(Nodes, code, load_binary, [Mod, File, Bin]); +remote_load(Nodes = [_ | _], Modules) when is_list(Modules) -> + [remote_load(Nodes, Mod) || Mod <- Modules]; +remote_load(Node, Mod) -> + remote_load([Node], Mod). + +%% @doc Obtain the source code of a module compiled with `debug_info'. +%% The returned list sadly does not allow to format the types and typed +%% records the way they look in the original module, but instead goes to +%% an intermediary form used in the AST. They will still be placed +%% in the right module attributes, however. +%% @todo Figure out a way to pretty-print typespecs and records. +-spec source(module()) -> iolist(). +source(Module) -> + Path = code:which(Module), + {ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(Path, [abstract_code]), + erl_prettypr:format(erl_syntax:form_list(AC)). + +%%% Ports Info %%% + +%% @doc returns a list of all TCP ports (the data type) open on the node. +-spec tcp() -> [port()]. +tcp() -> recon_lib:port_list(name, "tcp_inet"). + +%% @doc returns a list of all UDP ports (the data type) open on the node. +-spec udp() -> [port()]. +udp() -> recon_lib:port_list(name, "udp_inet"). + +%% @doc returns a list of all SCTP ports (the data type) open on the node. +-spec sctp() -> [port()]. +sctp() -> recon_lib:port_list(name, "sctp_inet"). + +%% @doc returns a list of all file handles open on the node. +-spec files() -> [port()]. +files() -> recon_lib:port_list(name, "efile"). + +%% @doc Shows a list of all different ports on the node with their respective +%% types. +-spec port_types() -> [{Type :: string(), Count :: pos_integer()}]. +port_types() -> + lists:usort( + %% sorts by biggest count, smallest type + fun({KA, VA}, {KB, VB}) -> {VA, KB} > {VB, KA} end, + recon_lib:count([Name || {_, Name} <- recon_lib:port_list(name)]) + ). + +%% @doc Fetches a given attribute from all inet ports (TCP, UDP, SCTP) +%% and returns the biggest `Num' consumers. +%% +%% The values to be used can be the number of octets (bytes) sent, received, +%% or both (`send_oct', `recv_oct', `oct', respectively), or the number +%% of packets sent, received, or both (`send_cnt', `recv_cnt', `cnt', +%% respectively). Individual absolute values for each metric will be returned +%% in the 3rd position of the resulting tuple. +%% +%% @todo Implement this function so it only stores `Num' entries in +%% memory at any given time, instead of as many as there are +%% processes. +-spec inet_count(AttributeName, Num) -> [inet_attrs()] when + AttributeName :: 'recv_cnt' | 'recv_oct' | 'send_cnt' | 'send_oct' + | 'cnt' | 'oct', + Num :: non_neg_integer(). +inet_count(Attr, Num) -> + recon_lib:sublist_top_n_attrs(recon_lib:inet_attrs(Attr), Num). + +%% @doc Fetches a given attribute from all inet ports (TCP, UDP, SCTP) +%% and returns the biggest entries, over a sliding time window. +%% +%% Warning: this function depends on data gathered at two snapshots, and then +%% building a dictionary with entries to differentiate them. This can take a +%% heavy toll on memory when you have many dozens of thousands of ports open. +%% +%% The values to be used can be the number of octets (bytes) sent, received, +%% or both (`send_oct', `recv_oct', `oct', respectively), or the number +%% of packets sent, received, or both (`send_cnt', `recv_cnt', `cnt', +%% respectively). Individual absolute values for each metric will be returned +%% in the 3rd position of the resulting tuple. +-spec inet_window(AttributeName, Num, Milliseconds) -> [inet_attrs()] when + AttributeName :: 'recv_cnt' | 'recv_oct' | 'send_cnt' | 'send_oct' + | 'cnt' | 'oct', + Num :: non_neg_integer(), + Milliseconds :: pos_integer(). +inet_window(Attr, Num, Time) when is_atom(Attr) -> + Sample = fun() -> recon_lib:inet_attrs(Attr) end, + {First, Last} = recon_lib:sample(Time, Sample), + recon_lib:sublist_top_n_attrs(recon_lib:sliding_window(First, Last), Num). + +%% @doc Allows to be similar to `erlang:port_info/1', but allows +%% more flexible port usage: usual ports, ports that were registered +%% locally (an atom), ports represented as strings (`"#Port<0.2013>"'), +%% or through an index lookup (`2013', for the same result as +%% `"#Port<0.2013>"'). +%% +%% Moreover, the function will try to fetch implementation-specific +%% details based on the port type (only inet ports have this feature +%% so far). For example, TCP ports will include information about the +%% remote peer, transfer statistics, and socket options being used. +%% +%% The information-specific and the basic port info are sorted and +%% categorized in broader categories ({@link port_info_type()}). +-spec port_info(port_term()) -> [{port_info_type(), + [{port_info_key(), term()}]}, ...]. +port_info(PortTerm) -> + Port = recon_lib:term_to_port(PortTerm), + [port_info(Port, Type) || Type <- [meta, signals, io, memory_used, + specific]]. + +%% @doc Allows to be similar to `erlang:port_info/2', but allows +%% more flexible port usage: usual ports, ports that were registered +%% locally (an atom), ports represented as strings (`"#Port<0.2013>"'), +%% or through an index lookup (`2013', for the same result as +%% `"#Port<0.2013>"'). +%% +%% Moreover, the function allows to to fetch information by category +%% as defined in {@link port_info_type()}, and although the type signature +%% doesn't show it in the generated documentation, individual items +%% accepted by `erlang:port_info/2' are accepted, and lists of them too. +-spec port_info(port_term(), port_info_type()) -> {port_info_type(), + [{port_info_key(), _}]} +; (port_term(), [atom()]) -> [{atom(), term()}] +; (port_term(), atom()) -> {atom(), term()}. +port_info(PortTerm, meta) -> + {meta, List} = port_info_type(PortTerm, meta, [id, name, os_pid]), + case port_info(PortTerm, registered_name) of + [] -> {meta, List}; + Name -> {meta, [Name | List]} + end; +port_info(PortTerm, signals) -> + port_info_type(PortTerm, signals, [connected, links, monitors]); +port_info(PortTerm, io) -> + port_info_type(PortTerm, io, [input, output]); +port_info(PortTerm, memory_used) -> + port_info_type(PortTerm, memory_used, [memory, queue_size]); +port_info(PortTerm, specific) -> + Port = recon_lib:term_to_port(PortTerm), + Props = case erlang:port_info(Port, name) of + {_, Type} when Type =:= "udp_inet"; + Type =:= "tcp_inet"; + Type =:= "sctp_inet" -> + case inet:getstat(Port) of + {ok, Stats} -> [{statistics, Stats}]; + _ -> [] + end ++ + case inet:peername(Port) of + {ok, Peer} -> [{peername, Peer}]; + {error, _} -> [] + end ++ + case inet:sockname(Port) of + {ok, Local} -> [{sockname, Local}]; + {error, _} -> [] + end ++ + case inet:getopts(Port, [active, broadcast, buffer, delay_send, + dontroute, exit_on_close, header, + high_watermark, ipv6_v6only, keepalive, + linger, low_watermark, mode, nodelay, + packet, packet_size, priority, + read_packets, recbuf, reuseaddr, + send_timeout, sndbuf]) of + {ok, Opts} -> [{options, Opts}]; + {error, _} -> [] + end; + {_, "efile"} -> + %% would be nice to support file-specific info, but things + %% are too vague with the file_server and how it works in + %% order to make this work efficiently + []; + _ -> + [] + end, + {type, Props}; +port_info(PortTerm, Keys) when is_list(Keys) -> + Port = recon_lib:term_to_port(PortTerm), + [erlang:port_info(Port, Key) || Key <- Keys]; +port_info(PortTerm, Key) when is_atom(Key) -> + erlang:port_info(recon_lib:term_to_port(PortTerm), Key). + +%% @private makes access to `port_info_type()' calls simpler. +%-spec port_info_type(pid_term(), port_info_type(), [port_info_key()]) -> +% {port_info_type(), [{port_info_key(), term()}]}. +port_info_type(PortTerm, Type, Keys) -> + Port = recon_lib:term_to_port(PortTerm), + {Type, [erlang:port_info(Port, Key) || Key <- Keys]}. + + +%%% RPC Utils %%% + +%% @doc Shorthand for `rpc([node()|nodes()], Fun)'. +-spec rpc(fun(() -> term())) -> {[Success :: _], [Fail :: _]}. +rpc(Fun) -> + rpc([node() | nodes()], Fun). + +%% @doc Shorthand for `rpc(Nodes, Fun, infinity)'. +-spec rpc(node()|[node(), ...], fun(() -> term())) -> {[Success :: _], [Fail :: _]}. +rpc(Nodes, Fun) -> + rpc(Nodes, Fun, infinity). + +%% @doc Runs an arbitrary fun (of arity 0) over one or more nodes. +-spec rpc(node()|[node(), ...], fun(() -> term()), timeout()) -> {[Success :: _], [Fail :: _]}. +rpc(Nodes = [_ | _], Fun, Timeout) when is_function(Fun, 0) -> + rpc:multicall(Nodes, erlang, apply, [Fun, []], Timeout); +rpc(Node, Fun, Timeout) when is_atom(Node) -> + rpc([Node], Fun, Timeout). + +%% @doc Shorthand for `named_rpc([node()|nodes()], Fun)'. +-spec named_rpc(fun(() -> term())) -> {[Success :: _], [Fail :: _]}. +named_rpc(Fun) -> + named_rpc([node() | nodes()], Fun). + +%% @doc Shorthand for `named_rpc(Nodes, Fun, infinity)'. +-spec named_rpc(node()|[node(), ...], fun(() -> term())) -> {[Success :: _], [Fail :: _]}. +named_rpc(Nodes, Fun) -> + named_rpc(Nodes, Fun, infinity). + +%% @doc Runs an arbitrary fun (of arity 0) over one or more nodes, and returns the +%% name of the node that computed a given result along with it, in a tuple. +-spec named_rpc(node()|[node(), ...], fun(() -> term()), timeout()) -> {[Success :: _], [Fail :: _]}. +named_rpc(Nodes = [_ | _], Fun, Timeout) when is_function(Fun, 0) -> + rpc:multicall(Nodes, erlang, apply, [fun() -> {node(), Fun()} end, []], Timeout); +named_rpc(Node, Fun, Timeout) when is_atom(Node) -> + named_rpc([Node], Fun, Timeout). + diff --git a/src/srvNodeMgr/tools_cq/recon/recon_alloc.erl b/src/srvNodeMgr/tools_cq/recon/recon_alloc.erl new file mode 100644 index 0000000..ec7c59b --- /dev/null +++ b/src/srvNodeMgr/tools_cq/recon/recon_alloc.erl @@ -0,0 +1,726 @@ +%%% @author Fred Hebert +%%% [http://ferd.ca/] +%%% @author Lukas Larsson +%%% @doc Functions to deal with +%%% Erlang's memory +%%% allocators, or particularly, to try to present the allocator data +%%% in a way that makes it simpler to discover possible problems. +%%% +%%% Tweaking Erlang memory allocators and their behaviour is a very tricky +%%% ordeal whenever you have to give up the default settings. This module +%%% (and its documentation) will try and provide helpful pointers to help +%%% in this task. +%%% +%%% This module should mostly be helpful to figure out if there is +%%% a problem, but will offer little help to figure out what is wrong. +%%% +%%% To figure this out, you need to dig deeper into the allocator data +%%% (obtainable with {@link allocators/0}), and/or have some precise knowledge +%%% about the type of load and work done by the VM to be able to assess what +%%% each reaction to individual tweak should be. +%%% +%%% A lot of trial and error might be required to figure out if tweaks have +%%% helped or not, ultimately. +%%% +%%% In order to help do offline debugging of memory allocator problems +%%% recon_alloc also has a few functions that store snapshots of the +%%% memory statistics. +%%% These snapshots can be used to freeze the current allocation values so that +%%% they do not change during analysis while using the regular functionality of +%%% this module, so that the allocator values can be saved, or that +%%% they can be shared, dumped, and reloaded for further analysis using files. +%%% See {@link snapshot_load/1} for a simple use-case. +%%% +%%% Glossary: +%%%
+%%%
sys_alloc
+%%%
System allocator, usually just malloc
+%%% +%%%
mseg_alloc
+%%%
Used by other allocators, can do mmap. Caches allocations
+%%% +%%%
temp_alloc
+%%%
Used for temporary allocations
+%%% +%%%
eheap_alloc
+%%%
Heap data (i.e. process heaps) allocator
+%%% +%%%
binary_alloc
+%%%
Global binary heap allocator
+%%% +%%%
ets_alloc
+%%%
ETS data allocator
+%%% +%%%
driver_alloc
+%%%
Driver data allocator
+%%% +%%%
sl_alloc
+%%%
Short-lived memory blocks allocator
+%%% +%%%
ll_alloc
+%%%
Long-lived data (i.e. Erlang code itself) allocator
+%%% +%%%
fix_alloc
+%%%
Frequently used fixed-size data allocator
+%%% +%%%
std_alloc
+%%%
Allocator for other memory blocks
+%%% +%%%
carrier
+%%%
When a given area of memory is allocated by the OS to the +%%% VM (through sys_alloc or mseg_alloc), it is put into a 'carrier'. There +%%% are two kinds of carriers: multiblock and single block. The default +%%% carriers data is sent to are multiblock carriers, owned by a specific +%%% allocator (ets_alloc, binary_alloc, etc.). The specific allocator can +%%% thus do allocation for specific Erlang requirements within bits of +%%% memory that has been preallocated before. This allows more reuse, +%%% and we can even measure the cache hit rates {@link cache_hit_rates/0}. +%%% +%%% There is however a threshold above which an item in memory won't fit +%%% a multiblock carrier. When that happens, the specific allocator does +%%% a special allocation to a single block carrier. This is done by the +%%% allocator basically asking for space directly from sys_alloc or +%%% mseg_alloc rather than a previously multiblock area already obtained +%%% before. +%%% +%%% This leads to various allocation strategies where you decide to +%%% choose: +%%%
    +%%%
  1. which multiblock carrier you're going to (if at all)
  2. +%%%
  3. which block in that carrier you're going to
  4. +%%%
+%%% +%%% See the official +%%% documentation on erts_alloc for more details. +%%%
+%%% +%%%
mbcs
+%%%
Multiblock carriers.
+%%% +%%%
sbcs
+%%%
Single block carriers.
+%%% +%%%
lmbcs
+%%%
Largest multiblock carrier size
+%%% +%%%
smbcs
+%%%
Smallest multiblock carrier size
+%%% +%%%
sbct
+%%%
Single block carrier threshold
+%%%
+%%% +%%% By default all sizes returned by this module are in bytes. You can change +%%% this by calling {@link set_unit/1}. +%%% +-module(recon_alloc). +-define(UTIL_ALLOCATORS, [temp_alloc, + eheap_alloc, + binary_alloc, + ets_alloc, + driver_alloc, + sl_alloc, + ll_alloc, + fix_alloc, + std_alloc +]). + +-type allocator() :: temp_alloc | eheap_alloc | binary_alloc | ets_alloc +| driver_alloc | sl_alloc | ll_alloc | fix_alloc +| std_alloc. +-type instance() :: non_neg_integer(). +-type allocdata(T) :: {{allocator(), instance()}, T}. +-type allocdata_types(T) :: {{allocator(), [instance()]}, T}. +-export_type([allocator/0, instance/0, allocdata/1]). + +-define(CURRENT_POS, 2). % pos in sizes tuples for current value +-define(MAX_POS, 4). % pos in sizes tuples for max value + +-export([memory/1, memory/2, fragmentation/1, cache_hit_rates/0, + average_block_sizes/1, sbcs_to_mbcs/1, allocators/0, + allocators/1]). + +%% Snapshot handling +-type memory() :: [{atom(), atom()}]. +-type snapshot() :: {memory(), [allocdata(term())]}. + +-export_type([memory/0, snapshot/0]). + +-export([snapshot/0, snapshot_clear/0, + snapshot_print/0, snapshot_get/0, + snapshot_save/1, snapshot_load/1]). + +%% Unit handling +-export([set_unit/1]). + +%%%%%%%%%%%%%% +%%% Public %%% +%%%%%%%%%%%%%% + + +%% @doc Equivalent to `memory(Key, current)'. +-spec memory(used | allocated | unused) -> pos_integer() +; (usage) -> number() +; (allocated_types | allocated_instances) -> + [{allocator(), pos_integer()}]. +memory(Key) -> memory(Key, current). + +%% @doc reports one of multiple possible memory values for the entire +%% node depending on what is to be reported: +%% +%%
    +%%
  • `used' reports the memory that is actively used for allocated +%% Erlang data;
  • +%%
  • `allocated' reports the memory that is reserved by the VM. It +%% includes the memory used, but also the memory yet-to-be-used but still +%% given by the OS. This is the amount you want if you're dealing with +%% ulimit and OS-reported values.
  • +%%
  • `allocated_types' report the memory that is reserved by the +%% VM grouped into the different util allocators.
  • +%%
  • `allocated_instances' report the memory that is reserved +%% by the VM grouped into the different schedulers. Note that +%% instance id 0 is the global allocator used to allocate data from +%% non-managed threads, i.e. async and driver threads.
  • +%%
  • `unused' reports the amount of memory reserved by the VM that +%% is not being allocated. +%% Equivalent to `allocated - used'.
  • +%%
  • `usage' returns a percentage (0.0 .. 1.0) of `used/allocated' +%% memory ratios.
  • +%%
+%% +%% The memory reported by `allocated' should roughly +%% match what the OS reports. If this amount is different by a large margin, +%% it may be the sign that someone is allocating memory in C directly, outside +%% of Erlang's own allocator -- a big warning sign. There are currently +%% three sources of memory alloction that are not counted towards this value: +%% The cached segments in the mseg allocator, any memory allocated as a +%% super carrier, and small pieces of memory allocated during startup +%% before the memory allocators are initialized. +%% +%% Also note that low memory usages can be the sign of fragmentation in +%% memory, in which case exploring which specific allocator is at fault +%% is recommended (see {@link fragmentation/1}) +-spec memory(used | allocated | unused, current | max) -> pos_integer() +; (usage, current | max) -> number() +; (allocated_types|allocated_instances, current | max) -> + [{allocator(), pos_integer()}]. +memory(used, Keyword) -> + lists:sum(lists:map(fun({_, Prop}) -> + container_size(Prop, Keyword, blocks_size) + end, util_alloc())); +memory(allocated, Keyword) -> + lists:sum(lists:map(fun({_, Prop}) -> + container_size(Prop, Keyword, carriers_size) + end, util_alloc())); +memory(allocated_types, Keyword) -> + lists:foldl(fun({{Alloc, _N}, Props}, Acc) -> + CZ = container_size(Props, Keyword, carriers_size), + orddict:update_counter(Alloc, CZ, Acc) + end, orddict:new(), util_alloc()); +memory(allocated_instances, Keyword) -> + lists:foldl(fun({{_Alloc, N}, Props}, Acc) -> + CZ = container_size(Props, Keyword, carriers_size), + orddict:update_counter(N, CZ, Acc) + end, orddict:new(), util_alloc()); +memory(unused, Keyword) -> + memory(allocated, Keyword) - memory(used, Keyword); +memory(usage, Keyword) -> + memory(used, Keyword) / memory(allocated, Keyword). + +%% @doc Compares the block sizes to the carrier sizes, both for +%% single block (`sbcs') and multiblock (`mbcs') carriers. +%% +%% The returned results are sorted by a weight system that is +%% somewhat likely to return the most fragmented allocators first, +%% based on their percentage of use and the total size of the carriers, +%% for both `sbcs' and `mbcs'. +%% +%% The values can both be returned for `current' allocator values, and +%% for `max' allocator values. The current values hold the present allocation +%% numbers, and max values, the values at the peak. Comparing both together +%% can give an idea of whether the node is currently being at its memory peak +%% when possibly leaky, or if it isn't. This information can in turn +%% influence the tuning of allocators to better fit sizes of blocks and/or +%% carriers. +-spec fragmentation(current | max) -> [allocdata([{atom(), term()}])]. +fragmentation(Keyword) -> + WeighedData = [begin + BlockSbcs = container_value(Props, Keyword, sbcs, blocks_size), + CarSbcs = container_value(Props, Keyword, sbcs, carriers_size), + BlockMbcs = container_value(Props, Keyword, mbcs, blocks_size), + CarMbcs = container_value(Props, Keyword, mbcs, carriers_size), + {Weight, Vals} = weighed_values({BlockSbcs, CarSbcs}, + {BlockMbcs, CarMbcs}), + {Weight, {Allocator, N}, Vals} + end || {{Allocator, N}, Props} <- util_alloc()], + [{Key, Val} || {_W, Key, Val} <- lists:reverse(lists:sort(WeighedData))]. + +%% @doc looks at the `mseg_alloc' allocator (allocator used by all the +%% allocators in {@link allocator()}) and returns information relative to +%% the cache hit rates. Unless memory has expected spiky behaviour, it should +%% usually be above 0.80 (80%). +%% +%% Cache can be tweaked using three VM flags: `+MMmcs', `+MMrmcbf', and +%% `+MMamcbf'. +%% +%% `+MMmcs' stands for the maximum amount of cached memory segments. Its +%% default value is '10' and can be anything from 0 to 30. Increasing +%% it first and verifying if cache hits get better should be the first +%% step taken. +%% +%% The two other options specify what are the maximal values of a segment +%% to cache, in relative (in percent) and absolute terms (in kilobytes), +%% respectively. Increasing these may allow more segments to be cached, but +%% should also add overheads to memory allocation. An Erlang node that has +%% limited memory and increases these values may make things worse on +%% that point. +%% +%% The values returned by this function are sorted by a weight combining +%% the lower cache hit joined to the largest memory values allocated. +-spec cache_hit_rates() -> [{{instance, instance()}, [{Key, Val}]}] when + Key :: hit_rate | hits | calls, + Val :: term(). +cache_hit_rates() -> + WeighedData = [begin + Mem = proplists:get_value(memkind, Props), + {_, Hits} = lists:keyfind(cache_hits, 1, proplists:get_value(status, Mem)), + {_, Giga, Ones} = lists:keyfind(mseg_alloc, 1, proplists:get_value(calls, Mem)), + Calls = 1000000000 * Giga + Ones, + HitRate = usage(Hits, Calls), + Weight = (1.00 - HitRate) * Calls, + {Weight, {instance, N}, [{hit_rate, HitRate}, {hits, Hits}, {calls, Calls}]} + end || {{_, N}, Props} <- alloc([mseg_alloc])], + [{Key, Val} || {_W, Key, Val} <- lists:reverse(lists:sort(WeighedData))]. + +%% @doc Checks all allocators in {@link allocator()} and returns the average +%% block sizes being used for `mbcs' and `sbcs'. This value is interesting +%% to use because it will tell us how large most blocks are. +%% This can be related to the VM's largest multiblock carrier size +%% (`lmbcs') and smallest multiblock carrier size (`smbcs') to specify +%% allocation strategies regarding the carrier sizes to be used. +%% +%% This function isn't exceptionally useful unless you know you have some +%% specific problem, say with sbcs/mbcs ratios (see {@link sbcs_to_mbcs/0}) +%% or fragmentation for a specific allocator, and want to figure out what +%% values to pick to increase or decrease sizes compared to the currently +%% configured value. +%% +%% Do note that values for `lmbcs' and `smbcs' are going to be rounded up +%% to the next power of two when configuring them. +-spec average_block_sizes(current | max) -> [{allocator(), [{Key, Val}]}] when + Key :: mbcs | sbcs, + Val :: number(). +average_block_sizes(Keyword) -> + Dict = lists:foldl(fun({{Instance, _}, Props}, Dict0) -> + CarSbcs = container_value(Props, Keyword, sbcs, blocks), + SizeSbcs = container_value(Props, Keyword, sbcs, blocks_size), + CarMbcs = container_value(Props, Keyword, mbcs, blocks), + SizeMbcs = container_value(Props, Keyword, mbcs, blocks_size), + Dict1 = dict:update_counter({Instance, sbcs, count}, CarSbcs, Dict0), + Dict2 = dict:update_counter({Instance, sbcs, size}, SizeSbcs, Dict1), + Dict3 = dict:update_counter({Instance, mbcs, count}, CarMbcs, Dict2), + Dict4 = dict:update_counter({Instance, mbcs, size}, SizeMbcs, Dict3), + Dict4 + end, + dict:new(), + util_alloc()), + average_group(average_calc(lists:sort(dict:to_list(Dict)))). + +%% @doc compares the amount of single block carriers (`sbcs') vs the +%% number of multiblock carriers (`mbcs') for each individual allocator in +%% {@link allocator()}. +%% +%% When a specific piece of data is allocated, it is compared to a threshold, +%% called the 'single block carrier threshold' (`sbct'). When the data is +%% larger than the `sbct', it gets sent to a single block carrier. When the +%% data is smaller than the `sbct', it gets placed into a multiblock carrier. +%% +%% mbcs are to be prefered to sbcs because they basically represent pre- +%% allocated memory, whereas sbcs will map to one call to sys_alloc +%% or mseg_alloc, which is more expensive than redistributing +%% data that was obtained for multiblock carriers. Moreover, the VM is able to +%% do specific work with mbcs that should help reduce fragmentation in ways +%% sys_alloc or mmap usually won't. +%% +%% Ideally, most of the data should fit inside multiblock carriers. If +%% most of the data ends up in `sbcs', you may need to adjust the multiblock +%% carrier sizes, specifically the maximal value (`lmbcs') and the threshold +%% (`sbct'). On 32 bit VMs, `sbct' is limited to 8MBs, but 64 bit VMs can go +%% to pretty much any practical size. +%% +%% Given the value returned is a ratio of sbcs/mbcs, the higher the value, +%% the worst the condition. The list is sorted accordingly. +-spec sbcs_to_mbcs(max | current) -> [allocdata(term())]. +sbcs_to_mbcs(Keyword) -> + WeightedList = [begin + Sbcs = container_value(Props, Keyword, sbcs, blocks), + Mbcs = container_value(Props, Keyword, mbcs, blocks), + Ratio = case {Sbcs, Mbcs} of + {0, 0} -> 0; + {_, 0} -> infinity; % that is bad! + {_, _} -> Sbcs / Mbcs + end, + {Ratio, {Allocator, N}} + end || {{Allocator, N}, Props} <- util_alloc()], + [{Alloc, Ratio} || {Ratio, Alloc} <- lists:reverse(lists:sort(WeightedList))]. + +%% @doc returns a dump of all allocator settings and values +-spec allocators() -> [allocdata(term())]. +allocators() -> + UtilAllocators = erlang:system_info(alloc_util_allocators), + Allocators = [sys_alloc, mseg_alloc | UtilAllocators], + %% versions is deleted in order to allow the use of the orddict api, + %% and never really having come across a case where it was useful to know. + [{{A, N}, lists:sort(proplists:delete(versions, Props))} || + A <- Allocators, + Allocs <- [erlang:system_info({allocator, A})], + Allocs =/= false, + {_, N, Props} <- Allocs]. + +%% @doc returns a dump of all allocator settings and values modified +%% depending on the argument. +%%
    +%%
  • `types` report the settings and accumulated values for each +%% allocator type. This is useful when looking for anomalies +%% in the system as a whole and not specific instances.
  • +%%
+-spec allocators(types) -> [allocdata_types(term())]. +allocators(types) -> + allocators_types(alloc(), []). + +allocators_types([{{Type, No}, Vs} | T], As) -> + case lists:keytake(Type, 1, As) of + false -> + allocators_types(T, [{Type, [No], sort_values(Type, Vs)} | As]); + {value, {Type, Nos, OVs}, NAs} -> + MergedValues = merge_values(sort_values(Type, Vs), OVs), + allocators_types(T, [{Type, [No | Nos], MergedValues} | NAs]) + end; +allocators_types([], As) -> + [{{Type, Nos}, Vs} || {Type, Nos, Vs} <- As]. + +merge_values([{Key, Vs} | T1], [{Key, OVs} | T2]) when Key =:= memkind -> + [{Key, merge_values(Vs, OVs)} | merge_values(T1, T2)]; +merge_values([{Key, Vs} | T1], [{Key, OVs} | T2]) when Key =:= calls; + Key =:= fix_types; + Key =:= sbmbcs; + Key =:= mbcs; + Key =:= mbcs_pool; + Key =:= sbcs; + Key =:= status -> + [{Key, lists:map( + fun({{K, MV1, V1}, {K, MV2, V2}}) -> + %% Merge the MegaVs + Vs into one + V = MV1 * 1000000 + V1 + MV2 * 1000000 + V2, + {K, V div 1000000, V rem 1000000}; + ({{K, V1}, {K, V2}}) when K =:= segments_watermark -> + %% We take the maximum watermark as that is + %% a value that we can use somewhat. Ideally + %% maybe the average should be used, but the + %% value is very rarely important so leave it + %% like this for now. + {K, lists:max([V1, V2])}; + ({{K, V1}, {K, V2}}) -> + {K, V1 + V2}; + ({{K, C1, L1, M1}, {K, C2, L2, M2}}) -> + %% Merge the Curr, Last, Max into one + {K, C1 + C2, L1 + L2, M1 + M2} + end, lists:zip(Vs, OVs))} | merge_values(T1, T2)]; +merge_values([{Type, _Vs} = E | T1], T2) when Type =:= mbcs_pool -> + %% For values never showing up in instance 0 but in all other + [E | merge_values(T1, T2)]; +merge_values(T1, [{Type, _Vs} = E | T2]) when Type =:= fix_types -> + %% For values only showing up in instance 0 + [E | merge_values(T1, T2)]; +merge_values([E | T1], [E | T2]) -> + %% For values that are constant + [E | merge_values(T1, T2)]; +merge_values([{options, _Vs1} | T1], [{options, _Vs2} = E | T2]) -> + %% Options change a but in between instance 0 and the other, + %% We show the others as they are the most interesting. + [E | merge_values(T1, T2)]; +merge_values([], []) -> + []. + +sort_values(mseg_alloc, Vs) -> + {value, {memkind, MemKindVs}, OVs} = lists:keytake(memkind, 1, Vs), + lists:sort([{memkind, lists:sort(MemKindVs)} | OVs]); +sort_values(_Type, Vs) -> + lists:sort(Vs). + +%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Snapshot handling %%% +%%%%%%%%%%%%%%%%%%%%%%%%% + +%% @doc Take a new snapshot of the current memory allocator statistics. +%% The snapshot is stored in the process dictionary of the calling process, +%% with all the limitations that it implies (i.e. no garbage-collection). +%% To unsert the snapshot, see {@link snapshot_clear/1}. +-spec snapshot() -> snapshot() | undefined. +snapshot() -> + put(recon_alloc_snapshot, snapshot_int()). + +%% @doc clear the current snapshot in the process dictionary, if present, +%% and return the value it had before being unset. +%% @end +%% Maybe we should use erlang:delete(Key) here instead? +-spec snapshot_clear() -> snapshot() | undefined. +snapshot_clear() -> + put(recon_alloc_snapshot, undefined). + +%% @doc print a dump of the current snapshot stored by {@link snapshot/0} +%% Prints `undefined' if no snapshot has been taken. +-spec snapshot_print() -> ok. +snapshot_print() -> + io:format("~p.~n", [snapshot_get()]). + +%% @doc returns the current snapshot stored by {@link snapshot/0}. +%% Returns `undefined' if no snapshot has been taken. +-spec snapshot_get() -> snapshot() | undefined. +snapshot_get() -> + get(recon_alloc_snapshot). + +%% @doc save the current snapshot taken by {@link snapshot/0} to a file. +%% If there is no current snapshot, a snaphot of the current allocator +%% statistics will be written to the file. +-spec snapshot_save(Filename) -> ok when + Filename :: file:name(). +snapshot_save(Filename) -> + Snapshot = case snapshot_get() of + undefined -> + snapshot_int(); + Snap -> + Snap + end, + case file:write_file(Filename, io_lib:format("~p.~n", [Snapshot])) of + ok -> ok; + {error, Reason} -> + erlang:error(Reason, [Filename]) + end. + + +%% @doc load a snapshot from a given file. The format of the data in the +%% file can be either the same as output by {@link snapshot_save()}, +%% or the output obtained by calling +%% `{erlang:memory(),[{A,erlang:system_info({allocator,A})} || A <- erlang:system_info(alloc_util_allocators)++[sys_alloc,mseg_alloc]]}.' +%% and storing it in a file. +%% If the latter option is taken, please remember to add a full stop at the end +%% of the resulting Erlang term, as this function uses `file:consult/1' to load +%% the file. +%% +%% Example usage: +%% +%%```On target machine: +%% 1> recon_alloc:snapshot(). +%% undefined +%% 2> recon_alloc:memory(used). +%% 18411064 +%% 3> recon_alloc:snapshot_save("recon_snapshot.terms"). +%% ok +%% +%% On other machine: +%% 1> recon_alloc:snapshot_load("recon_snapshot.terms"). +%% undefined +%% 2> recon_alloc:memory(used). +%% 18411064''' +%% +-spec snapshot_load(Filename) -> snapshot() | undefined when + Filename :: file:name(). +snapshot_load(Filename) -> + {ok, [Terms]} = file:consult(Filename), + Snapshot = + case Terms of + %% We handle someone using + %% {erlang:memory(), + %% [{A,erlang:system_info({allocator,A})} || + %% A <- erlang:system_info(alloc_util_allocators)++[sys_alloc,mseg_alloc]]} + %% to dump data. + {M, [{Alloc, _D} | _] = Allocs} when is_atom(Alloc) -> + {M, [{{A, N}, lists:sort(proplists:delete(versions, Props))} || + {A, Instances = [_ | _]} <- Allocs, + {_, N, Props} <- Instances]}; + %% We assume someone used recon_alloc:snapshot() to store this one + {M, Allocs} -> + {M, [{AN, lists:sort(proplists:delete(versions, Props))} || + {AN, Props} <- Allocs]} + end, + put(recon_alloc_snapshot, Snapshot). + +%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Handling of units %%% +%%%%%%%%%%%%%%%%%%%%%%%%% + +%% @doc set the current unit to be used by recon_alloc. This effects all +%% functions that return bytes. +%% +%% Eg. +%% ```1> recon_alloc:memory(used,current). +%% 17548752 +%% 2> recon_alloc:set_unit(kilobyte). +%% undefined +%% 3> recon_alloc:memory(used,current). +%% 17576.90625''' +%% +-spec set_unit(byte | kilobyte | megabyte | gigabyte) -> ok. +set_unit(byte) -> + put(recon_alloc_unit, undefined); +set_unit(kilobyte) -> + put(recon_alloc_unit, 1024); +set_unit(megabyte) -> + put(recon_alloc_unit, 1024 * 1024); +set_unit(gigabyte) -> + put(recon_alloc_unit, 1024 * 1024 * 1024). + +conv({Mem, Allocs} = D) -> + case get(recon_alloc_unit) of + undefined -> + D; + Factor -> + {conv_mem(Mem, Factor), conv_alloc(Allocs, Factor)} + end. + +conv_mem(Mem, Factor) -> + [{T, M / Factor} || {T, M} <- Mem]. + +conv_alloc([{{sys_alloc, _I}, _Props} = Alloc | R], Factor) -> + [Alloc | conv_alloc(R, Factor)]; +conv_alloc([{{mseg_alloc, _I} = AI, Props} | R], Factor) -> + MemKind = orddict:fetch(memkind, Props), + Status = orddict:fetch(status, MemKind), + {segments_size, Curr, Last, Max} = lists:keyfind(segments_size, 1, Status), + NewSegSize = {segments_size, Curr / Factor, Last / Factor, Max / Factor}, + NewStatus = lists:keyreplace(segments_size, 1, Status, NewSegSize), + NewProps = orddict:store(memkind, orddict:store(status, NewStatus, MemKind), + Props), + [{AI, NewProps} | conv_alloc(R, Factor)]; +conv_alloc([{AI, Props} | R], Factor) -> + FactorFun = fun({T, Curr}) when + T =:= blocks_size; T =:= carriers_size -> + {T, Curr / Factor}; + ({T, Curr, Last, Max}) when + T =:= blocks_size; T =:= carriers_size; + T =:= mseg_alloc_carriers_size; + T =:= sys_alloc_carriers_size -> + {T, Curr / Factor, Last / Factor, Max / Factor}; + (T) -> + T + end, + NewMbcsProp = [FactorFun(Prop) || Prop <- orddict:fetch(mbcs, Props)], + NewSbcsProp = [FactorFun(Prop) || Prop <- orddict:fetch(sbcs, Props)], + NewProps = orddict:store(sbcs, NewSbcsProp, + orddict:store(mbcs, NewMbcsProp, Props)), + case orddict:find(mbcs_pool, Props) of + error -> + [{AI, NewProps} | conv_alloc(R, Factor)]; + {ok, MbcsPoolProps} -> + NewMbcsPoolProp = [FactorFun(Prop) || Prop <- MbcsPoolProps], + NewPoolProps = orddict:store(mbcs_pool, NewMbcsPoolProp, NewProps), + [{AI, NewPoolProps} | conv_alloc(R, Factor)] + end; +conv_alloc([], _Factor) -> + []. + +%%%%%%%%%%%%%%% +%%% Private %%% +%%%%%%%%%%%%%%% + +%% Sort on small usage vs large size. +%% The weight cares about both the sbcs and mbcs values, and also +%% returns a proplist of possibly interesting values. +weighed_values({SbcsBlockSize, SbcsCarrierSize}, + {MbcsBlockSize, MbcsCarrierSize}) -> + SbcsUsage = usage(SbcsBlockSize, SbcsCarrierSize), + MbcsUsage = usage(MbcsBlockSize, MbcsCarrierSize), + SbcsWeight = (1.00 - SbcsUsage) * SbcsCarrierSize, + MbcsWeight = (1.00 - MbcsUsage) * MbcsCarrierSize, + Weight = SbcsWeight + MbcsWeight, + {Weight, [{sbcs_usage, SbcsUsage}, + {mbcs_usage, MbcsUsage}, + {sbcs_block_size, SbcsBlockSize}, + {sbcs_carriers_size, SbcsCarrierSize}, + {mbcs_block_size, MbcsBlockSize}, + {mbcs_carriers_size, MbcsCarrierSize}]}. + +%% Returns the `BlockSize/CarrierSize' as a 0.0 -> 1.0 percentage, +%% but also takes 0/0 to be 100% to make working with sorting and +%% weights simpler. +usage(0, 0) -> 1.00; +usage(0.0, 0.0) -> 1.00; +%usage(N,0) -> ???; +usage(Block, Carrier) -> Block / Carrier. + +%% Calculation for the average of blocks being used. +average_calc([]) -> + []; +average_calc([{{Instance, Type, count}, Ct}, {{Instance, Type, size}, Size} | Rest]) -> + case {Size, Ct} of + {_, 0} when Size == 0 -> [{Instance, Type, 0} | average_calc(Rest)]; + _ -> [{Instance, Type, Size / Ct} | average_calc(Rest)] + end. + +%% Regrouping/merging values together in proplists +average_group([]) -> []; +average_group([{Instance, Type1, N}, {Instance, Type2, M} | Rest]) -> + [{Instance, [{Type1, N}, {Type2, M}]} | average_group(Rest)]. + +%% Get the total carrier size +container_size(Props, Keyword, Container) -> + Sbcs = container_value(Props, Keyword, sbcs, Container), + Mbcs = container_value(Props, Keyword, mbcs, Container), + Sbcs + Mbcs. + +container_value(Props, Keyword, Type, Container) + when is_atom(Keyword) -> + container_value(Props, key2pos(Keyword), Type, Container); +container_value(Props, Pos, mbcs = Type, Container) + when Pos == ?CURRENT_POS, + ((Container =:= blocks) or (Container =:= blocks_size) + or (Container =:= carriers) or (Container =:= carriers_size)) -> + %% We include the mbcs_pool into the value for mbcs. + %% The mbcs_pool contains carriers that have been abandoned + %% by the specific allocator instance and can therefore be + %% grabbed by another instance of the same type. + %% The pool was added in R16B02 and enabled by default in 17.0. + %% See erts/emulator/internal_docs/CarrierMigration.md in + %% Erlang/OTP repo for more details. + Pool = case proplists:get_value(mbcs_pool, Props) of + PoolProps when PoolProps =/= undefined -> + element(Pos, lists:keyfind(Container, 1, PoolProps)); + _ -> 0 + end, + TypeProps = proplists:get_value(Type, Props), + Pool + element(Pos, lists:keyfind(Container, 1, TypeProps)); +container_value(Props, Pos, Type, Container) + when Type =:= sbcs; Type =:= mbcs -> + TypeProps = proplists:get_value(Type, Props), + element(Pos, lists:keyfind(Container, 1, TypeProps)). + +%% Create a new snapshot +snapshot_int() -> + {erlang:memory(), allocators()}. + +%% If no snapshot has been taken/loaded then we use current values +snapshot_get_int() -> + case snapshot_get() of + undefined -> + conv(snapshot_int()); + Snapshot -> + conv(Snapshot) + end. + +%% Get the alloc part of a snapshot +alloc() -> + {_Mem, Allocs} = snapshot_get_int(), + Allocs. +alloc(Type) -> + [{{T, Instance}, Props} || {{T, Instance}, Props} <- alloc(), + lists:member(T, Type)]. + +%% Get only alloc_util allocs +util_alloc() -> + alloc(?UTIL_ALLOCATORS). + +key2pos(current) -> + ?CURRENT_POS; +key2pos(max) -> + ?MAX_POS. diff --git a/src/srvNodeMgr/tools_cq/recon/recon_lib.erl b/src/srvNodeMgr/tools_cq/recon/recon_lib.erl new file mode 100644 index 0000000..8bcb770 --- /dev/null +++ b/src/srvNodeMgr/tools_cq/recon/recon_lib.erl @@ -0,0 +1,278 @@ +%%% @author Fred Hebert +%%% [http://ferd.ca/] +%%% @doc Regroups useful functionality used by recon when dealing with data +%%% from the node. The functions in this module allow quick runtime access +%%% to fancier behaviour than what would be done using recon module itself. +%%% @end +-module(recon_lib). +-export([sliding_window/2, sample/2, count/1, + port_list/1, port_list/2, + proc_attrs/1, proc_attrs/2, + inet_attrs/1, inet_attrs/2, + triple_to_pid/3, term_to_pid/1, + term_to_port/1, + time_map/5, time_fold/6, + scheduler_usage_diff/2, + sublist_top_n_attrs/2]). +%% private exports +-export([binary_memory/1]). + +-type diff() :: [recon:proc_attrs() | recon:inet_attrs()]. + +%% @doc Compare two samples and return a list based on some key. The type mentioned +%% for the structure is `diff()' (`{Key,Val,Other}'), which is compatible with +%% the {@link recon:proc_attrs()} type. +-spec sliding_window(First :: diff(), Last :: diff()) -> diff(). +sliding_window(First, Last) -> + Dict = lists:foldl( + fun({Key, {Current, Other}}, Acc) -> + dict:update(Key, + fun({Old, _Other}) -> {Current - Old, Other} end, + {Current, Other}, + Acc) + end, + dict:from_list([{K, {V, O}} || {K, V, O} <- First]), + [{K, {V, O}} || {K, V, O} <- Last] + ), + [{K, V, O} || {K, {V, O}} <- dict:to_list(Dict)]. + +%% @doc Runs a fun once, waits `Ms', runs the fun again, +%% and returns both results. +-spec sample(Ms :: non_neg_integer(), fun(() -> term())) -> + {First :: term(), Second :: term()}. +sample(Delay, Fun) -> + First = Fun(), + timer:sleep(Delay), + Second = Fun(), + {First, Second}. + +%% @doc Takes a list of terms, and counts how often each of +%% them appears in the list. The list returned is in no +%% particular order. +-spec count([term()]) -> [{term(), Count :: integer()}]. +count(Terms) -> + Dict = lists:foldl( + fun(Val, Acc) -> dict:update_counter(Val, 1, Acc) end, + dict:new(), + Terms + ), + dict:to_list(Dict). + +%% @doc Returns a list of all the open ports in the VM, coupled with +%% one of the properties desired from `erlang:port_info/1-2'. +-spec port_list(Attr :: atom()) -> [{port(), term()}]. +port_list(Attr) -> + [{Port, Val} || Port <- erlang:ports(), + {_, Val} <- [erlang:port_info(Port, Attr)]]. + +%% @doc Returns a list of all the open ports in the VM, but only +%% if the `Attr''s resulting value matches `Val'. `Attr' must be +%% a property accepted by `erlang:port_info/2'. +-spec port_list(Attr :: atom(), term()) -> [port()]. +port_list(Attr, Val) -> + [Port || Port <- erlang:ports(), + {Attr, Val} =:= erlang:port_info(Port, Attr)]. + +%% @doc Returns the attributes ({@link recon:proc_attrs()}) of +%% all processes of the node, except the caller. +-spec proc_attrs(term()) -> [recon:proc_attrs()]. +proc_attrs(AttrName) -> + [Attrs || Pid <- processes() -- [self()], + {ok, Attrs} <- [proc_attrs(AttrName, Pid)]]. + +%% @doc Returns the attributes of a given process. This form of attributes +%% is standard for most comparison functions for processes in recon. +%% +%% A special attribute is `binary_memory', which will reduce the memory used +%% by the process for binary data on the global heap. +-spec proc_attrs(term(), pid()) -> {ok, recon:proc_attrs()} | {error, term()}. +proc_attrs(binary_memory, Pid) -> + case process_info(Pid, [binary, registered_name, + current_function, initial_call]) of + [{_, Bins}, {registered_name, Name}, Init, Cur] -> + {ok, {Pid, binary_memory(Bins), [Name || is_atom(Name)] ++ [Init, Cur]}}; + undefined -> + {error, undefined} + end; +proc_attrs(AttrName, Pid) -> + case process_info(Pid, [AttrName, registered_name, + current_function, initial_call]) of + [{_, Attr}, {registered_name, Name}, Init, Cur] -> + {ok, {Pid, Attr, [Name || is_atom(Name)] ++ [Init, Cur]}}; + undefined -> + {error, undefined} + end. + +%% @doc Returns the attributes ({@link recon:inet_attrs()}) of +%% all inet ports (UDP, SCTP, TCP) of the node. +-spec inet_attrs(term()) -> [recon:inet_attrs()]. +inet_attrs(AttrName) -> + Ports = [Port || Port <- erlang:ports(), + {_, Name} <- [erlang:port_info(Port, name)], + Name =:= "tcp_inet" orelse + Name =:= "udp_inet" orelse + Name =:= "sctp_inet"], + [Attrs || Port <- Ports, + {ok, Attrs} <- [inet_attrs(AttrName, Port)]]. + +%% @doc Returns the attributes required for a given inet port (UDP, +%% SCTP, TCP). This form of attributes is standard for most comparison +%% functions for processes in recon. +-spec inet_attrs(AttributeName, port()) -> {ok, recon:inet_attrs()} +| {error, term()} when + AttributeName :: 'recv_cnt' | 'recv_oct' | 'send_cnt' | 'send_oct' + | 'cnt' | 'oct'. +inet_attrs(Attr, Port) -> + Attrs = case Attr of + cnt -> [recv_cnt, send_cnt]; + oct -> [recv_oct, send_oct]; + _ -> [Attr] + end, + case inet:getstat(Port, Attrs) of + {ok, Props} -> + ValSum = lists:foldl(fun({_, X}, Y) -> X + Y end, 0, Props), + {ok, {Port, ValSum, Props}}; + {error, Reason} -> + {error, Reason} + end. + + +%% @doc Equivalent of `pid(X,Y,Z)' in the Erlang shell. +-spec triple_to_pid(N, N, N) -> pid() when + N :: non_neg_integer(). +triple_to_pid(X, Y, Z) -> + list_to_pid("<" ++ integer_to_list(X) ++ "." ++ + integer_to_list(Y) ++ "." ++ + integer_to_list(Z) ++ ">"). + +%% @doc Transforms a given term to a pid. +-spec term_to_pid(recon:pid_term()) -> pid(). +term_to_pid(Pid) when is_pid(Pid) -> Pid; +term_to_pid(Name) when is_atom(Name) -> whereis(Name); +term_to_pid(List = "<0." ++ _) -> list_to_pid(List); +term_to_pid(Binary = <<"<0.", _/binary>>) -> list_to_pid(binary_to_list(Binary)); +term_to_pid({global, Name}) -> global:whereis_name(Name); +term_to_pid({via, Module, Name}) -> Module:whereis_name(Name); +term_to_pid({X, Y, Z}) when is_integer(X), is_integer(Y), is_integer(Z) -> + triple_to_pid(X, Y, Z). + +%% @doc Transforms a given term to a port +-spec term_to_port(recon:port_term()) -> port(). +term_to_port(Port) when is_port(Port) -> Port; +term_to_port(Name) when is_atom(Name) -> whereis(Name); +term_to_port("#Port<0." ++ Id) -> + N = list_to_integer(lists:sublist(Id, length(Id) - 1)), % drop trailing '>' + term_to_port(N); +term_to_port(N) when is_integer(N) -> + %% We rebuild the term from the int received: + %% http://www.erlang.org/doc/apps/erts/erl_ext_dist.html#id86892 + Name = iolist_to_binary(atom_to_list(node())), + NameLen = iolist_size(Name), + Vsn = binary:last(term_to_binary(self())), + Bin = <<131, % term encoding value + 102, % port tag + 100, % atom ext tag, used for node name + NameLen:2/unit:8, + Name:NameLen/binary, + N:4/unit:8, % actual counter value + Vsn:8>>, % version + binary_to_term(Bin). + +%% @doc Calls a given function every `Interval' milliseconds and supports +%% a map-like interface (each result is modified and returned) +-spec time_map(N, Interval, Fun, State, MapFun) -> [term()] when + N :: non_neg_integer(), + Interval :: pos_integer(), + Fun :: fun((State) -> {term(), State}), + State :: term(), + MapFun :: fun((_) -> term()). +time_map(0, _, _, _, _) -> + []; +time_map(N, Interval, Fun, State, MapFun) -> + {Res, NewState} = Fun(State), + timer:sleep(Interval), + [MapFun(Res) | time_map(N - 1, Interval, Fun, NewState, MapFun)]. + +%% @doc Calls a given function every `Interval' milliseconds and supports +%% a fold-like interface (each result is modified and accumulated) +-spec time_fold(N, Interval, Fun, State, FoldFun, Init) -> [term()] when + N :: non_neg_integer(), + Interval :: pos_integer(), + Fun :: fun((State) -> {term(), State}), + State :: term(), + FoldFun :: fun((term(), Init) -> Init), + Init :: term(). +time_fold(0, _, _, _, _, Acc) -> + Acc; +time_fold(N, Interval, Fun, State, FoldFun, Init) -> + {Res, NewState} = Fun(State), + timer:sleep(Interval), + Acc = FoldFun(Res, Init), + time_fold(N - 1, Interval, Fun, NewState, FoldFun, Acc). + +%% @doc Diffs two runs of erlang:statistics(scheduler_wall_time) and +%% returns usage metrics in terms of cores and 0..1 percentages. +-spec scheduler_usage_diff(SchedTime, SchedTime) -> [{SchedulerId, Usage}] when + SchedTime :: [{SchedulerId, ActiveTime, TotalTime}], + SchedulerId :: pos_integer(), + Usage :: number(), + ActiveTime :: non_neg_integer(), + TotalTime :: non_neg_integer(). +scheduler_usage_diff(First, Last) -> + lists:map( + fun({{I, _A0, T}, {I, _A1, T}}) -> {I, 0.0}; % Avoid divide by zero + ({{I, A0, T0}, {I, A1, T1}}) -> {I, (A1 - A0) / (T1 - T0)} + end, + lists:zip(lists:sort(First), lists:sort(Last)) + ). + +%% @doc Returns the top n element of a list of process or inet attributes +-spec sublist_top_n_attrs([Attrs], pos_integer()) -> [Attrs] + when Attrs :: recon:proc_attrs() | recon:inet_attrs(). +sublist_top_n_attrs(_, 0) -> + %% matching lists:sublist/2 behaviour + []; +sublist_top_n_attrs(List, Len) -> + pheap_fill(List, Len, []). + +%% @private crush binaries from process_info into their amount of place +%% taken in memory. +binary_memory(Bins) -> + lists:foldl(fun({_, Mem, _}, Tot) -> Mem + Tot end, 0, Bins). + +%%%%%%%%%%%%%%% +%%% PRIVATE %%% +%%%%%%%%%%%%%%% +pheap_fill(List, 0, Heap) -> + pheap_full(List, Heap); +pheap_fill([], _, Heap) -> + pheap_to_list(Heap, []); +pheap_fill([{Y, X, _} = H | T], N, Heap) -> + pheap_fill(T, N - 1, insert({{X, Y}, H}, Heap)). + +pheap_full([], Heap) -> + pheap_to_list(Heap, []); +pheap_full([{Y, X, _} = H | T], [{K, _} | HeapT] = Heap) -> + case {X, Y} of + N when N > K -> + pheap_full(T, insert({N, H}, merge_pairs(HeapT))); + _ -> + pheap_full(T, Heap) + end. + +pheap_to_list([], Acc) -> Acc; +pheap_to_list([{_, H} | T], Acc) -> + pheap_to_list(merge_pairs(T), [H | Acc]). + +-compile({inline, [insert/2, merge/2]}). +insert(E, []) -> [E]; %% merge([E], H) +insert(E, [E2 | _] = H) when E =< E2 -> [E, H]; +insert(E, [E2 | H]) -> [E2, [E] | H]. + +merge(H1, []) -> H1; +merge([E1 | H1], [E2 | _] = H2) when E1 =< E2 -> [E1, H2 | H1]; +merge(H1, [E2 | H2]) -> [E2, H1 | H2]. + +merge_pairs([]) -> []; +merge_pairs([H]) -> H; +merge_pairs([A, B | T]) -> merge(merge(A, B), merge_pairs(T)). diff --git a/src/srvNodeMgr/tools_cq/recon/recon_trace.erl b/src/srvNodeMgr/tools_cq/recon/recon_trace.erl new file mode 100644 index 0000000..7fad89f --- /dev/null +++ b/src/srvNodeMgr/tools_cq/recon/recon_trace.erl @@ -0,0 +1,644 @@ +%%% @author Fred Hebert +%%% [http://ferd.ca/] +%%% @doc +%%% `recon_trace' is a module that handles tracing in a safe manner for single +%%% Erlang nodes, currently for function calls only. Functionality includes: +%%% +%%%
    +%%%
  • Nicer to use interface (arguably) than `dbg' or trace BIFs.
  • +%%%
  • Protection against dumb decisions (matching all calls on a node +%%% being traced, for example)
  • +%%%
  • Adding safe guards in terms of absolute trace count or +%%% rate-limitting
  • +%%%
  • Nicer formatting than default traces
  • +%%%
+%%% +%%% == Tracing Erlang Code == +%%% +%%% The Erlang Trace BIFs allow to trace any Erlang code at all. They work in +%%% two parts: pid specifications, and trace patterns. +%%% +%%% Pid specifications let you decide which processes to target. They can be +%%% specific pids, `all' pids, `existing' pids, or `new' pids (those not +%%% spawned at the time of the function call). +%%% +%%% The trace patterns represent functions. Functions can be specified in two +%%% parts: specifying the modules, functions, and arguments, and then with +%%% Erlang match specifications to add constraints to arguments (see +%%% {@link calls/3} for details). +%%% +%%% What defines whether you get traced or not is the intersection of both: +%%% +%%% ``` +%%% _,--------,_ _,--------,_ +%%% ,-' `-,,-' `-, +%%% ,-' ,-' '-, `-, +%%% | Matching -' '- Matching | +%%% | Pids | Getting | Trace | +%%% | | Traced | Patterns | +%%% | -, ,- | +%%% '-, '-, ,-' ,-' +%%% '-,_ _,-''-,_ _,-' +%%% '--------' '--------' +%%% ''' +%%% +%%% If either the pid specification excludes a process or a trace pattern +%%% excludes a given call, no trace will be received. +%%% +%%% == Example Session == +%%% +%%% First let's trace the `queue:new' functions in any process: +%%% +%%% ``` +%%% 1> recon_trace:calls({queue, new, '_'}, 1). +%%% 1 +%%% 13:14:34.086078 <0.44.0> queue:new() +%%% Recon tracer rate limit tripped. +%%% ''' +%%% +%%% The limit was set to `1' trace message at most, and `recon' let us +%%% know when that limit was reached. +%%% +%%% Let's instead look for all the `queue:in/2' calls, to see what it is +%%% we're inserting in queues: +%%% +%%% ``` +%%% 2> recon_trace:calls({queue, in, 2}, 1). +%%% 1 +%%% 13:14:55.365157 <0.44.0> queue:in(a, {[],[]}) +%%% Recon tracer rate limit tripped. +%%% ''' +%%% +%%% In order to see the content we want, we should change the trace patterns +%%% to use a `fun' that matches on all arguments in a list (`_') and returns +%%% `return_trace()'. This last part will generate a second trace for each +%%% call that includes the return value: +%%% +%%% ``` +%%% 3> recon_trace:calls({queue, in, fun(_) -> return_trace() end}, 3). +%%% 1 +%%% +%%% 13:15:27.655132 <0.44.0> queue:in(a, {[],[]}) +%%% +%%% 13:15:27.655467 <0.44.0> queue:in/2 --> {[a],[]} +%%% +%%% 13:15:27.757921 <0.44.0> queue:in(a, {[],[]}) +%%% Recon tracer rate limit tripped. +%%% ''' +%%% +%%% Matching on argument lists can be done in a more complex manner: +%%% +%%% ``` +%%% 4> recon_trace:calls( +%%% 4> {queue, '_', fun([A,_]) when is_list(A); is_integer(A) andalso A > 1 -> return_trace() end}, +%%% 4> {10,100} +%%% 4> ). +%%% 32 +%%% +%%% 13:24:21.324309 <0.38.0> queue:in(3, {[],[]}) +%%% +%%% 13:24:21.371473 <0.38.0> queue:in/2 --> {[3],[]} +%%% +%%% 13:25:14.694865 <0.53.0> queue:split(4, {[10,9,8,7],[1,2,3,4,5,6]}) +%%% +%%% 13:25:14.695194 <0.53.0> queue:split/2 --> {{[4,3,2],[1]},{[10,9,8,7],[5,6]}} +%%% +%%% 5> recon_trace:clear(). +%%% ok +%%% ''' +%%% +%%% Note that in the pattern above, no specific function ('_') was +%%% matched against. Instead, the `fun' used restricted functions to those +%%% having two arguments, the first of which is either a list or an integer +%%% greater than `1'. +%%% +%%% The limit was also set using `{10,100}' instead of an integer, making the +%%% rate-limitting at 10 messages per 100 milliseconds, instead of an absolute +%%% value. +%%% +%%% Any tracing can be manually interrupted by calling `recon_trace:clear()', +%%% or killing the shell process. +%%% +%%% Be aware that extremely broad patterns with lax rate-limitting (or very +%%% high absolute limits) may impact your node's stability in ways +%%% `recon_trace' cannot easily help you with. +%%% +%%% In doubt, start with the most restrictive tracing possible, with low +%%% limits, and progressively increase your scope. +%%% +%%% See {@link calls/3} for more details and tracing possibilities. +%%% +%%% == Structure == +%%% +%%% This library is production-safe due to taking the following structure for +%%% tracing: +%%% +%%% ``` +%%% [IO/Group leader] <---------------------, +%%% | | +%%% [shell] ---> [tracer process] ----> [formatter] +%%% ''' +%%% +%%% The tracer process receives trace messages from the node, and enforces +%%% limits in absolute terms or trace rates, before forwarding the messages +%%% to the formatter. This is done so the tracer can do as little work as +%%% possible and never block while building up a large mailbox. +%%% +%%% The tracer process is linked to the shell, and the formatter to the +%%% tracer process. The formatter also traps exits to be able to handle +%%% all received trace messages until the tracer termination, but will then +%%% shut down as soon as possible. +%%% +%%% In case the operator is tracing from a remote shell which gets +%%% disconnected, the links between the shell and the tracer should make it +%%% so tracing is automatically turned off once you disconnect. +%%% +%%% If sending output to the Group Leader is not desired, you may specify +%%% a different pid() via the option `io_server' in the {@link calls/3} function. +%%% For instance to write the traces to a file you can do something like +%%% +%%% ``` +%%% 1> {ok, Dev} = file:open("/tmp/trace",[write]). +%%% 2> recon_trace:calls({queue, in, fun(_) -> return_trace() end}, 3, [{io_server, Dev}]). +%%% 1 +%%% 3> +%%% Recon tracer rate limit tripped. +%%% 4> file:close(Dev). +%%% ''' +%%% +%%% The only output still sent to the Group Leader is the rate limit being +%%% tripped, and any errors. The rest will be sent to the other IO +%%% server (see [http://erlang.org/doc/apps/stdlib/io_protocol.html]). +%%% @end +-module(recon_trace). + +%% API +-export([clear/0, calls/2, calls/3]). + +-export([format/1]). + +%% Internal exports +-export([count_tracer/1, rate_tracer/2, formatter/5]). + +-type matchspec() :: [{[term()], [term()], [term()]}]. +-type shellfun() :: fun((_) -> term()). +-type formatterfun() :: fun((_) -> iodata()). +-type millisecs() :: non_neg_integer(). +-type pidspec() :: all | existing | new | recon:pid_term(). +-type max_traces() :: non_neg_integer(). +-type max_rate() :: {max_traces(), millisecs()}. + +%% trace options +-type options() :: [{pid, pidspec() | [pidspec(), ...]} % default: all +| {timestamp, formatter | trace} % default: formatter +| {args, args | arity} % default: args +| {io_server, pid()} % default: group_leader() +| {formatter, formatterfun()} % default: internal formatter +| return_to | {return_to, boolean()} % default: false +%% match pattern options +| {scope, global | local} % default: global +]. + +-type mod() :: '_' | module(). +-type fn() :: '_' | atom(). +-type args() :: '_' | 0..255 | return_trace | matchspec() | shellfun(). +-type tspec() :: {mod(), fn(), args()}. +-type max() :: max_traces() | max_rate(). +-type num_matches() :: non_neg_integer(). + +-export_type([mod/0, fn/0, args/0, tspec/0, num_matches/0, options/0, + max_traces/0, max_rate/0]). + +%%%%%%%%%%%%%% +%%% PUBLIC %%% +%%%%%%%%%%%%%% + +%% @doc Stops all tracing at once. +-spec clear() -> ok. +clear() -> + erlang:trace(all, false, [all]), + erlang:trace_pattern({'_', '_', '_'}, false, [local, meta, call_count, call_time]), + erlang:trace_pattern({'_', '_', '_'}, false, []), % unsets global + maybe_kill(recon_trace_tracer), + maybe_kill(recon_trace_formatter), + ok. + +%% @equiv calls({Mod, Fun, Args}, Max, []) +-spec calls(tspec() | [tspec(), ...], max()) -> num_matches(). +calls({Mod, Fun, Args}, Max) -> + calls([{Mod, Fun, Args}], Max, []); +calls(TSpecs = [_ | _], Max) -> + calls(TSpecs, Max, []). + +%% @doc Allows to set trace patterns and pid specifications to trace +%% function calls. +%% +%% The basic calls take the trace patterns as tuples of the form +%% `{Module, Function, Args}' where: +%% +%%
    +%%
  • `Module' is any atom representing a module
  • +%%
  • `Function' is any atom representing a function, or the wildcard +%% '_'
  • +%%
  • `Args' is either the arity of a function (`0..255'), a wildcard +%% pattern ('_'), a +%% match specification, +%% or a function from a shell session that can be transformed into +%% a match specification
  • +%%
+%% +%% There is also an argument specifying either a maximal count (a number) +%% of trace messages to be received, or a maximal frequency (`{Num, Millisecs}'). +%% +%% Here are examples of things to trace: +%% +%%
    +%%
  • All calls from the `queue' module, with 10 calls printed at most: +%% ``recon_trace:calls({queue, '_', '_'}, 10)''
  • +%%
  • All calls to `lists:seq(A,B)', with 100 calls printed at most: +%% `recon_trace:calls({lists, seq, 2}, 100)'
  • +%%
  • All calls to `lists:seq(A,B)', with 100 calls per second at most: +%% `recon_trace:calls({lists, seq, 2}, {100, 1000})'
  • +%%
  • All calls to `lists:seq(A,B,2)' (all sequences increasing by two) +%% with 100 calls at most: +%% `recon_trace:calls({lists, seq, fun([_,_,2]) -> ok end}, 100)'
  • +%%
  • All calls to `iolist_to_binary/1' made with a binary as an argument +%% already (kind of useless conversion!): +%% `recon_trace:calls({erlang, iolist_to_binary, fun([X]) when is_binary(X) -> ok end}, 10)'
  • +%%
  • Calls to the queue module only in a given process `Pid', at a rate +%% of 50 per second at most: +%% ``recon_trace:calls({queue, '_', '_'}, {50,1000}, [{pid, Pid}])''
  • +%%
  • Print the traces with the function arity instead of literal arguments: +%% `recon_trace:calls(TSpec, Max, [{args, arity}])'
  • +%%
  • Matching the `filter/2' functions of both `dict' and `lists' modules, +%% across new processes only: +%% `recon_trace:calls([{dict,filter,2},{lists,filter,2}], 10, [{pid, new}])'
  • +%%
  • Tracing the `handle_call/3' functions of a given module for all new processes, +%% and those of an existing one registered with `gproc': +%% `recon_trace:calls({Mod,handle_call,3}, {10,100}, [{pid, [{via, gproc, Name}, new]}'
  • +%%
  • Show the result of a given function call: +%% `recon_trace:calls({Mod,Fun,fun(_) -> return_trace() end}, Max, Opts)' +%% or +%% ``recon_trace:calls({Mod,Fun,[{'_', [], [{return_trace}]}]}, Max, Opts)'', +%% the important bit being the `return_trace()' call or the +%% `{return_trace}' match spec value. +%% A short-hand version for this pattern of 'match anything, trace everything' +%% for a function is `recon_trace:calls({Mod, Fun, return_trace})'.
  • +%%
+%% +%% There's a few more combination possible, with multiple trace patterns per call, and more +%% options: +%% +%%
    +%%
  • `{pid, PidSpec}': which processes to trace. Valid options is any of +%% `all', `new', `existing', or a process descriptor (`{A,B,C}', +%% `""', an atom representing a name, `{global, Name}', +%% `{via, Registrar, Name}', or a pid). It's also possible to specify +%% more than one by putting them in a list.
  • +%%
  • `{timestamp, formatter | trace}': by default, the formatter process +%% adds timestamps to messages received. If accurate timestamps are +%% required, it's possible to force the usage of timestamps within +%% trace messages by adding the option `{timestamp, trace}'.
  • +%%
  • `{args, arity | args}': whether to print arity in function calls +%% or their (by default) literal representation.
  • +%%
  • `{scope, global | local}': by default, only 'global' (fully qualified +%% function calls) are traced, not calls made internally. To force tracing +%% of local calls, pass in `{scope, local}'. This is useful whenever +%% you want to track the changes of code in a process that isn't called +%% with `Module:Fun(Args)', but just `Fun(Args)'.
  • +%%
  • `{formatter, fun(Term) -> io_data() end}': override the default +%% formatting functionality provided by recon.
  • +%%
  • `{io_server, pid() | atom()}': by default, recon logs to the current +%% group leader, usually the shell. This option allows to redirect +%% trace output to a different IO server (such as a file handle).
  • +%%
  • `return_to': If this option is set (in conjunction with the match +%% option `{scope, local}'), the function to which the value is returned +%% is output in a trace. Note that this is distinct from giving the +%% *caller* since exception handling or calls in tail position may +%% hide the original caller.
  • +%%
+%% +%% Also note that putting extremely large `Max' values (i.e. `99999999' or +%% `{10000,1}') will probably negate most of the safe-guarding this library +%% does and be dangerous to your node. Similarly, tracing extremely large +%% amounts of function calls (all of them, or all of `io' for example) +%% can be risky if more trace messages are generated than any process on +%% the node could ever handle, despite the precautions taken by this library. +%% @end +-spec calls(tspec() | [tspec(), ...], max(), options()) -> num_matches(). + +calls({Mod, Fun, Args}, Max, Opts) -> + calls([{Mod, Fun, Args}], Max, Opts); +calls(TSpecs = [_ | _], {Max, Time}, Opts) -> + Pid = setup(rate_tracer, [Max, Time], + validate_formatter(Opts), validate_io_server(Opts)), + trace_calls(TSpecs, Pid, Opts); +calls(TSpecs = [_ | _], Max, Opts) -> + Pid = setup(count_tracer, [Max], + validate_formatter(Opts), validate_io_server(Opts)), + trace_calls(TSpecs, Pid, Opts). + +%%%%%%%%%%%%%%%%%%%%%%% +%%% PRIVATE EXPORTS %%% +%%%%%%%%%%%%%%%%%%%%%%% +%% @private Stops when N trace messages have been received +count_tracer(0) -> + exit(normal); +count_tracer(N) -> + receive + Msg -> + recon_trace_formatter ! Msg, + count_tracer(N - 1) + end. + +%% @private Stops whenever the trace message rates goes higher than +%% `Max' messages in `Time' milliseconds. Note that if the rate +%% proposed is higher than what the IO system of the formatter +%% can handle, this can still put a node at risk. +%% +%% It is recommended to try stricter rates to begin with. +rate_tracer(Max, Time) -> rate_tracer(Max, Time, 0, os:timestamp()). + +rate_tracer(Max, Time, Count, Start) -> + receive + Msg -> + recon_trace_formatter ! Msg, + Now = os:timestamp(), + Delay = timer:now_diff(Now, Start) div 1000, + if Delay > Time -> rate_tracer(Max, Time, 0, Now) + ; Max > Count -> rate_tracer(Max, Time, Count + 1, Start) + ; Max =:= Count -> exit(normal) + end + end. + +%% @private Formats traces to be output +formatter(Tracer, Parent, Ref, FormatterFun, IOServer) -> + process_flag(trap_exit, true), + link(Tracer), + Parent ! {Ref, linked}, + formatter(Tracer, IOServer, FormatterFun). + +formatter(Tracer, IOServer, FormatterFun) -> + receive + {'EXIT', Tracer, normal} -> + io:format("Recon tracer rate limit tripped.~n"), + exit(normal); + {'EXIT', Tracer, Reason} -> + exit(Reason); + TraceMsg -> + io:format(IOServer, FormatterFun(TraceMsg), []), + formatter(Tracer, IOServer, FormatterFun) + end. + + +%%%%%%%%%%%%%%%%%%%%%%% +%%% SETUP FUNCTIONS %%% +%%%%%%%%%%%%%%%%%%%%%%% + +%% starts the tracer and formatter processes, and +%% cleans them up before each call. +setup(TracerFun, TracerArgs, FormatterFun, IOServer) -> + clear(), + Ref = make_ref(), + Tracer = spawn_link(?MODULE, TracerFun, TracerArgs), + register(recon_trace_tracer, Tracer), + Format = spawn(?MODULE, formatter, [Tracer, self(), Ref, FormatterFun, IOServer]), + register(recon_trace_formatter, Format), + receive + {Ref, linked} -> Tracer + after 5000 -> + error(setup_failed) + end. + +%% Sets the traces in action +trace_calls(TSpecs, Pid, Opts) -> + {PidSpecs, TraceOpts, MatchOpts} = validate_opts(Opts), + Matches = [begin + {Arity, Spec} = validate_tspec(Mod, Fun, Args), + erlang:trace_pattern({Mod, Fun, Arity}, Spec, MatchOpts) + end || {Mod, Fun, Args} <- TSpecs], + [erlang:trace(PidSpec, true, [call, {tracer, Pid} | TraceOpts]) + || PidSpec <- PidSpecs], + lists:sum(Matches). + + +%%%%%%%%%%%%%%%%%% +%%% VALIDATION %%% +%%%%%%%%%%%%%%%%%% + +validate_opts(Opts) -> + PidSpecs = validate_pid_specs(proplists:get_value(pid, Opts, all)), + Scope = proplists:get_value(scope, Opts, global), + TraceOpts = case proplists:get_value(timestamp, Opts, formatter) of + formatter -> []; + trace -> [timestamp] + end ++ + case proplists:get_value(args, Opts, args) of + args -> []; + arity -> [arity] + end ++ + case proplists:get_value(return_to, Opts, undefined) of + true when Scope =:= local -> + [return_to]; + true when Scope =:= global -> + io:format("Option return_to only works with option {scope, local}~n"), + %% Set it anyway + [return_to]; + _ -> + [] + end, + MatchOpts = [Scope], + {PidSpecs, TraceOpts, MatchOpts}. + +%% Support the regular specs, but also allow `recon:pid_term()' and lists +%% of further pid specs. +-spec validate_pid_specs(pidspec() | [pidspec(), ...]) -> + [all | new | existing | pid(), ...]. +validate_pid_specs(all) -> [all]; +validate_pid_specs(existing) -> [existing]; +validate_pid_specs(new) -> [new]; +validate_pid_specs([Spec]) -> validate_pid_specs(Spec); +validate_pid_specs(PidTerm = [Spec | Rest]) -> + %% can be "" or [pidspec()] + try + [recon_lib:term_to_pid(PidTerm)] + catch + error:function_clause -> + validate_pid_specs(Spec) ++ validate_pid_specs(Rest) + end; +validate_pid_specs(PidTerm) -> + %% has to be `recon:pid_term()'. + [recon_lib:term_to_pid(PidTerm)]. + +validate_tspec(Mod, Fun, Args) when is_function(Args) -> + validate_tspec(Mod, Fun, fun_to_ms(Args)); +%% helper to save typing for common actions +validate_tspec(Mod, Fun, return_trace) -> + validate_tspec(Mod, Fun, [{'_', [], [{return_trace}]}]); +validate_tspec(Mod, Fun, Args) -> + BannedMods = ['_', ?MODULE, io, lists], + %% The banned mod check can be bypassed by using + %% match specs if you really feel like being dumb. + case {lists:member(Mod, BannedMods), Args} of + {true, '_'} -> error({dangerous_combo, {Mod, Fun, Args}}); + {true, []} -> error({dangerous_combo, {Mod, Fun, Args}}); + _ -> ok + end, + case Args of + '_' -> {'_', true}; + _ when is_list(Args) -> {'_', Args}; + _ when Args >= 0, Args =< 255 -> {Args, true} + end. + +validate_formatter(Opts) -> + case proplists:get_value(formatter, Opts) of + F when is_function(F, 1) -> F; + _ -> fun format/1 + end. + +validate_io_server(Opts) -> + proplists:get_value(io_server, Opts, group_leader()). + +%%%%%%%%%%%%%%%%%%%%%%%% +%%% TRACE FORMATTING %%% +%%%%%%%%%%%%%%%%%%%%%%%% +%% Thanks Geoff Cant for the foundations for this. +format(TraceMsg) -> + {Type, Pid, {Hour, Min, Sec}, TraceInfo} = extract_info(TraceMsg), + {FormatStr, FormatArgs} = case {Type, TraceInfo} of + %% {trace, Pid, 'receive', Msg} + {'receive', [Msg]} -> + {"< ~p", [Msg]}; + %% {trace, Pid, send, Msg, To} + {send, [Msg, To]} -> + {" > ~p: ~p", [To, Msg]}; + %% {trace, Pid, send_to_non_existing_process, Msg, To} + {send_to_non_existing_process, [Msg, To]} -> + {" > (non_existent) ~p: ~p", [To, Msg]}; + %% {trace, Pid, call, {M, F, Args}} + {call, [{M, F, Args}]} -> + {"~p:~p~s", [M, F, format_args(Args)]}; + %% {trace, Pid, return_to, {M, F, Arity}} + {return_to, [{M, F, Arity}]} -> + {" '--> ~p:~p/~p", [M, F, Arity]}; + %% {trace, Pid, return_from, {M, F, Arity}, ReturnValue} + {return_from, [{M, F, Arity}, Return]} -> + {"~p:~p/~p --> ~p", [M, F, Arity, Return]}; + %% {trace, Pid, exception_from, {M, F, Arity}, {Class, Value}} + {exception_from, [{M, F, Arity}, {Class, Val}]} -> + {"~p:~p/~p ~p ~p", [M, F, Arity, Class, Val]}; + %% {trace, Pid, spawn, Spawned, {M, F, Args}} + {spawn, [Spawned, {M, F, Args}]} -> + {"spawned ~p as ~p:~p~s", [Spawned, M, F, format_args(Args)]}; + %% {trace, Pid, exit, Reason} + {exit, [Reason]} -> + {"EXIT ~p", [Reason]}; + %% {trace, Pid, link, Pid2} + {link, [Linked]} -> + {"link(~p)", [Linked]}; + %% {trace, Pid, unlink, Pid2} + {unlink, [Linked]} -> + {"unlink(~p)", [Linked]}; + %% {trace, Pid, getting_linked, Pid2} + {getting_linked, [Linker]} -> + {"getting linked by ~p", [Linker]}; + %% {trace, Pid, getting_unlinked, Pid2} + {getting_unlinked, [Unlinker]} -> + {"getting unlinked by ~p", [Unlinker]}; + %% {trace, Pid, register, RegName} + {register, [Name]} -> + {"registered as ~p", [Name]}; + %% {trace, Pid, unregister, RegName} + {unregister, [Name]} -> + {"no longer registered as ~p", [Name]}; + %% {trace, Pid, in, {M, F, Arity} | 0} + {in, [{M, F, Arity}]} -> + {"scheduled in for ~p:~p/~p", [M, F, Arity]}; + {in, [0]} -> + {"scheduled in", []}; + %% {trace, Pid, out, {M, F, Arity} | 0} + {out, [{M, F, Arity}]} -> + {"scheduled out from ~p:~p/~p", [M, F, Arity]}; + {out, [0]} -> + {"scheduled out", []}; + %% {trace, Pid, gc_start, Info} + {gc_start, [Info]} -> + HeapSize = proplists:get_value(heap_size, Info), + OldHeapSize = proplists:get_value(old_heap_size, Info), + MbufSize = proplists:get_value(mbuf_size, Info), + {"gc beginning -- heap ~p bytes", + [HeapSize + OldHeapSize + MbufSize]}; + %% {trace, Pid, gc_end, Info} + {gc_end, [Info]} -> + HeapSize = proplists:get_value(heap_size, Info), + OldHeapSize = proplists:get_value(old_heap_size, Info), + MbufSize = proplists:get_value(mbuf_size, Info), + {"gc finished -- heap ~p bytes", + [HeapSize + OldHeapSize + MbufSize]}; + _ -> + {"unknown trace type ~p -- ~p", [Type, TraceInfo]} + end, + io_lib:format("~n~p:~p:~9.6.0f ~p " ++ FormatStr ++ "~n", + [Hour, Min, Sec, Pid] ++ FormatArgs). + +extract_info(TraceMsg) -> + case tuple_to_list(TraceMsg) of + [trace_ts, Pid, Type | Info] -> + {TraceInfo, [Timestamp]} = lists:split(length(Info) - 1, Info), + {Type, Pid, to_hms(Timestamp), TraceInfo}; + [trace, Pid, Type | TraceInfo] -> + {Type, Pid, to_hms(os:timestamp()), TraceInfo} + end. + +to_hms(Stamp = {_, _, Micro}) -> + {_, {H, M, Secs}} = calendar:now_to_local_time(Stamp), + Seconds = Secs rem 60 + (Micro / 1000000), + {H, M, Seconds}; +to_hms(_) -> + {0, 0, 0}. + +format_args(Arity) when is_integer(Arity) -> + "/" ++ integer_to_list(Arity); +format_args(Args) when is_list(Args) -> + "(" ++ string:join([io_lib:format("~p", [Arg]) || Arg <- Args], ", ") ++ ")". + + +%%%%%%%%%%%%%%% +%%% HELPERS %%% +%%%%%%%%%%%%%%% + +maybe_kill(Name) -> + case whereis(Name) of + undefined -> + ok; + Pid -> + unlink(Pid), + exit(Pid, kill), + wait_for_death(Pid, Name) + end. + +wait_for_death(Pid, Name) -> + case is_process_alive(Pid) orelse whereis(Name) =:= Pid of + true -> + timer:sleep(10), + wait_for_death(Pid, Name); + false -> + ok + end. + +%% Borrowed from dbg +fun_to_ms(ShellFun) when is_function(ShellFun) -> + case erl_eval:fun_data(ShellFun) of + {fun_data, ImportList, Clauses} -> + case ms_transform:transform_from_shell( + dbg, Clauses, ImportList) of + {error, [{_, [{_, _, Code} | _]} | _], _} -> + io:format("Error: ~s~n", + [ms_transform:format_error(Code)]), + {error, transform_error}; + Else -> + Else + end; + false -> + exit(shell_funs_only) + end. diff --git a/src/srvNodeMgr/tools_cq/seek_info.erl b/src/srvNodeMgr/tools_cq/seek_info.erl new file mode 100644 index 0000000..5cb195a --- /dev/null +++ b/src/srvNodeMgr/tools_cq/seek_info.erl @@ -0,0 +1,249 @@ +-module(seek_info). + +%% API +-export([ + eprof_start/0, + eprof_stop/0, + fprof_start/0, + fprof_start/1, + fprof_stop/0, + fprof_analyze/0, + queue/1, + memory/1, + reds/1, + help/0 +]). + +-define(APPS, [kernel, game]). + +%%==================================================================== +%% API +%%==================================================================== +help() -> + io:format("Brief help:~n" + "~p:queue(N) -> show top N pids sorted by queue length~n" + "~p:memory(N) -> show top N pids sorted by memory usage~n" + "~p:reds(N) -> show top N pids sorted by reductions~n" + "Erlang shell with Ctrl+C~n" + "~p:eprof_start() -> start eprof on all available pids; " + "DO NOT use on production system!~n" + "~p:eprof_stop() -> stop eprof and print result~n" + "~p:fprof_start() -> start fprof on all available pids; " + "DO NOT use on production system!~n" + "~p:fprof_stop() -> stop eprof and print formatted result~n" + "~p:fprof_start(N) -> start and run fprof for N seconds; " + "use ~p:fprof_analyze() to analyze collected statistics and " + "print formatted result; use on production system with CARE~n" + "~p:fprof_analyze() -> analyze previously collected statistics " + "using ~p:fprof_start(N) and print formatted result~n" + "~p:help() -> print this help~n", + lists:duplicate(12, ?MODULE)). + +eprof_start() -> + eprof:start(), + case lists:keyfind(running, 1, application:info()) of + {_, Apps} -> + case get_procs(?APPS, Apps) of + [] -> + {error, no_procs_found}; + Procs -> + eprof:start_profiling(Procs) + end; + _ -> + {error, no_app_info} + end. + +fprof_start() -> + fprof_start(0). + +fprof_start(Duration) -> + case lists:keyfind(running, 1, application:info()) of + {_, Apps} -> + case get_procs(?APPS, Apps) of + [] -> + {error, no_procs_found}; + Procs -> + fprof:trace([start, {procs, Procs}]), + io:format("Profiling started~n"), + if Duration > 0 -> + timer:sleep(Duration * 1000), + fprof:trace([stop]), + fprof:stop(); + true -> + ok + end + end; + _ -> + {error, no_app_info} + end. + +fprof_stop() -> + fprof:trace([stop]), + fprof:profile(), + fprof:analyse([totals, no_details, {sort, own}, + no_callers, {dest, "fprof.analysis"}]), + fprof:stop(), + format_fprof_analyze(). + +fprof_analyze() -> + fprof_stop(). + +eprof_stop() -> + eprof:stop_profiling(), + eprof:analyze(). + +queue(N) -> + dump(N, lists:reverse(lists:ukeysort(1, all_pids(queue)))). + +memory(N) -> + dump(N, lists:reverse(lists:ukeysort(3, all_pids(memory)))). + +reds(N) -> + dump(N, lists:reverse(lists:ukeysort(4, all_pids(reductions)))). + +%%==================================================================== +%% Internal functions +%%==================================================================== +get_procs(Apps, AppList) -> + io:format("Searching for processes to profile...~n", []), + Procs = lists:foldl( + fun({App, Leader}, Acc) when is_pid(Leader) -> + case lists:member(App, Apps) of + true -> + [get_procs2(Leader) | Acc]; + false -> + Acc + end; + (_, Acc) -> + Acc + end, [], AppList), + io:format("Found ~p processes~n", [length(Procs)]), + Procs. + +get_procs2(Leader) -> + lists:filter( + fun(Pid) -> + case process_info(Pid, group_leader) of + {_, Leader} -> + true; + _ -> + false + end + end, processes()). + +format_fprof_analyze() -> + case file:consult("fprof.analysis") of + {ok, [_, [{totals, _, _, TotalOWN}] | Rest]} -> + OWNs = + lists:flatmap( + fun({MFA, _, _, OWN}) -> + Percent = OWN * 100 / TotalOWN, + case round(Percent) of + 0 -> []; + _ -> [{mfa_to_list(MFA), Percent}] + end + end, Rest), + ACCs = collect_accs(Rest), + MaxACC = find_max(ACCs), + MaxOWN = find_max(OWNs), + io:format("=== Sorted by OWN:~n"), + lists:foreach( + fun({MFA, Per}) -> + L = length(MFA), + S = lists:duplicate(MaxOWN - L + 2, $ ), + io:format("~s~s~.2f%~n", [MFA, S, Per]) + end, lists:reverse(lists:keysort(2, OWNs))), + io:format("~n=== Sorted by ACC:~n"), + lists:foreach( + fun({MFA, Per}) -> + L = length(MFA), + S = lists:duplicate(MaxACC - L + 2, $ ), + io:format("~s~s~.2f%~n", [MFA, S, Per]) + end, lists:reverse(lists:keysort(2, ACCs))); + Err -> + Err + end. + +mfa_to_list({M, F, A}) -> + atom_to_list(M) ++ ":" ++ atom_to_list(F) ++ "/" ++ integer_to_list(A); +mfa_to_list(F) when is_atom(F) -> + atom_to_list(F). + +find_max(List) -> + find_max(List, 0). + +find_max([{V, _} | Tail], Acc) -> + find_max(Tail, lists:max([length(V), Acc])); +find_max([], Acc) -> + Acc. + +collect_accs(List) -> + List1 = lists:filter( + fun({{sys, _, _}, _, _, _}) -> + false; + ({suspend, _, _, _}) -> + false; + ({{gen_fsm, _, _}, _, _, _}) -> + false; + ({{gen, _, _}, _, _, _}) -> + false; + ({{gen_server, _, _}, _, _, _}) -> + false; + ({{proc_lib, _, _}, _, _, _}) -> + false; + (_) -> + true + end, List), + calculate(List1). + +calculate(List1) -> + TotalACC = lists:sum([A || {_, _, A, _} <- List1]), + List2 = lists:foldl(fun({MFA, _, ACC, _}, NewList) -> + Percent = ACC * 100 / TotalACC, + case round(Percent) of + 0 -> NewList; + _ -> [{mfa_to_list(MFA), Percent} | NewList] + end + end, [], List1), + lists:reverse(List2). + +all_pids(Type) -> + lists:foldl( + fun(P, Acc) when P == self() -> + Acc; + (P, Acc) -> + case catch process_info( + P, [message_queue_len, memory, reductions, + dictionary, current_function, registered_name]) of + [{_, Len}, {_, Memory}, {_, Reds}, + {_, Dict}, {_, CurFun}, {_, RegName}] -> + IntQLen = get_internal_queue_len(Dict), + if Type == queue andalso Len == 0 andalso IntQLen == 0 -> + Acc; + true -> + [{lists:max([Len, IntQLen]), + Len, Memory, Reds, Dict, CurFun, P, RegName} | Acc] + end; + _ -> + Acc + end + end, [], processes()). + +get_internal_queue_len(Dict) -> + case lists:keysearch('$internal_queue_len', 1, Dict) of + {value, {_, N}} -> N; + _ -> 0 + end. + +dump(N, Rs) -> + lists:foreach( + fun({_, MsgQLen, Memory, Reds, Dict, CurFun, Pid, RegName}) -> + io:format("** pid(~s)~n" + "** registered name: ~p~n" + "** memory: ~p~n" + "** reductions: ~p~n" + "** message queue len: ~p~n" + "** current_function: ~p~n" + "** dictionary: ~p~n~n", + [pid_to_list(Pid), RegName, Memory, Reds, MsgQLen, CurFun, Dict]) + end, lists:sublist(Rs, N)). \ No newline at end of file diff --git a/src/srvNodeMgr/tools_cq/test.erl b/src/srvNodeMgr/tools_cq/test.erl new file mode 100644 index 0000000..1f45eed --- /dev/null +++ b/src/srvNodeMgr/tools_cq/test.erl @@ -0,0 +1,103 @@ +-module(test). + +-export([ + time_compare/2, + time_compare/3 +]). + + +%% Apis ---------------------------------------- +% @doc 运行时间比较 +% @spec time_compare(Num, TList) -> ok | skip. +% Num :: integer() +% TList :: [Test] +% Test :: {InfoString :: string(), Fun :: fun()} +% format the test result as following: +% +time_compare(Num, TList) -> + time_compare(Num, TList, []). + +% @doc 运行时间比较 +% @spec time_compare(Num, TList, Option) -> ok | skip. +% same as time_compare/2 +% Option :: nyi (Not Yet Implemented) +time_compare(0, _TList, _Opt) -> + skip; +time_compare(Num, TList, _Opt) -> + ResultList = [do_time_compare(Num, Tester) || Tester <- TList], + io:format("=============================================================================================~n"), + do_print_out(ResultList), + io:format("=============================================================================================~n"). + +%% Privates ------------------------------------ +do_time_compare(Num, {Label, Func}) -> + statistics(wall_clock), + do_time_compare_f1(1, Num, Func), + {_, Time} = statistics(wall_clock), + US = Time * 1000 / Num, + io:format("~-30s [wall_clock: total:~10w ms avg:~7.3f us]~n", [Label, Time, US]), + {Label, Time}; +do_time_compare(Num, {Label, Func, ProcessNum}) -> + Pids = do_time_compare_init(Num, 0, ProcessNum, Func, []), + statistics(wall_clock), + [erlang:send(Pid, go) || Pid <- Pids], + do_time_compare_water(length(Pids)), + {_, Time} = statistics(wall_clock), + US = Time * 1000 / Num, + io:format("~-30s [wall_clock: total: ~10w ms avg: ~7.3f)us]~n", [Label, Time, US]), + {Label, Time}. + +do_time_compare_init(0, _Start, _ProcessNum, _Func, Pids) -> + Pids; +do_time_compare_init(Num, Start, 1, Func, Pids) -> + Self = erlang:self(), + Pid = spawn_link(fun() -> do_time_compare_worker(Self, Func, Start + 1, Start + Num) end), + [Pid | Pids]; +do_time_compare_init(Num, Start, ProcessNum, Func, Pids) -> + Self = erlang:self(), + DoRound = Num div ProcessNum, + Pid = spawn_link(fun() -> do_time_compare_worker(Self, Func, Start + 1, Start + DoRound) end), + do_time_compare_init(Num - DoRound, Start + DoRound, ProcessNum - 1, Func, [Pid | Pids]). + +do_time_compare_water(0) -> + ok; +do_time_compare_water(Num) -> + receive + ok -> + do_time_compare_water(Num - 1); + Msg -> + erlang:error(io_lib:format("bad message: ~w", [Msg])) + end. + +do_time_compare_worker(Pid, Func, From, To) -> + receive + go -> + do_time_compare_f1(From, To, Func), + Pid ! ok; + _ -> + ok + end. + +do_time_compare_f1(Max, Max, Func) -> + Func(Max); +do_time_compare_f1(I, Max, Func) -> + Func(I), + do_time_compare_f1(I + 1, Max, Func). + +do_print_out([{_LabelBase, TimeBase} | _] = ResultList) -> + do_print_out(ResultList, TimeBase). + +% @doc 第二个参数为参照 +% @spec do_print_out(ResultList, {RunTimeBase, WallClockBase}) -> ok. +% ResultList :: [Result] +% Result :: [Label, RunTime, WallClock] +% Label :: string() +% RunTimeBase = WallClockBase :: integer() +do_print_out(ResultList, TimeBase) -> + [ + io:format( + "~-30s [ wall_clock: ~10w ms percentage: ~7.2f%]~n", + [Label, Time, Time / (TimeBase + 0.000000001) * 100] + ) + || {Label, Time} <- ResultList + ]. diff --git a/src/srvNodeMgr/tools_cq/tester.erl b/src/srvNodeMgr/tools_cq/tester.erl new file mode 100644 index 0000000..aa25b77 --- /dev/null +++ b/src/srvNodeMgr/tools_cq/tester.erl @@ -0,0 +1,74 @@ +-module(tester). +-include("common.hrl"). + +-export([ + test_online_num/1, + test_scene_sign/0, + test_lists_and_proplists/0, + test_lists_and_proplists/3 +]). + +%% Apis ------------------------------ +test_online_num(Repeat) -> + F1 = fun(_) -> lib_world_boss:online_num() end, + F2 = fun(_) -> lib_world_boss:online_num2() end, + Tester = [ + {"lib_world_boss:online_num", F1}, + {"lib_world_boss:online_num2", F2} + ], + test:time_compare(Repeat, Tester). + +test_lists_and_proplists() -> + test_lists_and_proplists(10000, 10, 50). + +test_lists_and_proplists(Repeat, ListSize, FindSize) -> + {ToFindKeys, MaterialList} = gen_list_material(ListSize, FindSize), + F1 = fun(_) -> [lists:keyfind(K, 1, MaterialList) || K <- ToFindKeys] end, + F2 = fun(_) -> [proplists:get_value(K, MaterialList) || K <- ToFindKeys] end, + Tester = [ + {"lists:keyfind/3", F1}, + {"proplists:get_value/2", F2} + ], + test:time_compare(Repeat, Tester). + +%% @doc 测试地图区域 +test_scene_sign() -> + case conf_scene_list:get_id_list() of + SceneList when SceneList =/= [] -> + SceneID = util:list_rand(SceneList), + test_scene_sign(10000, SceneID, 100); + _ -> + ignore + end. + +test_scene_sign(Repeat, SceneID, FindSize) -> + {MaxX, MaxY} = lib_scene_sign:get_max_xy(SceneID), + PosList = [{util:rand(1, MaxX), util:rand(1, MaxY)} || _ <- lists:seq(1, FindSize)], + %% 从s3取svr_scene_sign模块,并将svr_scene_sign:load_sign/1改为call + svr_scene_sign:ensure_scene_sign(SceneID), + F1 = fun(_) -> + [svr_scene_sign:get_scene_poses({SceneID, X, Y}) || {X, Y} <- PosList] + end, + Ets = conf_scene:get_ets(SceneID), + F2 = fun(_) -> + [lib_scene_sign:is_pos_blocked(Ets, {SceneID, X, Y}) || {X, Y} <- PosList] + end, + F3 = fun(_) -> + [lib_scene_sign:is_pos_blocked({SceneID, X, Y}) || {X, Y} <- PosList] + end, + Tester = [ + {"scene_sign+gen_server", F1}, + {"scene_sign+ets", F2}, + {"scene_sign+ets", F3} + ], + test:time_compare(Repeat, Tester). + + +%% Privates -------------------------- + +gen_list_material(ListSize, FindSize) -> + Keys = [list_to_atom(lists:concat([k, I])) || I <- lists:seq(1, ListSize)], + Values = [list_to_atom(lists:concat([v, I])) || I <- lists:seq(1, ListSize)], + List = lists:zip(Keys, Values), + ToFind = util:gen_n(FindSize, Keys), + {ToFind, List}. diff --git a/src/srvNodeMgr/tools_cq/tools.erl b/src/srvNodeMgr/tools_cq/tools.erl new file mode 100644 index 0000000..bc65515 --- /dev/null +++ b/src/srvNodeMgr/tools_cq/tools.erl @@ -0,0 +1,150 @@ +-module(tools). + +-export([ + eprof_all/1, + eprof/2, + task_tc/1, + get_dic/2, + timertc/3 % 执行时间测试 +]). + + +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(). + +%% ----------------------------- +%% 执行时间统计 +%% ----------------------------- +%% @spec 执行时间统计 +%% ProNum: 开启进程数 +%% ExeNum: 每个进程执行次数 +%% Fun: 执行函数 +%% @return: 执行时间(us) +%% @end +timertc(ProNum, ExeNum, Fun) -> + Parent = self(), + lists:sum([receive {Pid, Res} -> Res end || Pid <- [spawn(fun() -> + ExeL__ = lists:seq(1, ExeNum), + {BA, BB, BC} = os:timestamp(), + Begin = 1000000000000 * BA + 1000000 * BB + BC, + [Fun() || _Exe <- ExeL__], + {EA, EB, EC} = os:timestamp(), + End = 1000000000000 * EA + 1000000 * EB + EC, + Us = End - Begin, + Parent ! {self(), Us}, + ok + end) || _ProL <- lists:seq(1, ProNum)]]). + + +%% ================================================================================================= +%% 测试函数 +%% ================================================================================================= +%% 测试type:term_to_bitstring 和 erlang:term_to_binary 的性能。结果erlang:term_to_binary 要快20倍左右 +% test_term_to_binary(N,M) -> +% D = [{shfkshfskhf,asdfhjsfj,asdfkhaslfj,sdfkhjasklfj,sflhjaslfkj,lkhasfkljs,ksdhflhjasf,sjdflj} || _<-lists:seq(1, 500)], +% D1 = type:term_to_bitstring(D), +% D2 = erlang:term_to_binary(D), +% F11 = fun() -> type:term_to_bitstring(D) end, +% F12 = fun() -> type:bitstring_to_term(D1) end, +% A1 = timertc(N,M, F11), +% A2 = timertc(N,M, F12), +% io:format("a ~p ~p~n", [A1, A2]), +% F21 = fun() -> erlang:term_to_binary(D) end, +% F22 = fun() -> erlang:binary_to_term(D2) end, +% B1 = timertc(N,M, F21), +% B2 = timertc(N,M, F22), +% io:format("b ~p ~p~n", [B1, B2]), +% ok. + +% task_tc(Msg, M, F, A) -> +% ListMsg = tuple_to_list(Msg), +% [AtomMsg | _] = ListMsg, +% statistics(runtime), +% statistics(wall_clock), +% Value = apply(M, F, A), +% {_, Time1} = statistics(runtime), +% {_, Time2} = statistics(wall_clock), +% time_test("Msg:~p, ~p:~p, Time1:~p, Time2:~p", [AtomMsg, M, F, Time1, Time2]), +% Value. + +task_tc(Msg) -> + ListMsg = tuple_to_list(Msg), + [AtomMsg | _] = ListMsg, + ProInfo = erlang:process_info(self(), total_heap_size), + NowMs = time:unixtime_ms(), + LastMs = + case get(last_ms) of + undefined -> NowMs; + LM -> LM + end, + put(last_ms, NowMs), + DiffMs = NowMs - LastMs, + LastT = + case get(last_time) of + undefined -> 0; + LT -> LT + end, + Ms = DiffMs + LastT, + put(last_time, Ms), + time_test("Msg:~p, Ms:~p, ProInfo:~p", [AtomMsg, Ms, ProInfo]). + +%% 日志记录函数 +time_test(Format, Args) -> + {{Year, Month, Day}, {Hour, Min, Sec}} = erlang:localtime(), + File = + case get("time_test") of + % 首次打开 + undefined -> + FileName = io_lib:format("time_test_~p_~p_~p.txt", [Year, Month, Day]), + PathName = config:get_log_path() ++ "/" ++ type:unicode_string(FileName), + {ok, File1} = file:open(PathName, [write, append]), + put("time_test", {File1, {Year, Month, Day}}), + File1; + % 已经打开 + {File2, {Year, Month, Day}} -> + File2; + % 需要切换 + {File2, _} -> + file:close(File2), + FileName = io_lib:format("time_test_~p_~p_~p.txt", [Year, Month, Day]), + PathName = config:get_log_path() ++ "/" ++ type:unicode_string(FileName), + {ok, File3} = file:open(PathName, [write, append]), + put("time_test", {File3, {Year, Month, Day}}), + File3 + end, + %Format1 = unicode:characters_to_list("#error" ++ " ~p-~p-~p ~p:~p:~p \r\n" ++ Format ++ "\r\n~n"), + Format1 = type:unicode_string("#error" ++ " ~p-~p-~p ~p:~p:~p \r\n" ++ Format ++ "\r\n~n"), + io:format(File, Format1, [Year, Month, Day, Hour, Min, Sec] ++ Args). + +get_dic(Dic, Default) -> + case get(Dic) of + undefined -> + Default; + Info -> + Info + end. + + + + + + + + + + + + + + + + + diff --git a/src/srvNodeMgr/tools_cq/u.erl b/src/srvNodeMgr/tools_cq/u.erl new file mode 100644 index 0000000..77447d5 --- /dev/null +++ b/src/srvNodeMgr/tools_cq/u.erl @@ -0,0 +1,137 @@ +%%---------------------------------------------------- +%% Erlang模块热更新到所有节点(包括server的回调函数,如果对state有影响时慎用) +%% +%% 检查:u:c() %% 列出前5分钟内编译过的文件 +%% u:c(N) %% 列出前N分钟内编译过的文件 +%% +%% 更新:u:u() %% 更新前5分钟内编译过的文件 +%% u:u(N) %% 更新前N分钟内编译过的文件 +%% u:u([svr_xx, ...]) %% 指定模块(不带后缀名) +%% u:u(m) %% 编译并加载文件 +%% +%% Tips: u - update, c - check +%% +%%---------------------------------------------------- + +-module(u). +-include_lib("kernel/include/file.hrl"). +-include("common.hrl"). + +-export([ + c/0, + c/1, + admin/0, + u/0, + u/1, + m/1, + info/1, + load/1 +]). + +c() -> + c(5). + +c(S) when is_integer(S) -> + case file:list_dir(".") of + {ok, FileList} -> + Files = get_new_file(FileList, S * 60), + info("---------check modules---------~n~w~n=========check modules=========", [Files]); + Any -> + info("Error Dir: ~w", [Any]) + end; +c(_) -> + info("ERROR======> Badarg", []). + +admin() -> + spawn(fun() -> u(m) end), + ok. + +u() -> + u(5). + +u(m) -> + StartTime = time:unixtime(), + info("----------makes----------", []), + c:cd("../"), + make:all(), + c:cd("ebin"), + EndTime = time:unixtime(), + Time = EndTime - StartTime, + info("Make Time : ~w s", [Time]), + u(Time / 60); +u(S) when is_number(S) -> + case file:list_dir(".") of + {ok, FileList} -> + Files = get_new_file(FileList, util:ceil(S * 60) + 3), + AllZone = svr_node:get_all_node(), + info("---------modules---------~n~w~n----------nodes----------", [Files]), + load(Files), + loads(AllZone, Files); + Any -> + info("Error Dir: ~w", [Any]) + end; +u(Files) when is_list(Files) -> + AllZone = svr_node:get_all_node(), + info("---------modules---------~n~w~n----------nodes----------", [Files]), + load(Files), + loads(AllZone, Files); +u(_) -> + info("ERROR======> Badarg", []). + +%% m(['src/data/*','src/lib/lib_goods.erl']) +m(Files) when is_list(Files) -> + StartTime = time:unixtime(), + info("----------makes----------~n~w~n", [Files]), + c:cd("../"), + Res = make:files(Files, [debug_info, {i, "include"}, {outdir, "ebin"}]), + c:cd("ebin"), + EndTime = time:unixtime(), + Time = EndTime - StartTime, + info("Make Time : ~w s", [Time]), + Res. + +info(V) -> + info(V, []). + +info(V, P) -> + io:format(V ++ "~n", P). + +%% 更新到所有节点 +loads([], _Files) -> ok; +loads([H | T], Files) -> + info("[~w]", [H#node.name]), + rpc:cast(H#node.name, u, load, [Files]), + loads(T, Files). + +get_new_file(Files, S) -> + get_new_file(Files, S, []). + +get_new_file([], _S, Result) -> Result; +get_new_file([H | T], S, Result) -> + NewResult = case string:tokens(H, ".") of + [Left, Right] when Right =:= "beam" -> + case file:read_file_info(H) of + {ok, FileInfo} -> + Now = calendar:local_time(), + case calendar:time_difference(FileInfo#file_info.mtime, Now) of + {Days, Times} -> + Seconds = calendar:time_to_seconds(Times), + case Days =:= 0 andalso Seconds < S of + true -> + FileName = type:list_to_atom(Left), + [FileName | Result]; + false -> Result + end; + _ -> Result + end; + _ -> Result + end; + _ -> Result + end, + get_new_file(T, S, NewResult). + +load([]) -> ok; +load([FileName | T]) -> + c:l(FileName), + info("loaded: ~w", [FileName]), + load(T).