@ -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. |
@ -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. |
@ -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)). |
@ -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. |
@ -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. |
@ -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) | |||
]). |
@ -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. | |||
@ -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. |
@ -0,0 +1,270 @@ | |||
%% Copyright (c) 2007 | |||
%% Mats Cronqvist <mats.cronqvist@ericsson.com> | |||
%% Chris Newcombe <chris.newcombe@gmail.com> | |||
%% Jacob Vorreuter <jacob.vorreuter@gmail.com> | |||
%% | |||
%% 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 <mats.cronqvist@ericsson.com> | |||
%%% Chris Newcombe <chris.newcombe@gmail.com> | |||
%%% Jacob Vorreuter <jacob.vorreuter@gmail.com> | |||
%%% - 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. |
@ -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}. |
@ -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 | |||
%% -------------------------------------------------------------------- |
@ -0,0 +1,348 @@ | |||
%% @author Ruslan Babayev <ruslan@babayev.com> | |||
%% @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". |
@ -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. |
@ -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). | |||
". |
@ -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). |
@ -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. |
@ -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. |
@ -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. | |||
@ -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. | |||
@ -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. | |||
@ -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. |
@ -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). | |||
@ -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). |
@ -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). | |||
@ -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). | |||
@ -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 = <<Len1:16, Content/binary>>, | |||
gen_tcp:send(Sokcet, pack(11010, Data)); | |||
call(11070, {Id, Content}, Sokcet) -> | |||
Len1 = byte_size(Content), | |||
Data = <<Id:32, Len1:16, Content/binary>>, | |||
gen_tcp:send(Sokcet, pack(11070, Data)). | |||
%% <<Id:32, Len:16, Nick1/binary, Len1:16, Bin1/binary>> | |||
handle_socket(11010, BinData) -> | |||
io:format("rec package ~p~n", [11010]), | |||
<<Id:32, Len1:16, Rest1/binary>> = BinData, | |||
<<Nick:Len1/binary-unit:8, Len2:16, Rest2/binary>> = Rest1, | |||
<<Content:Len2/binary-unit:8>> = 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]), | |||
<<Id:32, Len1:16, Rest1/binary>> = BinData, | |||
<<Nick:Len1/binary-unit:8, Len2:16, Rest2/binary>> = Rest1, | |||
<<Content:Len2/binary-unit:8>> = 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, | |||
<<L:16, Cmd:16, Data/binary>>. |
@ -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, <<Mail_type:8, Mail_page:8>>)); | |||
%%==========提取附件========== | |||
call(19006, {Mail_id}, Sokcet) -> | |||
gen_tcp:send(Sokcet, pack(19006, <<Mail_id:32>>)); | |||
%%-----------新版邮件协议请求服务器 190x c_2_s--------------- | |||
%%==========获取信件列表及内容========= | |||
call(19051, {Page_index}, Sokcet) -> | |||
gen_tcp:send(Sokcet, pack(19051, <<Page_index:16>>)). | |||
%%-----------旧版邮件协议接收信息 1900x s_2_c ------------ | |||
%%==========获取信件列表及内容============= | |||
handle_socket(19004, BinData) -> | |||
<<Result:8, Rest/binary>> = BinData, | |||
if Result =/= 0 -> | |||
<<Mail_num:8, Mail_page:8, MailNum:16, BinList/binary>> = 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]), | |||
<<Result:16, MailId:32>> = 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] | |||
%% 对应:<<Id:32, Type:8, State:8, Timestamp:32, Len1:16, SName/binary, Len2:16, Title/binary, Len3:16, Content/binary, GLen:16, GoodsBin/binary, Gold:32, Coin:32>> | |||
%% AccList 列表累加器,使用时初始为[] | |||
get_list(AccList, Bin, N) when N > 0 -> | |||
case Bin of | |||
<<Id:32, Type:8, State:8, Timestamp:32, Len1:16, Rest/binary>> -> | |||
<<SName:Len1/binary-unit:8, Len2:16, Rest2/binary>> = Rest, | |||
<<Title:Len2/binary-unit:8, Len3:16, Rest3/binary>> = Rest2, | |||
<<Content:Len3/binary-unit:8, Len4:16, Rest4/binary>> = Rest3, | |||
<<GoodsBin:Len4/binary-unit:8, Gold:32, Coin:32, Rest5/binary>> = 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, | |||
<<L:16, Cmd:16, Data/binary>>. |
@ -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, <<Len:16, 60000:16>>} -> | |||
BodyLen = Len - ?HEADER_LENGTH, | |||
case gen_tcp:recv(Socket, BodyLen, 3000) of | |||
{ok, <<Bin/binary>>} -> | |||
<<Rlen:16, RB/binary>> = Bin, | |||
case Rlen of | |||
1 -> | |||
<<Bin1/binary>> = RB, | |||
{IP, Bin2} = pt:read_string(Bin1), | |||
<<Port:16, _Num:8>> = 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, <<Len:16, Cmd:16>>}} -> | |||
?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 -> | |||
<<Code:8, _Bin1/binary>> = 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 -> | |||
<<Accid:32, _Bin2/binary>> = _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 -> | |||
<<Code:8, PlayerId:64, _Bin/binary>> = 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 -> | |||
<<Code:8, _Bin/binary>> = BinData, | |||
?TRACE("10004: Code: ~p ~n", [Code]), | |||
if Code =/= 0 -> | |||
gen_server:cast(Pid, enter_ok); | |||
true -> | |||
gen_server:cast(Pid, {stop}) | |||
end; | |||
13001 -> | |||
<<Uid:64, Gender:8, Level:8, Speed:8, Scene:16, X:8, Y:8, Hp:32, _Other/binary>> = 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, <<Len:16, Cmd:16>>}} -> | |||
%%?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",[]), | |||
<<Code:16, N:8>> = 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, <<X:16, Y:16>>)); | |||
%%ai模式跑步 | |||
handle(run, {X, Y, SX, SY}, Socket) -> | |||
?TRACE("----running:[~p][~p]~n", [X, Y]), | |||
gen_tcp:send(Socket, pack(12001, <<X:8, Y:8, SX:8, SY:8>>)); | |||
%%进入场景 | |||
handle(enter_scene, Sid, Socket) -> | |||
gen_tcp:send(Socket, pack(12005, <<Sid:32>>)), | |||
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, <<L:16, Bin/binary>>)); | |||
%%静止 | |||
handle(undefined, a, _Socket) -> | |||
ok; | |||
%%获取其他玩家信息 | |||
handle(get_player_info, Id, Socket) -> | |||
gen_tcp:send(Socket, pack(13004, <<Id:16>>)); | |||
%%获取自己信息 | |||
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 = <<ActionLen:16, Action/binary>>, | |||
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(<<L:32, 10002:16, Num:16, Bin/binary>>) -> | |||
?TRACE("client read: ~p ~p ~p~n", [L, 10002, Num]), | |||
F = fun(Bin1) -> | |||
<<Id:32, S:16, C:16, Sex:16, Lv:16, Bin2/binary>> = 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(<<L:32, Cmd:16>>) -> | |||
?TRACE("client read: ~p ~p~n", [L, Cmd]); | |||
read(<<L:32, Cmd:16, Status:16>>) -> | |||
?TRACE("client read: ~p ~p ~p~n", [L, Cmd, Status]); | |||
read(<<L:32, Cmd:16, Bin/binary>>) -> | |||
?TRACE("client read: ~p ~p ~p~n", [L, Cmd, Bin]); | |||
read(Bin) -> | |||
?TRACE("client rec: ~p~n", [Bin]). | |||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |||
%%辅助函数 | |||
%%读取字符串 | |||
read_string(Bin) -> | |||
case Bin of | |||
<<Len:16, Bin1/binary>> -> | |||
case Bin1 of | |||
<<Str:Len/binary-unit:8, Rest/binary>> -> | |||
{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, | |||
<<L:16, Cmd:16, Data/binary>>. | |||
rand(Same, Same) -> Same; | |||
rand(Min, Max) -> | |||
M = Min - 1, | |||
if | |||
Max - M =< 0 -> | |||
0; | |||
true -> | |||
random:uniform(Max - M) + M | |||
end. |
@ -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, <<Result:16>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol 30004 完成任务并挑选奖励 | |||
%%-------------------------------------- | |||
read(30004, <<Result:16>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol:30005 查询任务npc状态 | |||
%%-------------------------------------- | |||
read(30005, <<BinData/binary>>) -> | |||
<<NpcListLen:16, NpcListBin/binary>> = BinData, | |||
Fun_NpcList = fun(_Idx, {RestBin, ResultList}) -> | |||
<<NpcId:16, NpcState:8, _NpcList_RestBin/binary>> = 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/binary>>) -> | |||
<<TaskListLen:16, TaskListBin/binary>> = BinData, | |||
Fun_TaskList = fun(_Idx, {RestBin, ResultList}) -> | |||
<<TaskId:16, TaskState:8, TaskProcess:32, _TaskList_RestBin/binary>> = 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, <<Result:16>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 30501 服务端通知客户端任务的完成条件已满足 | |||
%%-------------------------------------- | |||
read(30501, <<BinData/binary>>) -> | |||
<<TaskListLen:16, TaskListBin/binary>> = BinData, | |||
Fun_TaskList = fun(_Idx, {RestBin, ResultList}) -> | |||
<<TaskId:16, _TaskList_RestBin/binary>> = 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/binary>>) -> | |||
<<TaskListLen:16, TaskListBin/binary>> = BinData, | |||
Fun_TaskList = fun(_Idx, {RestBin, ResultList}) -> | |||
<<TaskId:16, FinNum:8, NowNum:8, _TaskList_RestBin/binary>> = 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/binary>>) -> | |||
<<TaskListLen:16, TaskListBin/binary>> = BinData, | |||
Fun_TaskList = fun(_Idx, {RestBin, ResultList}) -> | |||
<<TaskId:16, _TaskList_RestBin/binary>> = 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, <<TaskId:16>>) -> | |||
{ok, [TaskId]}; | |||
%%-------------------------------------- | |||
%%Protocol:30506 通知客户端服务器为玩家自动完成了某个任务 | |||
%%-------------------------------------- | |||
read(30506, <<TaskId:16>>) -> | |||
{ok, [TaskId]}; | |||
%%-------------------------------------- | |||
%%Protocol:30507 通知客户端日常任务重置 | |||
%%-------------------------------------- | |||
read(30507, <<BinData/binary>>) -> | |||
<<TaskListLen:16, TaskListBin/binary>> = BinData, | |||
Fun_TaskList = fun(_Idx, {RestBin, ResultList}) -> | |||
<<Type:8, _TaskList_RestBin/binary>> = 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, <<TaskId:16>>)}; | |||
%%-------------------------------------- | |||
%%Protocol:协议号:30004 完成任务并挑选奖励 | |||
%%-------------------------------------- | |||
write(30004, [TaskId]) -> | |||
{ok, pt:pack(30004, <<TaskId:16>>)}; | |||
%%-------------------------------------- | |||
%%Protocol:30005 查询任务npc状态 | |||
%%-------------------------------------- | |||
write(30005, [NpcList]) -> | |||
Fun_NpcList = fun([NpcId]) -> | |||
<<NpcId:16>> | |||
end, | |||
NpcList_Len = length(NpcList), | |||
NpcList_ABin = any_to_binary(lists:map(Fun_NpcList, NpcList)), | |||
NpcList_ABinData = <<NpcList_Len:16, NpcList_ABin/binary>>, | |||
{ok, pt:pack(30005, <<NpcList_ABinData/binary>>)}; | |||
%%-------------------------------------- | |||
%%Protocol:30006 获取指定长度任务列表 | |||
%%-------------------------------------- | |||
write(30006, [Len]) -> | |||
{ok, pt:pack(30006, <<Len:8>>)}; | |||
%%-------------------------------------- | |||
%%Protocol:30007 消耗元宝自动完成任务 | |||
%%-------------------------------------- | |||
write(30007, [TaskId]) -> | |||
{ok, pt:pack(30007, <<TaskId:16>>)}; | |||
%%-------------------------------------- | |||
%%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), | |||
<<Len:16, BinData/binary>>. | |||
any_to_binary(Any) -> | |||
tool:to_binary(Any). | |||
@ -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, <<TaskId:16>>)); | |||
call(30004, TaskId, Socket) -> | |||
gen_tcp:send(Socket, pack(30004, <<TaskId:16>>)); | |||
call(30007, TaskId, Socket) -> | |||
gen_tcp:send(Socket, pack(30007, <<TaskId:16>>)); | |||
call(30005, NpcList, Socket) -> | |||
gen_tcp:send(Socket, pack(30005, NpcList)); | |||
call(30006, Size, Socket) -> | |||
gen_tcp:send(Socket, pack(30006, <<Size:8>>)). | |||
%%打包数据 | |||
pack(Cmd, Data) -> | |||
L = byte_size(Data) + ?HEADER_LENGTH, | |||
<<L:16, Cmd:16, Data/binary>>. |
@ -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, <<Uid:64, BinData/binary>>) -> | |||
{Name, _Name_DoneBin} = pt:read_string(BinData), | |||
<<Type:8, _Type_DoneBin/binary>> = _Name_DoneBin, | |||
{Content, _Content_DoneBin} = pt:read_string(_Type_DoneBin), | |||
{ok, [Uid, Name, Type, Content]}; | |||
%%-------------------------------------- | |||
%%Protocol: 11001 发送世界信息 | |||
%%-------------------------------------- | |||
read(11001, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 11002 发送场景信息 | |||
%%-------------------------------------- | |||
read(11002, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 11003 发送帮派信息 | |||
%%-------------------------------------- | |||
read(11003, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 11004 发送私聊信息 | |||
%%-------------------------------------- | |||
read(11004, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 11005 GM指令 | |||
%%-------------------------------------- | |||
read(11005, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 11010 系统信息/广播 | |||
%%-------------------------------------- | |||
read(11010, <<Type:8, BinData/binary>>) -> | |||
{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, <<ShowState:8, Content_StrBin/binary>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 11002 发送场景信息 | |||
%%-------------------------------------- | |||
write(11002, [Content]) -> | |||
Content_StrBin = pack_string(Content), | |||
{ok, pt:pack(11002, <<Content_StrBin/binary>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 11003 发送帮派信息 | |||
%%-------------------------------------- | |||
write(11003, [Content]) -> | |||
Content_StrBin = pack_string(Content), | |||
{ok, pt:pack(11003, <<Content_StrBin/binary>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 11004 发送私聊信息 | |||
%%-------------------------------------- | |||
write(11004, [PeerId, Content]) -> | |||
Content_StrBin = pack_string(Content), | |||
{ok, pt:pack(11004, <<PeerId:64, Content_StrBin/binary>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 11005 GM指令 | |||
%%-------------------------------------- | |||
write(11005, [Type, Content]) -> | |||
Content_StrBin = pack_string(Content), | |||
{ok, pt:pack(11005, <<Type:8, Content_StrBin/binary>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 11010 系统信息/广播 | |||
%%-------------------------------------- | |||
%%-------------------------------------- | |||
%%Protocol: 11099 调试信息 | |||
%%-------------------------------------- | |||
write(11099, [Content]) -> | |||
Content_StrBin = pack_string(Content), | |||
{ok, pt:pack(11099, <<Content_StrBin/binary>>)}; | |||
%%-------------------------------------- | |||
%% 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), | |||
<<Len:16, BinData/binary>>. | |||
any_to_binary(Any) -> | |||
tool:to_binary(Any). | |||
@ -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, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 19002 获取GM反馈 | |||
%%-------------------------------------- | |||
read(19002, <<BinData/binary>>) -> | |||
<<FbListLen:16, FbListBin/binary>> = BinData, | |||
Fun_FbList = fun(_Idx, {RestBin, ResultList}) -> | |||
<<FbId:32, Type:8, State:8, _ContentList_RestBin/binary>> = RestBin, | |||
<<ContentListLen:16, ContentListBin/binary>> = _ContentList_RestBin, | |||
Fun_ContentList = fun(_Idx, {RestBin, ResultList}) -> | |||
{Name, _Name_DoneBin} = pt:read_string(RestBin), | |||
{Content, _Content_DoneBin} = pt:read_string(_Name_DoneBin), | |||
<<Date:32, _ContentList_RestBin/binary>> = _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, <<Num:8>>) -> | |||
{ok, [Num]}; | |||
%%-------------------------------------- | |||
%%Protocol: 19011 邮件列表 | |||
%%-------------------------------------- | |||
read(19011, <<BinData/binary>>) -> | |||
<<MailListLen:16, MailListBin/binary>> = BinData, | |||
Fun_MailList = fun(_Idx, {RestBin, ResultList}) -> | |||
<<MailId:32, Type:8, State:8, Date:32, _SName_RestBin/binary>> = 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, <<StCode:8, MailId:32, BinData/binary>>) -> | |||
{Content, _Content_DoneBin} = pt:read_string(BinData), | |||
<<GoodListLen:16, GoodListBin/binary>> = _Content_DoneBin, | |||
Fun_GoodList = fun(_Idx, {RestBin, ResultList}) -> | |||
<<GoodTypeId:32, GoodsNum:8, Exist:8, _GoodList_RestBin/binary>> = 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, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 19014 收取附件 | |||
%%-------------------------------------- | |||
read(19014, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 19015 删除邮件 | |||
%%-------------------------------------- | |||
read(19015, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 19016 发送邮件 | |||
%%-------------------------------------- | |||
read(19016, <<Result:8, BinData/binary>>) -> | |||
<<ErrRecvListLen:16, ErrRecvListBin/binary>> = 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, <<Type:8, Content_StrBin/binary>>)}; | |||
%%-------------------------------------- | |||
%%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, <<MailId:32>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 19013 回复邮件 | |||
%%-------------------------------------- | |||
write(19013, [MailId, Content]) -> | |||
Content_StrBin = pack_string(Content), | |||
{ok, pt:pack(19013, <<MailId:32, Content_StrBin/binary>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 19014 收取附件 | |||
%%-------------------------------------- | |||
write(19014, [MailId]) -> | |||
{ok, pt:pack(19014, <<MailId:32>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 19015 删除邮件 | |||
%%-------------------------------------- | |||
write(19015, [MailId]) -> | |||
{ok, pt:pack(19015, <<MailId:32>>)}; | |||
%%-------------------------------------- | |||
%%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), | |||
<<Name_StrBin/binary>> | |||
end, | |||
RecvList_Len = length(RecvList), | |||
RecvList_ABin = any_to_binary(lists:map(Fun_RecvList, RecvList)), | |||
RecvList_ABinData = <<RecvList_Len:16, RecvList_ABin/binary>>, | |||
{ok, pt:pack(19016, <<Title_StrBin/binary, Content_StrBin/binary, RecvList_ABinData/binary>>)}; | |||
%%-------------------------------------- | |||
%% 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), | |||
<<Len:16, BinData/binary>>. | |||
any_to_binary(Any) -> | |||
tool:to_binary(Any). | |||
@ -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,<<CurPageNo:8,TotalPage:8,BinData/binary>>) -> | |||
read(40001, Data) -> | |||
NewData = zlib:uncompress(Data), | |||
<<CurPageNo:8, TotalPage:8, BinData/binary>> = NewData, | |||
%?TRACE("read 40001 CurPageNo= ~p ,TotalPage=~p ~n", [CurPageNo,TotalPage]), | |||
<<GuildListLen:16, GuildListBin/binary>> = BinData, | |||
%?TRACE("read 40001 GuildListLen: ~p ~n", [GuildListLen]), | |||
Fun_GuildList = fun(_Idx, {RestBin, ResultList}) -> | |||
<<GuildId:32, _GuildName_RestBin/binary>> = RestBin, | |||
%?TRACE("read 40001 GuildId: ~p ~n", [GuildId]), | |||
{GuildName, _GuildName_DoneBin} = pt:read_string(_GuildName_RestBin), | |||
<<CurNum:8, MaxNum:8, Level:8, Uid:64, _Name_RestBin/binary>> = _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, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40003 加入帮派 | |||
%%-------------------------------------- | |||
read(40003, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40004 退出所在帮派 | |||
%%-------------------------------------- | |||
read(40004, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40005 查询帮派成员 | |||
%%-------------------------------------- | |||
read(40005, <<StCode:8>>) -> | |||
{ok, [StCode]}; | |||
read(40005, <<StCode:8, BinData/binary>>) -> | |||
<<MemListLen:16, MemListBin/binary>> = BinData, | |||
Fun_MemList = fun(_Idx, {RestBin, ResultList}) -> | |||
<<Uid:64, _Name_RestBin/binary>> = RestBin, | |||
{Name, _Name_DoneBin} = pt:read_string(_Name_RestBin), | |||
<<Level:8, Career:8, Gender:8, Position:8, Contrib:32, LastLoginTime:32, Online:8, _MemList_RestBin/binary>> = _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, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40007 弹劾操作 | |||
%%-------------------------------------- | |||
read(40007, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40008 获取弹劾信息 | |||
%%-------------------------------------- | |||
read(40008, <<StCode:8>>) -> | |||
{ok, [StCode]}; | |||
read(40008, <<StCode:8, BinData/binary>>) -> | |||
<<RejectListLen:16, RejectListBin/binary>> = BinData, | |||
Fun_RejectList = fun(_Idx, {RestBin, ResultList}) -> | |||
<<Uid:64, Pos:8, State:8, AgreeNum:8, DisagreeNum:8, RemainTime:32, _RejectList_RestBin/binary>> = 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/binary>>) -> | |||
<<LogListLen:16, LogListBin/binary>> = BinData, | |||
Fun_LogList = fun(_Idx, {RestBin, ResultList}) -> | |||
<<Uid:32, _Name_RestBin/binary>> = RestBin, | |||
{Name, _Name_DoneBin} = pt:read_string(_Name_RestBin), | |||
<<TimeStamp:32, _Content_RestBin/binary>> = _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, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40031 帮派申请列表(帮主/副帮主) | |||
%%-------------------------------------- | |||
read(40031, <<BinData/binary>>) -> | |||
<<ApplyListLen:16, ApplyListBin/binary>> = BinData, | |||
Fun_ApplyList = fun(_Idx, {RestBin, ResultList}) -> | |||
<<Uid:64, _Name_RestBin/binary>> = RestBin, | |||
{Name, _Name_DoneBin} = pt:read_string(_Name_RestBin), | |||
<<Level:8, Career:8, Gender:8, Force:32, TimeStamp:32, _ApplyList_RestBin/binary>> = _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, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40033 提升职务(帮主) | |||
%%-------------------------------------- | |||
read(40033, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40034 解散帮派(帮主) | |||
%%-------------------------------------- | |||
read(40034, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40035 踢出成员(帮主/副帮主) | |||
%%-------------------------------------- | |||
read(40035, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40036 帮派升级(帮主/副帮主/长老) | |||
%%-------------------------------------- | |||
read(40036, <<Result:8, UplevelCd:32>>) -> | |||
{ok, [Result, UplevelCd]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40037 帮主让位 | |||
%%-------------------------------------- | |||
read(40037, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40039 帮派公告设置 | |||
%%-------------------------------------- | |||
read(40039, <<Result:8>>) -> | |||
{ok, [Result]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40070 帮派新增成员信息(广播) | |||
%%-------------------------------------- | |||
read(40070, <<Uid:64, BinData/binary>>) -> | |||
{Name, _Name_DoneBin} = pt:read_string(BinData), | |||
<<Level:8, _Level_DoneBin/binary>> = _Name_DoneBin, | |||
<<Career:8, _Career_DoneBin/binary>> = _Level_DoneBin, | |||
<<Gender:8, _Gender_DoneBin/binary>> = _Career_DoneBin, | |||
{ok, [Uid, Name, Level, Career, Gender]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40071 被踢通知(接收玩家) | |||
%%-------------------------------------- | |||
read(40071, <<GuildId:32, BinData/binary>>) -> | |||
{GuildName, _GuildName_DoneBin} = pt:read_string(BinData), | |||
{ok, [GuildId, GuildName]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40072 帮派邀请 | |||
%%-------------------------------------- | |||
read(40072, <<Uid:64, BinData/binary>>) -> | |||
{Name, _Name_DoneBin} = pt:read_string(BinData), | |||
<<GuildId:32, _GuildId_DoneBin/binary>> = _Name_DoneBin, | |||
<<MemNum:8, _MemNum_DoneBin/binary>> = _GuildId_DoneBin, | |||
<<Level:8, _Level_DoneBin/binary>> = _MemNum_DoneBin, | |||
{GuildName, _GuildName_DoneBin} = pt:read_string(_Level_DoneBin), | |||
<<Uid:64, _Uid_DoneBin/binary>> = _GuildName_DoneBin, | |||
{Name, _Name_DoneBin} = pt:read_string(_Uid_DoneBin), | |||
{ok, [Uid, Name, GuildId, MemNum, Level, GuildName, Uid, Name]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40073 职位变化通告(广播) | |||
%%-------------------------------------- | |||
read(40073, <<Uid:64, BinData/binary>>) -> | |||
{Name, _Name_DoneBin} = pt:read_string(BinData), | |||
<<OldPos:8, _OldPos_DoneBin/binary>> = _Name_DoneBin, | |||
<<NewPos:8, _NewPos_DoneBin/binary>> = _OldPos_DoneBin, | |||
{ok, [Uid, Name, OldPos, NewPos]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40074 帮主让位通知(广播) | |||
%%-------------------------------------- | |||
read(40074, <<OldUid:64, BinData/binary>>) -> | |||
{OldName, _OldName_DoneBin} = pt:read_string(BinData), | |||
<<NewUid:64, _NewUid_DoneBin/binary>> = _OldName_DoneBin, | |||
{NewName, _NewName_DoneBin} = pt:read_string(_NewUid_DoneBin), | |||
{ok, [OldUid, OldName, NewUid, NewName]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40075 帮派升级通知(广播) | |||
%%-------------------------------------- | |||
read(40075, <<OldLevel:8, NewLevel:8>>) -> | |||
{ok, [OldLevel, NewLevel]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40076 拒绝申请通知(仅玩家) | |||
%%-------------------------------------- | |||
read(40076, <<GuildId:32, BinData/binary>>) -> | |||
{GuildName, _GuildName_DoneBin} = pt:read_string(BinData), | |||
{ok, [GuildId, GuildName]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40077 新帮派公告 | |||
%%-------------------------------------- | |||
read(40077, <<BinData/binary>>) -> | |||
{Content, _Content_DoneBin} = pt:read_string(BinData), | |||
{ok, [Content]}; | |||
%%-------------------------------------- | |||
%%Protocol: 40078 申请加入批准通知(仅玩家) | |||
%%-------------------------------------- | |||
read(40078, <<GuildId:32, BinData/binary>>) -> | |||
{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, <<PageNo:8, IsNotFull:8, IsSameGroup:8>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 40002 创建帮派 | |||
%%-------------------------------------- | |||
write(40002, [Name, Announce]) -> | |||
Name_StrBin = pack_string(Name), | |||
Announce_StrBin = pack_string(Announce), | |||
{ok, pt:pack(40002, <<Name_StrBin/binary, Announce_StrBin/binary>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 40003 加入帮派 | |||
%%-------------------------------------- | |||
write(40003, [GuildId]) -> | |||
{ok, pt:pack(40003, <<GuildId:32>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 40004 退出所在帮派 | |||
%%-------------------------------------- | |||
write(40004, _) -> | |||
{ok, pt:pack(40004, <<>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 40005 查询帮派成员 | |||
%%<<GuildId:32,IsOnline:8>> | |||
%%-------------------------------------- | |||
write(40005, [GuildId, IsOnline]) -> | |||
{ok, pt:pack(40005, <<GuildId:32, IsOnline:8>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 40006 发起弹劾 | |||
%%-------------------------------------- | |||
write(40006, _) -> | |||
{ok, pt:pack(40006, <<>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 40007 弹劾操作 | |||
%%-------------------------------------- | |||
write(40007, [Ops]) -> | |||
{ok, pt:pack(40007, <<Ops:8>>)}; | |||
%%-------------------------------------- | |||
%%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, <<PlayerId:64>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 40031 帮派申请列表(帮主/副帮主) | |||
%%-------------------------------------- | |||
write(40031, _) -> | |||
{ok, pt:pack(40031, <<>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 40032 通过或拒绝加入申请(帮主/副帮主) | |||
%%-------------------------------------- | |||
write(40032, [Uid, Ops]) -> | |||
{ok, pt:pack(40032, <<Uid:64, Ops:8>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 40033 提升职务(帮主) | |||
%%-------------------------------------- | |||
write(40033, [Uid]) -> | |||
{ok, pt:pack(40033, <<Uid:64>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 40034 解散帮派(帮主) | |||
%%-------------------------------------- | |||
write(40034, _) -> | |||
{ok, pt:pack(40034, <<>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 40035 踢出成员(帮主/副帮主) | |||
%%-------------------------------------- | |||
write(40035, [PlayerId]) -> | |||
{ok, pt:pack(40035, <<PlayerId:64>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 40036 帮派升级(帮主/副帮主/长老) | |||
%%-------------------------------------- | |||
write(40036, _) -> | |||
{ok, pt:pack(40036, <<>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 40037 帮主让位 | |||
%%-------------------------------------- | |||
write(40037, [Uid]) -> | |||
{ok, pt:pack(40037, <<Uid:64>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 40039 帮派公告设置 | |||
%%-------------------------------------- | |||
write(40039, [Content]) -> | |||
Content_StrBin = pack_string(Content), | |||
{ok, pt:pack(40039, <<Content_StrBin/binary>>)}; | |||
%%-------------------------------------- | |||
%%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), | |||
<<Len:16, BinData/binary>>. | |||
any_to_binary(Any) -> | |||
tool:to_binary(Any). | |||
@ -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, <<UpgradeType:8>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 44006 升星 | |||
%%-------------------------------------- | |||
write(44006, [AutoBuy, BatchUpgrade]) -> | |||
{ok, pt:pack(44006, <<AutoBuy:8, BatchUpgrade:8>>)}; | |||
%%-------------------------------------- | |||
%%Protocol: 44007 升阶 | |||
%%-------------------------------------- | |||
write(44007, [AutoBuy]) -> | |||
{ok, pt:pack(44007, <<AutoBuy:8>>)}; | |||
%%-------------------------------------- | |||
%% 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), | |||
<<Len:16, BinData/binary>>. | |||
any_to_binary(Any) -> | |||
tool:to_binary(Any). | |||
@ -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, <<Len:16, Cmd:16>>}} -> | |||
?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 -> | |||
<<Code:8, _Bin1/binary>> = 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 -> | |||
<<Accid:32, _Bin2/binary>> = _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 -> | |||
<<Code:8, PlayerId:64, _Bin/binary>> = 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 -> | |||
<<Code:8, _Bin/binary>> = 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 -> | |||
<<SceneId:16, _Other/binary>> = 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 -> | |||
<<Code:8, NewSceneId:16, ReviveX:8, ReviveY:8, _Other/binary>> = 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), | |||
<<DataLen:16, Data/binary>> = 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 ->%%修改机器人帮派属性 | |||
<<GuildId:32, _GuildName_RestBin/binary>> = BinData, | |||
{GuildName, _GuildName_DoneBin} = pt:read_string(_GuildName_RestBin), | |||
<<Position:8>> = _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, <<DestX:16, DestY:16>>)); | |||
%%跑步 | |||
handle(broad_path, {DestX, DestY, Path}, Socket) -> | |||
Len = length(Path), | |||
Fun = fun({X, Y}) -> | |||
<<X:16, Y:16>> | |||
end, | |||
MoveBin = tool:to_binary([Fun(M) || M <- Path]), | |||
gen_tcp:send(Socket, pack(12010, <<DestX:16, DestY:16, Len:16, MoveBin/binary>>)); | |||
%% %%ai模式跑步 | |||
%% handle(run, {X,Y, SX, SY}, Socket) -> | |||
%% ?TRACE("----running:[~p][~p]~n",[X,Y]), | |||
%% gen_tcp:send(Socket, pack(12001, <<X:8, Y:8, SX:8, SY:8>>)); | |||
%%进入场景 | |||
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, <<SceneId:16, Posx:16, Posy:16>>)); | |||
%%静止 | |||
handle(undefined, a, _Socket) -> | |||
ok; | |||
%%获取其他玩家信息 | |||
handle(get_player_info, Id, Socket) -> | |||
gen_tcp:send(Socket, pack(13004, <<Id:16>>)); | |||
%%获取自己信息 | |||
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 = <<ActionLen:16, Action/binary>>, | |||
%% 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 | |||
<<Len:16, Bin1/binary>> -> | |||
case Bin1 of | |||
<<Str:Len/binary-unit:8, Rest/binary>> -> | |||
{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, | |||
<<L:16, Cmd:16, Data/binary>>. | |||
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). | |||
@ -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 | |||
}). |
@ -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(<<SkillId:8, _:8, Rest/binary>>, 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, <<SkillId:8, 0:8, 123456:32, 0:16, 0:16, 2:8, MonId:32>>)). | |||
%%随机使用技能 | |||
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(<<ScenedId:16, BinData/binary>>) -> | |||
<<PlayerListLen:16, PlayerListBin/binary>> = BinData, | |||
Fun_PlayerList = fun(_Idx, {RestBin, ResultList}) -> | |||
<<PosX:16, PosY:16, UId:64, _NmBin_RestBin/binary>> = RestBin, | |||
{NmBin, _NmBin_DoneBin} = pt:read_string(_NmBin_RestBin), | |||
<<Stts:8, Sex:8, Crr:8, CurHp:32, MaxHp:32, Magic:32, MagicMax:32, Weapon:32, Armor:32, Fashion:32, WwaponAcc:32, Wing:32, Mount:32, WeaponStrenLv:8, ArmorStrenLv:8, FashionStrenLv:8 | |||
, WaponAccStrenLv:8, WingStrenLv:8, PetStatus:8, PetQualityLv:8, PetFacade:16, _:16, _PetName_RestBin/binary>> = _NmBin_DoneBin, | |||
{PetName, _PetName_DoneBin} = pt:read_string(_PetName_RestBin), | |||
<<Level:8, _:8, _:8, _:16, _:8, _:32, GulidRestBin/binary>> = _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)), | |||
<<MonListLen:16, MonListBin/binary>> = _PlayerList_DoneBin, | |||
Fun_MonList = fun(_Idx, {RestBin, ResultList}) -> | |||
<<MonId:32, MonTId:32, PosX:16, PosY:16, Towards:16, Stts:8, CurHp:32, MaxHp:32, Magic:32, MagicMax:32, _:8, _:16, _BuffList_RestBin/binary>> = RestBin, | |||
<<BuffListLen:16, BuffListBin/binary>> = _BuffList_RestBin, | |||
Fun_BuffList = fun(_Idx, {RestBin1, ResultList}) -> | |||
<<BuffId:16, ExpirTime:32, _BuffList_RestBin/binary>> = 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)), | |||
<<DropListLen:16, DropListBin/binary>> = _MonList_DoneBin, | |||
Fun_DropList = fun(_Idx, {RestBin2, ResultList}) -> | |||
<<DropId:32, MonId:32, GoodsId:32, GoodsNum:32, DropX:16, DropY:16, EftTime:16, _DropList_RestBin/binary>> = 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)]}. |
@ -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]). | |||
@ -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, <<Len:16, Cmd:16>>}} -> | |||
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 = <<Accid:32, 123456789:32, Len:16, StrBin/binary>>, | |||
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, <<DestX:8, DestY:8>>)); | |||
%%跑步 | |||
handle(broad_path, {DestX, DestY, Path}, Socket) -> | |||
Len = length(Path), | |||
Fun = fun({X, Y}) -> | |||
<<X:8, Y:8>> | |||
end, | |||
MoveBin = tool:to_binary([Fun(M) || M <- Path]), | |||
gen_tcp:send(Socket, pack(12010, <<DestX:8, DestY:8, Len:16, MoveBin/binary>>)); | |||
%%ai模式跑步 | |||
handle(run, {X, Y, SX, SY}, Socket) -> | |||
io:format("----running:[~p][~p]~n", [X, Y]), | |||
gen_tcp:send(Socket, pack(12001, <<X:8, Y:8, SX:8, SY:8>>)); | |||
%%进入场景 | |||
handle(enter_scene, [SceneId], Socket) -> | |||
Posx = random:uniform(30), | |||
Posy = random:uniform(20), | |||
gen_tcp:send(Socket, pack(12001, <<SceneId:16, Posx:8, Posy:8>>)); | |||
%%静止 | |||
handle(undefined, a, _Socket) -> | |||
ok; | |||
%%获取其他玩家信息 | |||
handle(get_player_info, Id, Socket) -> | |||
gen_tcp:send(Socket, pack(13004, <<Id:16>>)); | |||
%%获取自己信息 | |||
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 = <<ActionLen:16, Action/binary>>, | |||
%% 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 | |||
<<Len:16, Bin1/binary>> -> | |||
case Bin1 of | |||
<<Str:Len/binary-unit:8, Rest/binary>> -> | |||
{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, | |||
<<L:16, Cmd:16, Data/binary>>. | |||
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). | |||
@ -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). |
@ -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, <<Id:64>>)), | |||
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, | |||
<<L:16, Cmd:16, Data/binary>>. |
@ -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, <<ContentLen:16, NewContent:ContentLen/binary-unit: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, <<LevelContentLen:16, NewLevelContent:LevelContentLen/binary-unit: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, | |||
<<L:16, Cmd:16, Data/binary>>. |
@ -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. | |||
@ -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 | |||
<<Len:8, SellingBin/binary>> -> | |||
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) -> | |||
<<Location:8, CellNum:16, ListNum:16, ListBin/binary>> = BinData, | |||
BagList = parse_bag_data(ListBin, []), | |||
do_sale(State, BagList). | |||
parse_sale_list(BinData, Result) -> | |||
case BinData of | |||
<<SaleId:64, GoodsUId:64, GoodsId:64, LeftTime:32, Num:32, Price:32, LeftData/binary>> -> | |||
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 | |||
<<GoodsId:64, TypeId:32, Cell:16, GoodsNum:16, Stren:8, StrenPer:8, Bind:8, LeftData/binary>> -> | |||
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, <<ContentLen:16, NewContent:ContentLen/binary-unit: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, <<ContentLen:16, NewContent:ContentLen/binary-unit: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, <<ContentLen:16, NewContent:ContentLen/binary-unit:8>>/binary>>)). | |||
query_bag(State) -> | |||
gen_tcp:send(State#robot.socket, pack(15002, <<0:8>>)). | |||
pack(Cmd, Data) -> | |||
L = byte_size(Data) + ?HEADER_LENGTH, | |||
<<L:16, Cmd:16, Data/binary>>. |
@ -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. |
@ -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. | |||
@ -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. | |||
@ -0,0 +1,9 @@ | |||
%%%--------------------------------------------- | |||
%%% @Module : robot_pet | |||
%%% @Author : smxx | |||
%%% @Created : 2013.03.xx | |||
%%% @Description: 宠物客户端测试程序 | |||
%%%--------------------------------------------- | |||
-module(robot_pet). | |||
%% -include("robot.hrl"). | |||
%% -compile(export_all). |
@ -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, <<ContentLen:16, NewContent:ContentLen/binary-unit:8>>/binary>>)). | |||
pack(Cmd, Data) -> | |||
L = byte_size(Data) + ?HEADER_LENGTH, | |||
<<L:16, Cmd:16, Data/binary>>. |
@ -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, <<TaskProcessId:32>>)). | |||
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, <<ContentLen:16, NewContent:ContentLen/binary-unit:8>>/binary>>)). | |||
submit_task(Socket, TaskProcessId) -> | |||
io:format("submit_task.....................~n"), | |||
gen_tcp:send(Socket, pack(30004, <<TaskProcessId:32>>)). | |||
parse_task_data(BinData, Result) -> | |||
case BinData of | |||
<<Id:32, TaskId:16, State:8, Mark:32, Grade:8, LeftData/binary>> -> | |||
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, <<TotalNum:8>>)). | |||
pack(Cmd, Data) -> | |||
L = byte_size(Data) + ?HEADER_LENGTH, | |||
<<L:16, Cmd:16, Data/binary>>. | |||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |||
%%辅助函数 | |||
%%读取字符串 | |||
read_string(Bin) -> | |||
case Bin of | |||
<<Len:16, Bin1/binary>> -> | |||
case Bin1 of | |||
<<Str:Len/binary-unit:8, Rest/binary>> -> | |||
{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). | |||
@ -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. |
@ -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. |
@ -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)]). |
@ -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]))). | |||
@ -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 **************************** | |||
%% ------------------------------------------------------------------------------------------------------ |
@ -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. |
@ -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). |
@ -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}]). | |||
@ -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]. | |||
@ -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). | |||
@ -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. | |||
@ -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. |
@ -0,0 +1,704 @@ | |||
%%% @author Fred Hebert <mononcqc@ferd.ca> | |||
%%% [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: | |||
%%% | |||
%%% <dl> | |||
%%% <dt>1. State information</dt> | |||
%%% <dd>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.</dd> | |||
%%% <dd>{@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.</dd> | |||
%%% <dd>{@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.</dd> | |||
%%% <dd>Functions to access node statistics, in a manner somewhat similar | |||
%%% to what <a href="https://github.com/ferd/vmstats">vmstats</a> | |||
%%% 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}.</dd> | |||
%%% | |||
%%% <dt>2. OTP tools</dt> | |||
%%% <dd>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.</dd> | |||
%%% | |||
%%% <dt>3. Code Handling</dt> | |||
%%% <dd>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.</dd> | |||
%%% <dd>{@link source/1} allows to print the source of a loaded module, | |||
%%% in case it's not available in the currently running node.</dd> | |||
%%% | |||
%%% <dt>4. Ports and Sockets</dt> | |||
%%% <dd>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.</dd> | |||
%%% <dd>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.</dd> | |||
%%% <dd>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.</dd> | |||
%%% | |||
%%% <dt>5. RPC</dt> | |||
%%% <dd>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.</dd> | |||
%%% <dd>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.</dd> | |||
%%% </dl> | |||
%%% @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(<A.B.C>)' 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(<A.B.C>, 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 <a href="http://www.erlang.org/doc/efficiency_guide/binaryhandling.html#id65722">The efficiency guide</a> | |||
%% 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: | |||
%% | |||
%% <ul> | |||
%% <li>executing process code</li> | |||
%% <li>executing driver code</li> | |||
%% <li>executing NIF code</li> | |||
%% <li>executing BIFs</li> | |||
%% <li>garbage collecting</li> | |||
%% <li>doing memory management</li> | |||
%% </ul> | |||
%% | |||
%% 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). | |||
@ -0,0 +1,726 @@ | |||
%%% @author Fred Hebert <mononcqc@ferd.ca> | |||
%%% [http://ferd.ca/] | |||
%%% @author Lukas Larsson <lukas@erlang.org> | |||
%%% @doc Functions to deal with | |||
%%% <a href="http://www.erlang.org/doc/man/erts_alloc.html">Erlang's memory | |||
%%% allocators</a>, 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 <em>if</em> there is | |||
%%% a problem, but will offer little help to figure out <em>what</em> 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: | |||
%%% <dl> | |||
%%% <dt>sys_alloc</dt> | |||
%%% <dd>System allocator, usually just malloc</dd> | |||
%%% | |||
%%% <dt>mseg_alloc</dt> | |||
%%% <dd>Used by other allocators, can do mmap. Caches allocations</dd> | |||
%%% | |||
%%% <dt>temp_alloc</dt> | |||
%%% <dd>Used for temporary allocations</dd> | |||
%%% | |||
%%% <dt>eheap_alloc</dt> | |||
%%% <dd>Heap data (i.e. process heaps) allocator</dd> | |||
%%% | |||
%%% <dt>binary_alloc</dt> | |||
%%% <dd>Global binary heap allocator</dd> | |||
%%% | |||
%%% <dt>ets_alloc</dt> | |||
%%% <dd>ETS data allocator</dd> | |||
%%% | |||
%%% <dt>driver_alloc</dt> | |||
%%% <dd>Driver data allocator</dd> | |||
%%% | |||
%%% <dt>sl_alloc</dt> | |||
%%% <dd>Short-lived memory blocks allocator</dd> | |||
%%% | |||
%%% <dt>ll_alloc</dt> | |||
%%% <dd>Long-lived data (i.e. Erlang code itself) allocator</dd> | |||
%%% | |||
%%% <dt>fix_alloc</dt> | |||
%%% <dd>Frequently used fixed-size data allocator</dd> | |||
%%% | |||
%%% <dt>std_alloc</dt> | |||
%%% <dd>Allocator for other memory blocks</dd> | |||
%%% | |||
%%% <dt>carrier</dt> | |||
%%% <dd>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: | |||
%%% <ol> | |||
%%% <li>which multiblock carrier you're going to (if at all)</li> | |||
%%% <li>which block in that carrier you're going to</li> | |||
%%% </ol> | |||
%%% | |||
%%% See <a href="http://www.erlang.org/doc/man/erts_alloc.html">the official | |||
%%% documentation on erts_alloc</a> for more details. | |||
%%% </dd> | |||
%%% | |||
%%% <dt>mbcs</dt> | |||
%%% <dd>Multiblock carriers.</dd> | |||
%%% | |||
%%% <dt>sbcs</dt> | |||
%%% <dd>Single block carriers.</dd> | |||
%%% | |||
%%% <dt>lmbcs</dt> | |||
%%% <dd>Largest multiblock carrier size</dd> | |||
%%% | |||
%%% <dt>smbcs</dt> | |||
%%% <dd>Smallest multiblock carrier size</dd> | |||
%%% | |||
%%% <dt>sbct</dt> | |||
%%% <dd>Single block carrier threshold</dd> | |||
%%% </dl> | |||
%%% | |||
%%% 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: | |||
%% | |||
%% <ul> | |||
%% <li>`used' reports the memory that is actively used for allocated | |||
%% Erlang data;</li> | |||
%% <li>`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. </li> | |||
%% <li>`allocated_types' report the memory that is reserved by the | |||
%% VM grouped into the different util allocators.</li> | |||
%% <li>`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.</li> | |||
%% <li>`unused' reports the amount of memory reserved by the VM that | |||
%% is not being allocated. | |||
%% Equivalent to `allocated - used'.</li> | |||
%% <li>`usage' returns a percentage (0.0 .. 1.0) of `used/allocated' | |||
%% memory ratios.</li> | |||
%% </ul> | |||
%% | |||
%% 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. | |||
%% <ul> | |||
%% <li>`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.</li> | |||
%% </ul> | |||
-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. |
@ -0,0 +1,278 @@ | |||
%%% @author Fred Hebert <mononcqc@ferd.ca> | |||
%%% [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)). |
@ -0,0 +1,644 @@ | |||
%%% @author Fred Hebert <mononcqc@ferd.ca> | |||
%%% [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: | |||
%%% | |||
%%% <ul> | |||
%%% <li>Nicer to use interface (arguably) than `dbg' or trace BIFs.</li> | |||
%%% <li>Protection against dumb decisions (matching all calls on a node | |||
%%% being traced, for example)</li> | |||
%%% <li>Adding safe guards in terms of absolute trace count or | |||
%%% rate-limitting</li> | |||
%%% <li>Nicer formatting than default traces</li> | |||
%%% </ul> | |||
%%% | |||
%%% == 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 (<code>'_'</code>) 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: | |||
%% | |||
%% <ul> | |||
%% <li>`Module' is any atom representing a module</li> | |||
%% <li>`Function' is any atom representing a function, or the wildcard | |||
%% <code>'_'</code></li> | |||
%% <li>`Args' is either the arity of a function (`0..255'), a wildcard | |||
%% pattern (<code>'_'</code>), a | |||
%% <a href="http://learnyousomeerlang.com/ets#you-have-been-selected">match specification</a>, | |||
%% or a function from a shell session that can be transformed into | |||
%% a match specification</li> | |||
%% </ul> | |||
%% | |||
%% 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: | |||
%% | |||
%% <ul> | |||
%% <li>All calls from the `queue' module, with 10 calls printed at most: | |||
%% ``recon_trace:calls({queue, '_', '_'}, 10)''</li> | |||
%% <li>All calls to `lists:seq(A,B)', with 100 calls printed at most: | |||
%% `recon_trace:calls({lists, seq, 2}, 100)'</li> | |||
%% <li>All calls to `lists:seq(A,B)', with 100 calls per second at most: | |||
%% `recon_trace:calls({lists, seq, 2}, {100, 1000})'</li> | |||
%% <li>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)'</li> | |||
%% <li>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)'</li> | |||
%% <li>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}])''</li> | |||
%% <li>Print the traces with the function arity instead of literal arguments: | |||
%% `recon_trace:calls(TSpec, Max, [{args, arity}])'</li> | |||
%% <li>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}])'</li> | |||
%% <li>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]}'</li> | |||
%% <li>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})'. </li> | |||
%% </ul> | |||
%% | |||
%% There's a few more combination possible, with multiple trace patterns per call, and more | |||
%% options: | |||
%% | |||
%% <ul> | |||
%% <li>`{pid, PidSpec}': which processes to trace. Valid options is any of | |||
%% `all', `new', `existing', or a process descriptor (`{A,B,C}', | |||
%% `"<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.</li> | |||
%% <li>`{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}'.</li> | |||
%% <li>`{args, arity | args}': whether to print arity in function calls | |||
%% or their (by default) literal representation.</li> | |||
%% <li>`{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)'.</li> | |||
%% <li>`{formatter, fun(Term) -> io_data() end}': override the default | |||
%% formatting functionality provided by recon.</li> | |||
%% <li>`{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).</li> | |||
%% <li>`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.</li> | |||
%% </ul> | |||
%% | |||
%% 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 "<a.b.c>" 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. |
@ -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)). |
@ -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 | |||
]. |
@ -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}. |
@ -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. | |||
@ -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). |