Current section

Files

Jump to
ecrontab src ecrontab.erl
Raw

src/ecrontab.erl

-module(ecrontab).
-include("ecrontab.hrl").
-include("ecrontab_parse.hrl").
-export([
start/0,
stop/0,
init_workers/1,
init_worker/1,
init_task_list/2,
add/3, add/4,
add_spec/4, add_spec/5,
worker_list/0,
add_worker/1,
stop_worker/1,
get_worker_count/0
]).
%% for test
-export([
app_performance_test/3,
parse_spec_performance_test/1,
next_time_performance_test/1,
loop_next_time/2, loop_next_time/3,
loop_next_time_do/3
]).
%% ====================================================================
%% app
%% ====================================================================
start() ->
application:ensure_all_started(?MODULE).
stop() ->
application:stop(?MODULE).
%% ====================================================================
%% API
%% ====================================================================
init_workers([]) -> ok;
init_workers([Worker | Workers]) ->
case init_worker(Worker) of
ok ->
init_workers(Workers);
Err -> Err
end.
init_worker({Worker, WorkerTaskList}) ->
{ok, WorkerPid} = add_worker(Worker),
init_task_list(WorkerPid, WorkerTaskList);
init_worker({Worker, MaxTaskCount, WorkerTaskList}) ->
{ok, WorkerPid} = add_worker([Worker, MaxTaskCount]),
init_task_list(WorkerPid, WorkerTaskList).
init_task_list(_WorkerPid, []) -> ok;
init_task_list(WorkerPid, [{Spec, MFA} | WorkerTaskList]) ->
case add(WorkerPid, Spec, MFA) of
ok ->
init_task_list(WorkerPid, WorkerTaskList);
Err -> Err
end.
-spec add(Worker :: any(), Spec :: list() | tuple() | spec(), MFA :: mfa()) ->
ok | {error, Reason :: any()}.
add(Worker, Spec, MFA) ->
add(Worker, Spec, MFA, []).
add(Worker, Spec, MFA, Options) when is_record(Spec, spec) ->
add_spec(Worker, Spec, MFA, Options);
add(Worker, Spec0, MFA, Options) ->
NowDatetime = erlang:localtime(),
case ecrontab_parse:parse_spec(Spec0, [{filter_over_time, NowDatetime}]) of
{ok, Spec} ->
do_add(Worker, Spec, MFA, NowDatetime, Options);
Err ->
Err
end.
-spec add_spec(Worker :: any(), Spec :: list() | tuple() | spec(), MFA :: mfa(), Options :: list()) ->
ok | {error, Reason :: any()}.
add_spec(Worker, Spec, MFA, Options) ->
add_spec(Worker, Spec, MFA, erlang:localtime(), Options).
add_spec(Worker, Spec, MFA, NowDatetime, Options) when is_record(Spec, spec) ->
do_add(Worker, Spec, MFA, NowDatetime, Options).
-spec worker_list() ->
[{Worker :: any(), Pid :: pid()}, ...].
worker_list() ->
ets:tab2list(?ETS_WORKER_NAME_INDEX).
add_worker(Args) when is_list(Args) ->
ecrontab_worker_sup:start_child(Args);
add_worker(WorkerName) ->
ecrontab_worker_sup:start_child([WorkerName]).
stop_worker(WorkerName) ->
case ets:lookup(?ETS_WORKER_NAME_INDEX, WorkerName) of
[{_, WorkerPid}] ->
supervisor:terminate_child(ecrontab_worker_sup, WorkerPid);
_ ->
{error, no_worker}
end.
-spec get_worker_count() -> Count :: integer().
get_worker_count() ->
proplists:get_value(workers, supervisor:count_children(ecrontab_worker_sup)).
%% ====================================================================
%% internal API
%% ====================================================================
do_add(Worker, Spec, MFA, NowDatetime, Options) when is_list(Options) ->
check_mfa(MFA),
Task = #task{spec = Spec, mfa = MFA, add_time = NowDatetime, options = Options},
do_add(Worker, Task).
do_add(WorkerPid, Task) when is_pid(WorkerPid) ->
ecrontab_worker:add(WorkerPid, Task);
do_add(WorkerName, Task) ->
case ets:lookup(?ETS_WORKER_NAME_INDEX, WorkerName) of
[{_, WorkerPid}] ->
ecrontab_worker:add(WorkerPid, Task);
_ ->
{error, no_worker}
end.
check_mfa(Fun) when is_function(Fun, 0) ->
true;
check_mfa({M, F, A} = MFA) when is_atom(M) andalso is_atom(F) andalso is_list(A) ->
case code:ensure_loaded(M) of
{module, M} ->
Arity = length(A),
case erlang:function_exported(M, F, Arity) of
true -> true;
false ->
exit({error_mfa, MFA})
end;
Err ->
exit({module_not_loaded, M, Err})
end;
check_mfa({_Node, M, F, A}) when is_atom(M) andalso is_atom(F) andalso is_list(A) ->
true;
check_mfa(MFA) ->
exit({error_mfa, MFA}).
%% ====================================================================
%% app performance test
%% ====================================================================
app_performance_test(WorkerCount, Count, Secs) when Secs > 0 andalso Secs < 60 ->
start(),
eprof:start(),
Self = self(),
eprof:profile([Self]),
[add_worker([N, Count]) || N <- lists:seq(1, WorkerCount)],
Datetime = erlang:localtime(),
SecList = app_performance_test_get_sec_list(5, Datetime, []),
{ok, Spec} = ecrontab_parse:parse_spec({'*', '*', '*', '*', '*', '*', SecList}, []),
Fun = fun() -> ok end,
[begin
[ok = add_spec(WorkerName, Spec, Fun, Datetime, []) || _ <- lists:seq(1, Count)],
ok
end || {WorkerName, _WorkerPid} <- worker_list()],
io:format("add spec ok"),
timer:sleep(Secs * 1000),
stop(),
eprof:stop_profiling(),
eprof:analyze(),
eprof:stop().
loop_wait(0) -> ok;
loop_wait(Count) ->
receive
ok ->
loop_wait(Count - 1)
end.
app_performance_test_get_sec_list(0, _, List) ->
List;
app_performance_test_get_sec_list(Secs, NowDatetime, List) ->
Datetime = ecrontab_time_util:next_second(NowDatetime),
Sec = ecrontab_time_util:get_datetime_second(Datetime),
app_performance_test_get_sec_list(Secs - 1, Datetime, [Sec | List]).
%% ====================================================================
%% parse_spec performance test
%% ====================================================================
parse_spec_performance_test(Count) ->
Tests =
[
{'*', '*', '*', '*', '*', '*', '*'},
{3016, '*', '*', '*', '*', '*', '*'},
{'*', [12], '*', '*', '*', '*', '*'},
{'*', '*', 6, '*', '*', '*', '*'},
{'*', '*', '*', 3, '*', '*', '*'},
{'*', '*', '*', '*', 23, '*', '*'},
{'*', '*', '*', '*', '*', 4, '*'},
{'*', '*', '*', '*', '*', '*', 55},
{<<"3016-3018">>, '*', '*', '*', '*', '*', '*'},
{<<"3016-3024/2">>, '*', '*', '*', '*', '*', '*'},
{<<"3016-3028/5">>, '*', '*', '*', '*', '*', '*'},
{[3016, 3017, 3018, 2010], '*', '*', '*', '*', '*', '*'},
{2016, 4, '*', 3, 4, 5, 0},
{'*', '*', '*', '*', '*', '*', [6, 7, 8, 10]}
],
Self = self(),
eprof:start(),
eprof:profile([Self]),
[spawn(fun() ->
loop_parse_spec(Spec, Count),
Self ! ok
end) || Spec <- Tests],
loop_wait(length(Tests)),
eprof:stop_profiling(),
eprof:analyze(),
eprof:stop().
loop_parse_spec(_Spec, 0) ->
ok;
loop_parse_spec(Spec, Count) ->
ecrontab_parse:parse_spec(Spec, []),
loop_parse_spec(Spec, Count - 1).
%% ====================================================================
%% next_time performance test
%% ====================================================================
next_time_performance_test(Count) ->
Tests = [
{'*', '*', '*', '*', '*', [5, 15], 0},
{'*', '*', '*', '*', '*', '*', 0}
],
Self = self(),
eprof:start(),
eprof:profile([Self]),
[spawn(fun() ->
loop_next_time(Spec, Count),
Self ! ok
end) || Spec <- Tests],
loop_wait(length(Tests)),
eprof:stop_profiling(),
eprof:analyze(),
eprof:stop().
loop_next_time(Spec, Count) ->
NowDatetime = {{2016, 3, 7}, {22, 2, 39}},
loop_next_time(Spec, NowDatetime, Count).
loop_next_time(Spec0, NowDatetime, Count) ->
{ok, Spec} = ecrontab_parse:parse_spec(Spec0, []),
{Time, _} = timer:tc(?MODULE, loop_next_time_do, [Spec, NowDatetime, Count]),
Ptime = Time / Count,
Times = 1000000 / Ptime,
io:format("Spec0:~p,Count:~p,tc Time:~ps,per count time:~pus,one sec times:~p~n", [Spec0, Count, Time / 1000000, Ptime, Times]).
loop_next_time_do(Spec, NowDatetime, 1) ->
ecrontab_next_time:next_time(Spec, NowDatetime),
ok;
loop_next_time_do(Spec, NowDatetime, N) ->
case ecrontab_next_time:next_time(Spec, NowDatetime) of
{ok, Datetime} ->
loop_next_time_do(Spec, Datetime, N - 1);
Err ->
Err
end.