Current section
Files
Jump to
Current section
Files
src/ecrontab.erl
-module(ecrontab).
-include("ecrontab.hrl").
-include("ecrontab_parse.hrl").
-export([
start/0,
stop/0,
add/3, add/4, add/5,
add_spec/4, add_spec/5, add_spec/6,
del/2,
worker_list/0,
add_workers/1,
add_worker/1,
stop_worker/1,
start_worker_tasks/1,
get_worker_count/0,
parse_tasks/1,
parse_task/1
]).
%% 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
]).
-export_type([
spec_type/0, spec_field_type/0, spec_field_value/0, spec_field/0, spec/0,
ecrontab_mfa/0, add_result/0, worker_name/0,
worker_setting/0, worker_settings/0,
worker_tasks/0, worker_task/0,
task/0, task_name/0
]).
-type spec_type() ::
?SPEC_TYPE_NORMAL |
?SPEC_TYPE_TIMESTAMP |
?SPEC_TYPE_EVERY_SECOND |
?SPEC_TYPE_INTERVAL_YEAR |
?SPEC_TYPE_ONLY_ONE.
-type spec_field_type() ::
?SPEC_FIELD_TYPE_ANY |
?SPEC_FIELD_TYPE_NUM |
?SPEC_FIELD_TYPE_LIST |
?SPEC_FIELD_TYPE_INTERVAL.
-type spec_field_any() :: ?SPEC_FIELD_ANY.
-type spec_field_num() :: non_neg_integer().
-type spec_field_list() :: [spec_field_num()].
-type spec_field_interval() :: non_neg_integer().
-type spec_field_value() ::
spec_field_any() |
spec_field_num() |
spec_field_list() |
spec_field_interval().
-type spec_field() :: #spec_field{}.
-type spec() :: #spec{}.
-type ecrontab_mfa() :: function() | mfa() | {node(), module(), atom(), list()}.
-type add_result() :: ok | {error, Reason :: any()}.
-type worker_name() :: any().
-type worker_settings() :: [worker_setting()].
-type worker_setting() ::
{worker_name()} |
{worker_name(), max_task_count()} |
{worker_name(), max_task_count(), worker_tasks()} |
{worker_name(), is_worker_auto_start()} |
{worker_name(), is_worker_auto_start(), worker_tasks()} |
{worker_name(), max_task_count(), is_worker_auto_start(), worker_tasks()}|
#{worker_name := worker_name(), max_task_count => max_task_count(),
is_worker_auto_start => is_worker_auto_start(), worker_tasks => worker_tasks()}.
-type max_task_count() :: pos_integer().
-type is_worker_auto_start() :: boolean().
-type worker_tasks() :: [worker_task()].
-type worker_task() :: task() |
{spec() | ecrontab_parse:parse_spec(), ecrontab_mfa()} |
{task_name(), spec() | ecrontab_parse:parse_spec(), ecrontab_mfa()} |
{task_name(), spec() | ecrontab_parse:parse_spec(), ecrontab_mfa(), task_options()}.
-type task() :: #task{}.
-type task_name() :: undefined | any().
-type task_options() :: proplists:proplist().
%% ====================================================================
%% app
%% ====================================================================
start() ->
application:ensure_all_started(?MODULE).
stop() ->
application:stop(?MODULE).
%% ====================================================================
%% API
%% ====================================================================
-spec add(worker_name(), ecrontab_parse:parse_spec() | spec(), ecrontab_mfa()) ->
add_result().
add(WorkerName, Spec, MFA) ->
add(WorkerName, Spec, MFA, []).
-spec add(worker_name(), Spec :: ecrontab_parse:parse_spec() | spec(), ecrontab_mfa(), task_options()) ->
add_result().
add(WorkerName, Spec, MFA, Options) when is_record(Spec, spec) ->
add_spec(WorkerName, Spec, MFA, Options);
add(WorkerName, ParseSpec, MFA, Options) ->
add(WorkerName, undefined, ParseSpec, MFA, Options).
-spec add(worker_name(), task_name(), ecrontab_parse:parse_spec() | spec(),
ecrontab_mfa(), task_options()) -> add_result().
add(WorkerName, Name, Spec0, MFA, Options) ->
NowDatetime = erlang:localtime(),
case ecrontab_parse:parse_spec(Spec0, [{filter_over_time, NowDatetime}]) of
{ok, Spec} ->
do_add(WorkerName, Name, Spec, MFA, NowDatetime, Options);
Error ->
Error
end.
-spec add_spec(worker_name(), spec(), ecrontab_mfa(), task_options()) ->
add_result().
add_spec(WorkerName, Spec, MFA, Options) ->
add_spec(WorkerName, Spec, MFA, erlang:localtime(), Options).
-spec add_spec(worker_name(), spec(), ecrontab_mfa(), calendar:datetime(), task_options()) ->
add_result().
add_spec(WorkerName, Spec, MFA, NowDatetime, Options) when is_record(Spec, spec) ->
do_add(WorkerName, undefined, Spec, MFA, NowDatetime, Options).
-spec add_spec(worker_name(), task_name(), spec(), ecrontab_mfa(),
calendar:datetime(), task_options()) -> add_result().
add_spec(WorkerName, Name, Spec, MFA, NowDatetime, Options) when is_record(Spec, spec) ->
do_add(WorkerName, Name, Spec, MFA, NowDatetime, Options).
-spec del(worker_name(), task_name()) -> ok | {error, Reason :: any()}.
del(WorkerPid, Name) when is_pid(WorkerPid) ->
ecrontab_worker:del(WorkerPid, Name);
del(WorkerName, TaskName) ->
case ets:lookup(?ETS_WORKER_NAME_INDEX, WorkerName) of
[{_, WorkerPid}] ->
ecrontab_worker:del(WorkerPid, TaskName);
_ ->
{error, no_worker}
end.
-spec worker_list() ->
[{worker_name(), Pid :: pid()}, ...].
worker_list() ->
ets:tab2list(?ETS_WORKER_NAME_INDEX).
-spec add_workers(worker_settings()) -> ok.
add_workers([]) -> ok;
add_workers([WorkerSettings | Workers]) ->
case add_worker(WorkerSettings) of
{ok, _} ->
add_workers(Workers);
Error -> Error
end.
-spec add_worker(worker_name() | worker_setting()) -> supervisor:startchild_ret().
add_worker(Args) when is_list(Args) ->
ecrontab_worker_sup:start_child(Args);
add_worker(WorkerSetting) when is_tuple(WorkerSetting) ->
ecrontab_worker_sup:start_child(WorkerSetting);
add_worker(WorkerSetting) when is_map(WorkerSetting) ->
ecrontab_worker_sup:start_child(WorkerSetting);
add_worker(WorkerName) ->
ecrontab_worker_sup:start_child({WorkerName}).
-spec stop_worker(worker_name()) -> ok | {error, no_worker} | {error, atom()}.
stop_worker(WorkerPid) when is_pid(WorkerPid) ->
supervisor:terminate_child(ecrontab_worker_sup, WorkerPid);
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 start_worker_tasks(worker_name()) -> ok | {error, no_worker}.
start_worker_tasks(WorkerName) ->
case ets:lookup(?ETS_WORKER_NAME_INDEX, WorkerName) of
[{_, WorkerPid}] ->
ecrontab_worker:start_tasks(WorkerPid);
_ ->
{error, no_worker}
end.
-spec get_worker_count() -> Count :: integer().
get_worker_count() ->
proplists:get_value(workers, supervisor:count_children(ecrontab_worker_sup)).
-spec parse_tasks(worker_tasks()) -> {ok, [#task{}]} | {error, any()}.
parse_tasks(WorkerTasks) ->
parse_tasks(WorkerTasks, []).
parse_tasks([], WorkerTasks) -> {ok, WorkerTasks};
parse_tasks([WorkerTask | WorkerTasks], Acc) ->
case parse_task(WorkerTask) of
{ok, Task} ->
parse_tasks(WorkerTasks, [Task | Acc]);
{error, Reason} ->
{error, {WorkerTask, Reason}}
end.
-spec parse_task(worker_task()) -> {ok, #task{}} | {error, any()}.
parse_task({Spec0, MFA}) ->
parse_task({undefined, Spec0, MFA});
parse_task({Name, Spec0, MFA}) ->
parse_task({Name, Spec0, MFA, []});
parse_task({Name, Spec0, MFA, Options}) ->
check_mfa(MFA),
NowDatetime = erlang:localtime(),
case parse_task_parse_spec(Spec0, NowDatetime) of
{ok, Spec} ->
{ok, #task{name = Name, spec = Spec, mfa = MFA, add_time = NowDatetime, options = Options}};
Error -> Error
end.
%% ====================================================================
%% internal API
%% ====================================================================
do_add(Worker, Name, Spec, MFA, NowDatetime, Options) when is_list(Options) ->
check_mfa(MFA),
Task = #task{name = Name, 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.
-spec check_mfa(ecrontab_mfa()) -> true.
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;
Error ->
exit({module_not_loaded, M, Error})
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}).
parse_task_parse_spec(Spec, _NowDatetime) when is_record(Spec, spec) -> Spec;
parse_task_parse_spec(Spec0, NowDatetime) ->
ecrontab_parse:parse_spec(Spec0, [{filter_over_time, NowDatetime}]).
%% ====================================================================
%% 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);
Error ->
Error
end.