Browse Source

Merge branch 'develop-optional_stats' into develop

develop
Pedram Nimreezi 7 years ago
parent
commit
8a575e38cb
2 changed files with 335 additions and 122 deletions
  1. +250
    -56
      src/glc.erl
  2. +85
    -66
      src/glc_code.erl

+ 250
- 56
src/glc.erl View File

@ -203,23 +203,29 @@ union(Queries) ->
%% The counters are reset by default, unless Reset is set to false %% The counters are reset by default, unless Reset is set to false
-spec compile(atom(), glc_ops:op() | [glc_ops:op()]) -> {ok, atom()}. -spec compile(atom(), glc_ops:op() | [glc_ops:op()]) -> {ok, atom()}.
compile(Module, Query) -> compile(Module, Query) ->
compile(Module, Query, undefined, true).
compile(Module, Query, [{statistics, true}]).
-spec compile(atom(), glc_ops:op() | [glc_ops:op()], boolean()) -> {ok, atom()}. -spec compile(atom(), glc_ops:op() | [glc_ops:op()], boolean()) -> {ok, atom()}.
compile(Module, Query, Reset) when is_boolean(Reset) ->
compile(Module, Query, undefined, Reset);
%compile(Module, Query, Reset) when is_boolean(Reset) ->
% compile(Module, Query, undefined, Reset);
compile(Module, Query, undefined) -> compile(Module, Query, undefined) ->
compile(Module, Query, undefined, true);
compile(Module, Query, [{statistics, true}]);
compile(Module, Query, Store) when is_list(Store) -> compile(Module, Query, Store) when is_list(Store) ->
compile(Module, Query, Store, true).
case lists:keyfind(statistics, 1, Store) of
{_, true} ->
compile(Module, Query, Store, true);
_ ->
compile(Module, Query, Store, false)
end.
compile(Module, Query, Store, Reset) ->
{ok, ModuleData} = module_data(Module, Query, Store),
case glc_code:compile(Module, ModuleData) of
{ok, Module} when Reset ->
compile(Module, Query, Store, Stats) ->
{ok, ModuleData} = module_data(Module, Query, Store, Stats),
case glc_code:compile(Module, ModuleData, Stats) of
{ok, Module} when is_boolean(Stats), Stats =:= true ->
reset_counters(Module), reset_counters(Module),
{ok, Module}; {ok, Module};
{ok, Module} -> {ok, Module} ->
ok = take_down(Module, Stats),
{ok, Module} {ok, Module}
end. end.
@ -284,26 +290,41 @@ job_input(Module) ->
job_time(Module) -> job_time(Module) ->
Module:info(job_time). Module:info(job_time).
%% @doc Release a compiled query.
%%
%% This releases all resources allocated by a compiled query. The query name
%% is expected to be associated with an existing query module. Calling this
%% function will shutdown all relevant processes and purge/delete the module.
-spec delete(atom()) -> ok.
delete(Module) ->
Params = params_name(Module),
-spec take_down(atom(), boolean()) -> ok.
take_down(Module, false) ->
Counts = counts_name(Module), Counts = counts_name(Module),
ManageParams = manage_params_name(Module),
ManageCounts = manage_counts_name(Module), ManageCounts = manage_counts_name(Module),
_ = [ begin _ = [ begin
ok = supervisor:terminate_child(Sup, Name), ok = supervisor:terminate_child(Sup, Name),
ok = supervisor:delete_child(Sup, Name) ok = supervisor:delete_child(Sup, Name)
end || {Sup, Name} <- end || {Sup, Name} <-
[{gr_manager_sup, ManageParams}, {gr_manager_sup, ManageCounts},
{gr_param_sup, Params}, {gr_counter_sup, Counts}]
[{gr_manager_sup, ManageCounts},
{gr_counter_sup, Counts}]
], ],
ok;
take_down(Module, true) ->
Params = params_name(Module),
ManageParams = manage_params_name(Module),
catch (take_down(Module, false)),
_ = [ begin
ok = supervisor:terminate_child(Sup, Name),
ok = supervisor:delete_child(Sup, Name)
end || {Sup, Name} <-
[{gr_manager_sup, ManageParams},
{gr_param_sup, Params}]
],
ok.
%% @doc Release a compiled query.
%%
%% This releases all resources allocated by a compiled query. The query name
%% is expected to be associated with an existing query module. Calling this
%% function will shutdown all relevant processes and purge/delete the module.
-spec delete(atom()) -> ok.
delete(Module) ->
ok = take_down(Module, true),
code:soft_purge(Module), code:soft_purge(Module),
code:delete(Module), code:delete(Module),
ok. ok.
@ -330,8 +351,8 @@ prepare_store(Store) ->
end, Store). end, Store).
%% @private Map a query to a module data term. %% @private Map a query to a module data term.
-spec module_data(atom(), term(), term()) -> {ok, #module{}}.
module_data(Module, Query, Store) ->
-spec module_data(atom(), term(), term(), boolean()) -> {ok, #module{}}.
module_data(Module, Query, Store, Stats) ->
%% terms in the query which are not valid arguments to the %% terms in the query which are not valid arguments to the
%% erl_syntax:abstract/1 functions are stored in ETS. %% erl_syntax:abstract/1 functions are stored in ETS.
%% the terms are only looked up once they are necessary to %% the terms are only looked up once they are necessary to
@ -342,13 +363,13 @@ module_data(Module, Query, Store) ->
%% the abstract_tables/1 function expects a list of name-atom pairs. %% the abstract_tables/1 function expects a list of name-atom pairs.
%% tables are referred to by name in the generated code. the table/1 %% tables are referred to by name in the generated code. the table/1
%% function maps names to registered processes response for those tables. %% function maps names to registered processes response for those tables.
Tables = module_tables(Module),
Tables = module_tables(Module, Stats),
Query2 = glc_lib:reduce(Query), Query2 = glc_lib:reduce(Query),
Store2 = prepare_store(Store), Store2 = prepare_store(Store),
{ok, #module{'query'=Query, tables=Tables, qtree=Query2, store=Store2}}. {ok, #module{'query'=Query, tables=Tables, qtree=Query2, store=Store2}}.
%% @private Create a data managed supervised process for params, counter tables %% @private Create a data managed supervised process for params, counter tables
module_tables(Module) ->
module_tables(Module, _Stats) ->
Params = params_name(Module), Params = params_name(Module),
Counts = counts_name(Module), Counts = counts_name(Module),
ManageParams = manage_params_name(Module), ManageParams = manage_params_name(Module),
@ -414,7 +435,7 @@ start() ->
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
setup_query(Module, Query) -> setup_query(Module, Query) ->
setup_query(Module, Query, undefined).
setup_query(Module, Query, [{statistics, true}]).
setup_query(Module, Query, Store) -> setup_query(Module, Query, Store) ->
?assertNot(erlang:module_loaded(Module)), ?assertNot(erlang:module_loaded(Module)),
@ -460,9 +481,18 @@ events_test_() ->
?assertEqual({null, false}, Mod:info('query')) ?assertEqual({null, false}, Mod:info('query'))
end end
}, },
{"no init counters test",
fun() ->
{compiled, Mod} = setup_query(testmod4a, glc:null(false), [{statistics, false}]),
glc:handle(Mod, gre:make([], [list])),
?assertEqual(0, Mod:info(input)),
?assertEqual(0, Mod:info(filter)),
?assertEqual(0, Mod:info(output))
end
},
{"init counters test", {"init counters test",
fun() -> fun() ->
{compiled, Mod} = setup_query(testmod4, glc:null(false)),
{compiled, Mod} = setup_query(testmod4b, glc:null(false)),
?assertEqual(0, Mod:info(input)), ?assertEqual(0, Mod:info(input)),
?assertEqual(0, Mod:info(filter)), ?assertEqual(0, Mod:info(filter)),
?assertEqual(0, Mod:info(output)) ?assertEqual(0, Mod:info(output))
@ -639,7 +669,7 @@ events_test_() ->
{"with function storage test", {"with function storage test",
fun() -> fun() ->
Self = self(), Self = self(),
Store = [{stored, value}],
Store = [{stored, value}, {statistics, true}],
{compiled, Mod} = setup_query(testmod15, {compiled, Mod} = setup_query(testmod15,
glc:with(glc:eq(a, 1), fun(Event, EStore) -> glc:with(glc:eq(a, 1), fun(Event, EStore) ->
Self ! {gre:fetch(a, Event), EStore} end), Self ! {gre:fetch(a, Event), EStore} end),
@ -651,7 +681,7 @@ events_test_() ->
}, },
{"delete test", {"delete test",
fun() -> fun() ->
{compiled, Mod} = setup_query(testmod16, glc:null(false)),
{compiled, Mod} = setup_query(testmod16a, glc:null(false)),
?assert(is_atom(Mod:table(params))), ?assert(is_atom(Mod:table(params))),
?assertMatch([_|_], gr_param:info(Mod:table(params))), ?assertMatch([_|_], gr_param:info(Mod:table(params))),
?assert(is_list(code:which(Mod))), ?assert(is_list(code:which(Mod))),
@ -669,6 +699,27 @@ events_test_() ->
?assertEqual(undefined, whereis(manage_counts_name(Mod))) ?assertEqual(undefined, whereis(manage_counts_name(Mod)))
end end
}, },
{"delete test no stats",
fun() ->
{compiled, Mod} = setup_query(testmod16b, glc:null(false),
[{statistics, false}]),
?assert(is_atom(Mod:table(params))),
?assertMatch([_|_], gr_param:info(Mod:table(params))),
?assert(is_list(code:which(Mod))),
?assert(is_pid(whereis(params_name(Mod)))),
?assertNot(is_pid(whereis(counts_name(Mod)))),
?assert(is_pid(whereis(manage_params_name(Mod)))),
?assertNot(is_pid(whereis(manage_counts_name(Mod)))),
glc:delete(Mod),
?assertEqual(non_existing, code:which(Mod)),
?assertEqual(undefined, whereis(params_name(Mod))),
?assertEqual(undefined, whereis(counts_name(Mod))),
?assertEqual(undefined, whereis(manage_params_name(Mod))),
?assertEqual(undefined, whereis(manage_counts_name(Mod)))
end
},
{"reset counters test", {"reset counters test",
fun() -> fun() ->
{compiled, Mod} = setup_query(testmod17, {compiled, Mod} = setup_query(testmod17,
@ -717,16 +768,35 @@ events_test_() ->
?assertEqual(7, length(gr_counter:list(Mod:table(counters)))) ?assertEqual(7, length(gr_counter:list(Mod:table(counters))))
end end
}, },
{"ets data recovery test no stats",
fun() ->
Self = self(),
{compiled, Mod} = setup_query(testmod18b,
glc:with(glc:eq(a, 1), fun(Event) -> Self ! gre:fetch(a, Event) end),
[{statistics, false}]),
glc:handle(Mod, gre:make([{a,1}], [list])),
?assertEqual(0, Mod:info(output)),
?assertEqual(1, receive Msg -> Msg after 0 -> notcalled end),
?assertEqual(1, length(gr_param:list(Mod:table(params)))),
true = exit(whereis(Mod:table(params)), kill),
?assertEqual(undefined, whereis(Mod:table(counters))),
?assertEqual(0, Mod:info(input)),
glc:handle(Mod, gre:make([{'a', 1}], [list])),
?assertEqual(0, Mod:info(input)),
?assertEqual(0, Mod:info(output)),
?assertEqual(1, length(gr_param:list(Mod:table(params))))
end
},
{"run timed job test", {"run timed job test",
fun() -> fun() ->
Self = self(), Self = self(),
Store = [{stored, value}],
Runtime = 0.15,
Store = [{stored, value}, {statistics, true}],
Runtime = 0.015,
{compiled, Mod} = setup_query(testmod19, {compiled, Mod} = setup_query(testmod19,
glc:gt(runtime, Runtime), glc:gt(runtime, Runtime),
Store), Store),
glc:run(Mod, fun(Event, EStore) -> glc:run(Mod, fun(Event, EStore) ->
timer:sleep(100),
timer:sleep(10),
Self ! {gre:fetch(a, Event), EStore} Self ! {gre:fetch(a, Event), EStore}
end, gre:make([{a,1}], [list])), end, gre:make([{a,1}], [list])),
?assertEqual(0, Mod:info(output)), ?assertEqual(0, Mod:info(output)),
@ -739,7 +809,7 @@ events_test_() ->
Store), Store),
glc:handle(Mod, gre:make([{'a', 1}], [list])), glc:handle(Mod, gre:make([{'a', 1}], [list])),
glc:run(Mod, fun(Event, EStore) -> glc:run(Mod, fun(Event, EStore) ->
timer:sleep(200),
timer:sleep(30),
Self ! {gre:fetch(a, Event), EStore} Self ! {gre:fetch(a, Event), EStore}
end, gre:make([{a,2}], [list])), end, gre:make([{a,2}], [list])),
?assertEqual(1, Mod:info(output)), ?assertEqual(1, Mod:info(output)),
@ -750,8 +820,11 @@ events_test_() ->
}, },
{"reset job counters", {"reset job counters",
fun() -> fun() ->
{compiled, Mod} = setup_query(testmod20,
glc:any([glc:eq(a, 1), glc:gt(runtime, 0.15)])),
Self = self(),
Store = [{stored, value}, {statistics, true}],
{compiled, Mod} = setup_query(testmod20a,
glc:any([glc:eq(a, 1), glc:gt(runtime, 0.015)]), Store),
glc:handle(Mod, gre:make([{'a', 2}], [list])), glc:handle(Mod, gre:make([{'a', 2}], [list])),
glc:handle(Mod, gre:make([{'b', 1}], [list])), glc:handle(Mod, gre:make([{'b', 1}], [list])),
?assertEqual(2, Mod:info(input)), ?assertEqual(2, Mod:info(input)),
@ -762,17 +835,20 @@ events_test_() ->
?assertEqual(3, Mod:info(filter)), ?assertEqual(3, Mod:info(filter)),
?assertEqual(1, Mod:info(output)), ?assertEqual(1, Mod:info(output)),
Self = self(),
glc:run(Mod, fun(Event, EStore) -> glc:run(Mod, fun(Event, EStore) ->
timer:sleep(100),
timer:sleep(20),
Self ! {gre:fetch(a, Event), EStore} Self ! {gre:fetch(a, Event), EStore}
end, gre:make([{a,1}], [list])), end, gre:make([{a,1}], [list])),
?assertEqual(2, Mod:info(output)), ?assertEqual(2, Mod:info(output)),
?assertEqual(3, Mod:info(filter)), ?assertEqual(3, Mod:info(filter)),
?assertEqual(1, receive {Msg, undefined} -> Msg after 0 -> notcalled end),
receive {Msg, _} ->
?assertEqual(1, Msg)
after 0 ->
erlang:error(notcalled)
end,
{_, Msg1} = glc:run(Mod, fun(_Event, _EStore) -> {_, Msg1} = glc:run(Mod, fun(_Event, _EStore) ->
timer:sleep(200),
timer:sleep(20),
{error, badtest} {error, badtest}
end, gre:make([{a,1}], [list])), end, gre:make([{a,1}], [list])),
@ -784,7 +860,7 @@ events_test_() ->
?assertEqual({error, badtest}, Msg1), ?assertEqual({error, badtest}, Msg1),
{_, Msg2} = glc:run(Mod, fun(_Event, _EStore) -> {_, Msg2} = glc:run(Mod, fun(_Event, _EStore) ->
timer:sleep(20),
timer:sleep(10),
{ok, goodtest} {ok, goodtest}
end, gre:make([{a,2}], [list])), end, gre:make([{a,2}], [list])),
@ -840,10 +916,110 @@ events_test_() ->
?assertEqual(0, Mod:info(job_run)) ?assertEqual(0, Mod:info(job_run))
end end
}, },
{"reset job counters with no statistics",
fun() ->
Self = self(),
Store = [{stored, value}, {statistics, false}],
{compiled, Mod} = setup_query(testmod20b,
glc:any([glc:eq(a, 1), glc:gt(runtime, 0.15)]), Store),
glc:handle(Mod, gre:make([{'a', 2}], [list])),
glc:handle(Mod, gre:make([{'b', 1}], [list])),
?assertEqual(0, Mod:info(input)),
?assertEqual(0, Mod:info(filter)),
glc:handle(Mod, gre:make([{'a', 1}], [list])),
glc:handle(Mod, gre:make([{'b', 2}], [list])),
?assertEqual(0, Mod:info(input)),
?assertEqual(0, Mod:info(filter)),
?assertEqual(0, Mod:info(output)),
glc:run(Mod, fun(Event, EStore) ->
Self ! {gre:fetch(a, Event), EStore}
end, gre:make([{a,1}], [list])),
?assertEqual(0, Mod:info(output)),
?assertEqual(0, Mod:info(filter)),
% not working?
% ?assertEqual(1, receive {Msg, undefined} -> Msg after 0 -> notcalled end),
receive {Msg, _} ->
?assertEqual(1, Msg)
after 0 ->
erlang:error(notcalled)
end,
{_, Msg1} = glc:run(Mod, fun(_Event, _EStore) ->
timer:sleep(20),
{error, badtest}
end, gre:make([{a,1}], [list])),
?assertEqual(0, Mod:info(output)),
?assertEqual(0, Mod:info(filter)),
?assertEqual(0, Mod:info(job_input)),
?assertEqual(0, Mod:info(job_error)),
?assertEqual(0, Mod:info(job_run)),
?assertEqual({error, badtest}, Msg1),
{_, Msg2} = glc:run(Mod, fun(_Event, _EStore) ->
timer:sleep(20),
{ok, goodtest}
end, gre:make([{a,2}], [list])),
?assertEqual(0, Mod:info(output)),
?assertEqual(0, Mod:info(filter)),
?assertEqual(0, Mod:info(job_input)),
?assertEqual(0, Mod:info(job_error)),
?assertEqual(0, Mod:info(job_run)),
?assertEqual({ok, goodtest}, Msg2),
glc:reset_counters(Mod, input),
?assertEqual(0, Mod:info(input)),
?assertEqual(0, Mod:info(filter)),
?assertEqual(0, Mod:info(output)),
?assertEqual(0, Mod:info(job_input)),
?assertEqual(0, Mod:info(job_error)),
?assertEqual(0, Mod:info(job_run)),
glc:reset_counters(Mod, filter),
?assertEqual(0, glc:input(Mod)),
?assertEqual(0, glc:filter(Mod)),
?assertEqual(0, glc:output(Mod)),
?assertEqual(0, glc:job_input(Mod)),
?assertEqual(0, glc:job_error(Mod)),
?assertEqual(0, glc:job_run(Mod)),
glc:reset_counters(Mod, output),
?assertEqual(0, Mod:info(input)),
?assertEqual(0, Mod:info(filter)),
?assertEqual(0, Mod:info(output)),
?assertEqual(0, Mod:info(job_input)),
?assertEqual(0, Mod:info(job_error)),
?assertEqual(0, Mod:info(job_run)),
glc:reset_counters(Mod, job_input),
?assertEqual(0, Mod:info(input)),
?assertEqual(0, Mod:info(filter)),
?assertEqual(0, Mod:info(output)),
?assertEqual(0, Mod:info(job_input)),
?assertEqual(0, Mod:info(job_error)),
?assertEqual(0, Mod:info(job_run)),
glc:reset_counters(Mod, job_error),
?assertEqual(0, Mod:info(input)),
?assertEqual(0, Mod:info(filter)),
?assertEqual(0, Mod:info(output)),
?assertEqual(0, Mod:info(job_input)),
?assertEqual(0, Mod:info(job_error)),
?assertEqual(0, Mod:info(job_run)),
glc:reset_counters(Mod, job_run),
?assertEqual(0, Mod:info(input)),
?assertEqual(0, Mod:info(filter)),
?assertEqual(0, Mod:info(output)),
?assertEqual(0, Mod:info(job_input)),
?assertEqual(0, Mod:info(job_error)),
?assertEqual(0, Mod:info(job_run))
end
},
{"variable storage test", {"variable storage test",
fun() -> fun() ->
{compiled, Mod} = setup_query(testmod20a,
glc:eq(a, 2), [{stream, time}]),
{compiled, Mod} = setup_query(testmod20c,
glc:eq(a, 2), [{stream, time}, {statistics, true}]),
glc:handle(Mod, gre:make([{'a', 2}], [list])), glc:handle(Mod, gre:make([{'a', 2}], [list])),
glc:handle(Mod, gre:make([{'b', 1}], [list])), glc:handle(Mod, gre:make([{'b', 1}], [list])),
?assertEqual(2, Mod:info(input)), ?assertEqual(2, Mod:info(input)),
@ -855,17 +1031,35 @@ events_test_() ->
?assertEqual({error, undefined}, glc:get(Mod, beam)) ?assertEqual({error, undefined}, glc:get(Mod, beam))
end end
}, },
{"with multi function any test no stats",
fun() ->
Self = self(),
Store = [{stored, value}, {statistics, false}],
G1 = glc:with(glc:eq(a, 1), fun(_Event, EStore) ->
Self ! {a, EStore} end),
G2 = glc:with(glc:eq(b, 2), fun(_Event, EStore) ->
Self ! {b, EStore} end),
{compiled, Mod} = setup_query(testmod20d, any([G1, G2]),
Store),
glc:handle(Mod, gre:make([{a,1}], [list])),
?assertEqual(0, Mod:info(output)),
?assertEqual(a, receive {Msg, _Store} -> Msg after 0 -> notcalled end),
?assertEqual(b, receive {Msg, _Store} -> Msg after 0 -> notcalled end)
end
},
{"with multi function any test", {"with multi function any test",
fun() -> fun() ->
Self = self(), Self = self(),
Store = [{stored, value}],
Store = [{stored, value}, {statistics, true}],
G1 = glc:with(glc:eq(a, 1), fun(_Event, EStore) -> G1 = glc:with(glc:eq(a, 1), fun(_Event, EStore) ->
Self ! {a, EStore} end), Self ! {a, EStore} end),
G2 = glc:with(glc:eq(b, 2), fun(_Event, EStore) -> G2 = glc:with(glc:eq(b, 2), fun(_Event, EStore) ->
Self ! {b, EStore} end), Self ! {b, EStore} end),
{compiled, Mod} = setup_query(testmod20b, any([G1, G2]),
{compiled, Mod} = setup_query(testmod20e, any([G1, G2]),
Store), Store),
glc:handle(Mod, gre:make([{a,1}], [list])), glc:handle(Mod, gre:make([{a,1}], [list])),
?assertEqual(1, Mod:info(output)), ?assertEqual(1, Mod:info(output)),
@ -876,7 +1070,7 @@ events_test_() ->
{"with multi function all test", {"with multi function all test",
fun() -> fun() ->
Self = self(), Self = self(),
Store = [{stored, value}],
Store = [{stored, value}, {statistics, true}],
G1 = glc:with(glc:eq(a, 1), fun(_Event, EStore) -> G1 = glc:with(glc:eq(a, 1), fun(_Event, EStore) ->
Self ! {a, EStore} end), Self ! {a, EStore} end),
@ -903,7 +1097,7 @@ events_test_() ->
{"with multi-function output match test", {"with multi-function output match test",
fun() -> fun() ->
Self = self(), Self = self(),
Store = [{stored, value}],
Store = [{stored, value}, {statistics, true}],
{compiled, Mod} = setup_query(testmod22, {compiled, Mod} = setup_query(testmod22,
[glc:with(glc:eq(a, 1), fun(Event, _EStore) -> [glc:with(glc:eq(a, 1), fun(Event, _EStore) ->
@ -920,7 +1114,7 @@ events_test_() ->
{"with multi-function output double-match test", {"with multi-function output double-match test",
fun() -> fun() ->
Self = self(), Self = self(),
Store = [{stored, value}],
Store = [{stored, value}, {statistics, true}],
{compiled, Mod} = setup_query(testmod23, {compiled, Mod} = setup_query(testmod23,
[glc:with(glc:eq(a, 1), fun(Event, _EStore) -> [glc:with(glc:eq(a, 1), fun(Event, _EStore) ->
Self ! {a, gre:fetch(a, Event)} end), Self ! {a, gre:fetch(a, Event)} end),
@ -936,7 +1130,7 @@ events_test_() ->
{"with multi function complex match test", {"with multi function complex match test",
fun() -> fun() ->
Self = self(), Self = self(),
Store = [{stored, value}],
Store = [{stored, value}, {statistics, true}],
G1 = glc:with(glc:gt(r, 0.1), fun(_Event, EStore) -> G1 = glc:with(glc:gt(r, 0.1), fun(_Event, EStore) ->
Self ! {a, EStore} end), Self ! {a, EStore} end),
@ -979,20 +1173,20 @@ events_test_() ->
{"with single-function run test", {"with single-function run test",
fun() -> fun() ->
Self = self(), Self = self(),
Store = [{stored, value}],
Store = [{stored, value}, {statistics, true}],
{compiled, Mod1} = setup_query(testmod25a, {compiled, Mod1} = setup_query(testmod25a,
glc:with(glc:all([glc:gt(runtime, 0.15), glc:lt(a, 3)]), fun(Event, EStore) ->
glc:with(glc:all([glc:gt(runtime, 0.015), glc:lt(a, 3)]), fun(Event, EStore) ->
Self ! {gre:fetch(a, Event), EStore} end), Self ! {gre:fetch(a, Event), EStore} end),
Store), Store),
glc:run(Mod1, fun(_Event, _EStore) -> timer:sleep(200), ok end, gre:make([{a, 2}], [list])),
glc:run(Mod1, fun(_Event, _EStore) -> timer:sleep(20), ok end, gre:make([{a, 2}], [list])),
?assertEqual(1, Mod1:info(output)), ?assertEqual(1, Mod1:info(output)),
?assertEqual(2, receive {Msg, Store} -> Msg after 250 -> notcalled end), ?assertEqual(2, receive {Msg, Store} -> Msg after 250 -> notcalled end),
{compiled, Mod2} = setup_query(testmod25b, {compiled, Mod2} = setup_query(testmod25b,
glc:with(glc:all([glc:gt(runtime, 0.15), glc:lt(a, 3)]), fun(Event, EStore) ->
glc:with(glc:all([glc:gt(runtime, 0.015), glc:lt(a, 3)]), fun(Event, EStore) ->
Self ! {gre:fetch(a, Event), EStore} Self ! {gre:fetch(a, Event), EStore}
end), Store), end), Store),
{_, {error, later}} = glc:run(Mod2, fun(_Event, _EStore) -> {_, {error, later}} = glc:run(Mod2, fun(_Event, _EStore) ->
timer:sleep(200),
timer:sleep(20),
erlang:exit(later) erlang:exit(later)
end, gre:make([{a, 2}], [list])), end, gre:make([{a, 2}], [list])),
?assertEqual(1, Mod2:info(output)), ?assertEqual(1, Mod2:info(output)),
@ -1003,9 +1197,9 @@ events_test_() ->
{"with multi-function output run error test", {"with multi-function output run error test",
fun() -> fun() ->
Self = self(), Self = self(),
Store = [{stored, value}],
Store = [{stored, value}, {statistics, true}],
{compiled, Mod} = setup_query(testmod26, {compiled, Mod} = setup_query(testmod26,
[glc:with(glc:gt(runtime, 0.15), fun(Event, _EStore) ->
[glc:with(glc:gt(runtime, 0.015), fun(Event, _EStore) ->
Self ! {a, gre:fetch(b, Event)} Self ! {a, gre:fetch(b, Event)}
end), end),
glc:with(glc:eq(c, 3), fun(Event, _EStore) -> glc:with(glc:eq(c, 3), fun(Event, _EStore) ->
@ -1023,7 +1217,7 @@ events_test_() ->
Store), Store),
Event = gre:make([{a,1}, {b, 3}, {c, 4}], [list]), Event = gre:make([{a,1}, {b, 3}, {c, 4}], [list]),
{_, {error, bye}} = glc:run(Mod, fun(_Event, _EStore) -> {_, {error, bye}} = glc:run(Mod, fun(_Event, _EStore) ->
timer:sleep(200),
timer:sleep(20),
erlang:error(bye) erlang:error(bye)
end, Event), end, Event),
?assertEqual(3, Mod:info(output)), ?assertEqual(3, Mod:info(output)),
@ -1036,7 +1230,7 @@ events_test_() ->
fun() -> fun() ->
Self = self(), Self = self(),
XPid = spawn(fun() -> receive {msg, Msg, Pid} -> Self ! {Msg, Pid} end end), XPid = spawn(fun() -> receive {msg, Msg, Pid} -> Self ! {Msg, Pid} end end),
Store = [{stored, XPid}],
Store = [{stored, XPid}, {statistics, true}],
{compiled, Mod} = setup_query(testmod27, {compiled, Mod} = setup_query(testmod27,
glc:with(glc:eq(a, 1), fun(Event, _EStore) -> glc:with(glc:eq(a, 1), fun(Event, _EStore) ->
{ok, Pid} = glc:get(testmod27, stored), {ok, Pid} = glc:get(testmod27, stored),

+ 85
- 66
src/glc_code.erl View File

@ -1,8 +1,8 @@
%% @doc Code generation functions. %% @doc Code generation functions.
-module(glc_code). -module(glc_code).
-compile({nowarn_unused_function, {abstract_module,2}}).
-compile({nowarn_unused_function, {abstract_module,3}}).
-compile({nowarn_unused_function, {abstract_tables,1}}). -compile({nowarn_unused_function, {abstract_tables,1}}).
-compile({nowarn_unused_function, {abstract_reset,0}}).
-compile({nowarn_unused_function, {abstract_reset,1}}).
-compile({nowarn_unused_function, {abstract_filter,3}}). -compile({nowarn_unused_function, {abstract_filter,3}}).
-compile({nowarn_unused_function, {abstract_filter_,4}}). -compile({nowarn_unused_function, {abstract_filter_,4}}).
-compile({nowarn_unused_function, {abstract_opfilter,6}}). -compile({nowarn_unused_function, {abstract_opfilter,6}}).
@ -21,33 +21,34 @@
-export([ -export([
compile/2
compile/3
]). ]).
-define(erl, erl_syntax). -define(erl, erl_syntax).
-record(module, { -record(module, {
'query' :: term(), 'query' :: term(),
tables :: [{atom(), atom()}],
qtree :: term(),
store :: term()
tables :: [{atom(), atom()}],
qtree :: term(),
store :: term()
}). }).
-type syntaxTree() :: erl_syntax:syntaxTree(). -type syntaxTree() :: erl_syntax:syntaxTree().
-record(state, { -record(state, {
event = undefined :: syntaxTree(),
fields = [] :: [{atom(), syntaxTree()}],
fieldc = 0 :: non_neg_integer(),
paramvars = [] :: [{term(), syntaxTree()}],
paramstab = undefined :: atom(),
countstab = undefined :: atom()
event = undefined :: syntaxTree(),
fields = [] :: [{atom(), syntaxTree()}],
fieldc = 0 :: non_neg_integer(),
paramvars = [] :: [{term(), syntaxTree()}],
paramstab = undefined :: atom(),
countstab = undefined :: atom(),
statistics = false :: boolean()
}). }).
-type nextFun() :: fun((#state{}) -> [syntaxTree()]). -type nextFun() :: fun((#state{}) -> [syntaxTree()]).
compile(Module, ModuleData) ->
{ok, forms, Forms} = abstract_module(Module, ModuleData),
compile(Module, ModuleData, Stats) ->
{ok, forms, Forms} = abstract_module(Module, ModuleData, Stats),
{ok, Module, Binary} = compile_forms(Forms, [nowarn_unused_vars]), {ok, Module, Binary} = compile_forms(Forms, [nowarn_unused_vars]),
{ok, loaded, Module} = load_binary(Module, Binary), {ok, loaded, Module} = load_binary(Module, Binary),
{ok, Module}. {ok, Module}.
@ -55,9 +56,9 @@ compile(Module, ModuleData) ->
%% abstract code generation functions %% abstract code generation functions
%% @private Generate an abstract dispatch module. %% @private Generate an abstract dispatch module.
-spec abstract_module(atom(), #module{}) -> {ok, forms, list()}.
abstract_module(Module, Data) ->
Forms = [?erl:revert(E) || E <- abstract_module_(Module, Data)],
-spec abstract_module(atom(), #module{}, #state{}) -> {ok, forms, list()}.
abstract_module(Module, Data, Stats) ->
Forms = [?erl:revert(E) || E <- abstract_module_(Module, Data, Stats)],
case lists:keyfind(errors, 1, erl_syntax_lib:analyze_forms(Forms)) of case lists:keyfind(errors, 1, erl_syntax_lib:analyze_forms(Forms)) of
false -> {ok, forms, Forms}; false -> {ok, forms, Forms};
{_, []} -> {ok, forms, Forms}; {_, []} -> {ok, forms, Forms};
@ -65,11 +66,15 @@ abstract_module(Module, Data) ->
end. end.
%% @private Generate an abstract dispatch module. %% @private Generate an abstract dispatch module.
-spec abstract_module_(atom(), #module{}) -> [?erl:syntaxTree()].
-spec abstract_module_(atom(), #module{}, #state{}) -> [?erl:syntaxTree()].
abstract_module_(Module, #module{tables=Tables, abstract_module_(Module, #module{tables=Tables,
qtree=Tree, store=Store}=Data) ->
qtree=Tree, store=Store}=Data, Stats) ->
{_, ParamsTable} = lists:keyfind(params, 1, Tables), {_, ParamsTable} = lists:keyfind(params, 1, Tables),
{_, CountsTable} = lists:keyfind(counters, 1, Tables), {_, CountsTable} = lists:keyfind(counters, 1, Tables),
State = #state{ event=?erl:variable("Event"),
paramstab=ParamsTable,
countstab=CountsTable,
statistics=Stats},
AbstractMod = [ AbstractMod = [
%% -module(Module) %% -module(Module)
?erl:attribute(?erl:atom(module), [?erl:atom(Module)]), ?erl:attribute(?erl:atom(module), [?erl:atom(Module)]),
@ -111,14 +116,14 @@ abstract_module_(Module, #module{tables=Tables,
%% info(Name) -> Term. %% info(Name) -> Term.
?erl:function( ?erl:function(
?erl:atom(info), ?erl:atom(info),
abstract_info(Data) ++
abstract_info(Data, State) ++
[?erl:clause( [?erl:clause(
[?erl:underscore()], none, [?erl:underscore()], none,
[abstract_apply(erlang, error, [?erl:atom(badarg)])])]), [abstract_apply(erlang, error, [?erl:atom(badarg)])])]),
%% reset_counters(Name) -> boolean(). %% reset_counters(Name) -> boolean().
?erl:function( ?erl:function(
?erl:atom(reset_counters), ?erl:atom(reset_counters),
abstract_reset() ++
abstract_reset(State) ++
[?erl:clause( [?erl:clause(
[?erl:underscore()], none, [?erl:underscore()], none,
[abstract_apply(erlang, error, [?erl:atom(badarg)])])]), [abstract_apply(erlang, error, [?erl:atom(badarg)])])]),
@ -133,13 +138,13 @@ abstract_module_(Module, #module{tables=Tables,
?erl:function( ?erl:function(
?erl:atom(handle), ?erl:atom(handle),
[?erl:clause([?erl:variable("Event")], none, [?erl:clause([?erl:variable("Event")], none,
[abstract_count(input),
[abstract_count(input, State),
?erl:application(none, ?erl:application(none,
?erl:atom(handle_), [?erl:variable("Event")])])]), ?erl:atom(handle_), [?erl:variable("Event")])])]),
?erl:function( ?erl:function(
?erl:atom(runjob), ?erl:atom(runjob),
[?erl:clause([?erl:variable("Fun"), ?erl:variable("Event")], none, [?erl:clause([?erl:variable("Fun"), ?erl:variable("Event")], none,
[abstract_count(job_input),
[abstract_count(job_input, State),
?erl:application(none, ?erl:application(none,
?erl:atom(job_), [?erl:variable("Fun"), ?erl:atom(job_), [?erl:variable("Fun"),
?erl:variable("Event")])])]), ?erl:variable("Event")])])]),
@ -147,10 +152,7 @@ abstract_module_(Module, #module{tables=Tables,
?erl:function( ?erl:function(
?erl:atom(handle_), ?erl:atom(handle_),
[?erl:clause([?erl:variable("Event")], none, [?erl:clause([?erl:variable("Event")], none,
abstract_filter(Tree, Data, #state{
event=?erl:variable("Event"),
paramstab=ParamsTable,
countstab=CountsTable}))]),
abstract_filter(Tree, Data, State))]),
?erl:function( ?erl:function(
?erl:atom(job_), ?erl:atom(job_),
[?erl:clause([?erl:variable("Fun"), [?erl:clause([?erl:variable("Fun"),
@ -169,7 +171,7 @@ abstract_module_(Module, #module{tables=Tables,
)]), )]),
?erl:function( ?erl:function(
?erl:atom(job_result), ?erl:atom(job_result),
abstract_runjob(Data)
abstract_runjob(Data, State)
) )
], ],
%% Transform Term -> Key to Key -> Term %% Transform Term -> Key to Key -> Term
@ -219,7 +221,7 @@ abstract_get(#module{'query'=_Query, store=Store}) ->
|| {K, _} <- Store]. || {K, _} <- Store].
%% @private %% @private
abstract_runjob(#module{'query'=_Query, store=_Store}) ->
abstract_runjob(#module{'query'=_Query, store=_Store}, State) ->
Time = abstract_apply(erlang, '/', [?erl:variable("Time"), Time = abstract_apply(erlang, '/', [?erl:variable("Time"),
?erl:abstract(1000000)]), ?erl:abstract(1000000)]),
[?erl:clause([?erl:variable("JobResult"), [?erl:clause([?erl:variable("JobResult"),
@ -235,11 +237,11 @@ abstract_runjob(#module{'query'=_Query, store=_Store}) ->
?erl:clause( ?erl:clause(
[?erl:tuple([?erl:atom(error),?erl:variable("Reason")])], [?erl:tuple([?erl:atom(error),?erl:variable("Reason")])],
none, none,
[abstract_count(input), abstract_count(job_error),
[abstract_count(input, State), abstract_count(job_error, State),
?erl:application(none, ?erl:atom(handle_), ?erl:application(none, ?erl:atom(handle_),
abstract_job(Time, [?erl:tuple([?erl:atom(error), abstract_job(Time, [?erl:tuple([?erl:atom(error),
?erl:variable("Reason")])])), ?erl:variable("Reason")])])),
abstract_count(job_time, ?erl:variable("Time")),
abstract_count(job_time, State, ?erl:variable("Time")),
?erl:tuple([?erl:variable("Time"), ?erl:tuple([?erl:variable("Time"),
?erl:tuple([?erl:atom(error), ?erl:tuple([?erl:atom(error),
?erl:variable("Reason")])])]), ?erl:variable("Reason")])])]),
@ -247,10 +249,10 @@ abstract_runjob(#module{'query'=_Query, store=_Store}) ->
?erl:clause( ?erl:clause(
[?erl:variable("Result")], [?erl:variable("Result")],
none, none,
[abstract_count(input), abstract_count(job_run),
[abstract_count(input, State), abstract_count(job_run, State),
?erl:application(none, ?erl:atom(handle_), ?erl:application(none, ?erl:atom(handle_),
abstract_job(Time)), abstract_job(Time)),
abstract_count(job_time, ?erl:variable("Time")),
abstract_count(job_time, State, ?erl:variable("Time")),
?erl:tuple([?erl:variable("Time"), ?erl:tuple([?erl:variable("Time"),
?erl:variable("Result")])]) ?erl:variable("Result")])])
]) ])
@ -270,21 +272,34 @@ abstract_job(Time, Error) ->
?erl:abstract([list])])]. ?erl:abstract([list])])].
%% @private Return the clauses of the info/1 function. %% @private Return the clauses of the info/1 function.
abstract_info(#module{'query'=Query}) ->
abstract_info(#module{'query'=Query}, State) ->
[?erl:clause([?erl:abstract(K)], none, V) [?erl:clause([?erl:abstract(K)], none, V)
|| {K, V} <- [ || {K, V} <- [
{'query', abstract_query(Query)}, {'query', abstract_query(Query)},
{input, abstract_getcount(input)},
{filter, abstract_getcount(filter)},
{output, abstract_getcount(output)},
{job_input, abstract_getcount(job_input)},
{job_run, abstract_getcount(job_run)},
{job_time, abstract_getcount(job_time)},
{job_error, abstract_getcount(job_error)}
{input, abstract_getcount(input, State)},
{filter, abstract_getcount(filter, State)},
{output, abstract_getcount(output, State)},
{job_input, abstract_getcount(job_input, State)},
{job_run, abstract_getcount(job_run, State)},
{job_time, abstract_getcount(job_time, State)},
{job_error, abstract_getcount(job_error, State)}
]]. ]].
abstract_reset() ->
abstract_reset(#state{statistics=false}) ->
Reset = [?erl:abstract(0)],
[?erl:clause([?erl:abstract(K)], none, V)
|| {K, V} <- [
{all, Reset},
{input, Reset},
{filter, Reset},
{output, Reset},
{job_input, Reset},
{job_run, Reset},
{job_time, Reset},
{job_error, Reset}
]];
abstract_reset(#state{statistics=true}) ->
[?erl:clause([?erl:abstract(K)], none, V) [?erl:clause([?erl:abstract(K)], none, V)
|| {K, V} <- [ || {K, V} <- [
{all, abstract_resetcount([input, filter, output, {all, abstract_resetcount([input, filter, output,
@ -301,18 +316,17 @@ abstract_reset() ->
%% @private Return a list of expressions to apply a filter. %% @private Return a list of expressions to apply a filter.
%% @todo Allow mulitple functions to be specified using `with/2'.
-spec abstract_filter(glc_ops:op() | [glc_ops:op()], #module{}, #state{}) -> [syntaxTree()]. -spec abstract_filter(glc_ops:op() | [glc_ops:op()], #module{}, #state{}) -> [syntaxTree()].
abstract_filter({Type, [{with, _Cond, _Fun}|_] = I}, Data, State) when Type =:= all; Type =:= any -> abstract_filter({Type, [{with, _Cond, _Fun}|_] = I}, Data, State) when Type =:= all; Type =:= any ->
Cond = glc_lib:reduce(glc:Type([Q || {with, Q, _} <- I])), Cond = glc_lib:reduce(glc:Type([Q || {with, Q, _} <- I])),
abstract_filter_(Cond, abstract_filter_(Cond,
_OnMatch=fun(State2) -> _OnMatch=fun(State2) ->
Funs = [ F || {with, _, F} <- I ], Funs = [ F || {with, _, F} <- I ],
[abstract_count(output)] ++
[abstract_count(output, State)] ++
abstract_with(Funs, Data, State2) end, abstract_with(Funs, Data, State2) end,
_OnNomatch=fun(_State2) -> [abstract_count(filter)] end, State);
_OnNomatch=fun(_State2) -> [abstract_count(filter, State)] end, State);
abstract_filter([{with, _Cond, _Fun}|_] = I, Data, State) -> abstract_filter([{with, _Cond, _Fun}|_] = I, Data, State) ->
OnNomatch = fun(_State2) -> [abstract_count(filter, 0)] end,
OnNomatch = fun(_State2) -> [abstract_count(filter, State, 0)] end,
Funs = lists:foldr(fun({with, Cond, Fun}, Acc) -> Funs = lists:foldr(fun({with, Cond, Fun}, Acc) ->
[{Cond, Fun, Data}|Acc] [{Cond, Fun, Data}|Acc]
end, [], I), end, [], I),
@ -320,13 +334,13 @@ abstract_filter([{with, _Cond, _Fun}|_] = I, Data, State) ->
abstract_filter({with, Cond, Fun}, Data, State) -> abstract_filter({with, Cond, Fun}, Data, State) ->
abstract_filter_(Cond, abstract_filter_(Cond,
_OnMatch=fun(State2) -> _OnMatch=fun(State2) ->
[abstract_count(output)] ++
[abstract_count(output, State)] ++
abstract_with(Fun, Data, State2) end, abstract_with(Fun, Data, State2) end,
_OnNomatch=fun(_State2) -> [abstract_count(filter)] end, State);
_OnNomatch=fun(_State2) -> [abstract_count(filter, State)] end, State);
abstract_filter(Cond, _Data, State) -> abstract_filter(Cond, _Data, State) ->
abstract_filter_(Cond, abstract_filter_(Cond,
_OnMatch=fun(_State2) -> [abstract_count(output)] end,
_OnNomatch=fun(_State2) -> [abstract_count(filter)] end, State).
_OnMatch=fun(_State2) -> [abstract_count(output, State)] end,
_OnNomatch=fun(_State2) -> [abstract_count(filter, State)] end, State).
%% @private Return a list of expressions to apply a filter. %% @private Return a list of expressions to apply a filter.
%% A filter expects two continuation functions which generates the expressions %% A filter expects two continuation functions which generates the expressions
@ -422,13 +436,13 @@ abstract_with(Fun, Data, State) when is_function(Fun, 1); is_function(Fun, 2) -
end, State). end, State).
abstract_within([{H, Fun, Data}|T], OnNomatch, State) -> abstract_within([{H, Fun, Data}|T], OnNomatch, State) ->
OnMatch = fun(State2) -> [abstract_count(output)] ++
OnMatch = fun(State2) -> [abstract_count(output, State)] ++
abstract_with(Fun, Data, State2) abstract_with(Fun, Data, State2)
++ abstract_within(T, OnNomatch, State2) ++ abstract_within(T, OnNomatch, State2)
end, end,
abstract_filter_(H, OnMatch, abstract_filter_(H, OnMatch,
_OnNomatch=fun(State2) -> _OnNomatch=fun(State2) ->
[abstract_count(filter)] ++
[abstract_count(filter, State)] ++
abstract_within(T, OnNomatch, State2) abstract_within(T, OnNomatch, State2)
end, State); end, State);
abstract_within([], OnNomatch, State) -> abstract_within([], OnNomatch, State) ->
@ -485,7 +499,8 @@ abstract_getparam([_|_]=Terms, OnBound, #state{paramvars=_Params, fields=_Fields
when is_list(Terms) -> when is_list(Terms) ->
{Keys, Bound} = lists:foldl(fun(Term, {Acc0, #state{paramvars=Params, {Keys, Bound} = lists:foldl(fun(Term, {Acc0, #state{paramvars=Params,
paramstab=ParamsTable}=State0}) ->
paramstab=ParamsTable,
statistics=_Stats}=State0}) ->
case lists:keyfind(Term, 1, Params) of case lists:keyfind(Term, 1, Params) of
{_, _Variable} -> {_, _Variable} ->
{Acc0, State0}; {Acc0, State0};
@ -557,28 +572,32 @@ param_variable(Key) ->
%% @private Return an expression to increment a counter. %% @private Return an expression to increment a counter.
%% @todo Pass state record. Only Generate code if `statistics' is enabled. %% @todo Pass state record. Only Generate code if `statistics' is enabled.
-spec abstract_count(atom()) -> syntaxTree().
abstract_count(Counter) ->
abstract_count(Counter, 1).
abstract_count(Counter, Value) when is_integer(Value) ->
-spec abstract_count(atom(), #state{}) -> syntaxTree().
abstract_count(Counter, State) ->
abstract_count(Counter, State, 1).
abstract_count(_Counter, #state{statistics=false}, Value) when is_integer(Value) ->
?erl:abstract(Value);
abstract_count(_Counter, #state{statistics=false}, Value) ->
?erl:abstract(Value);
abstract_count(Counter, #state{statistics=true}, Value) when is_integer(Value) ->
abstract_apply(gr_counter, update_counter, abstract_apply(gr_counter, update_counter,
[abstract_apply(table, [?erl:atom(counters)]), [abstract_apply(table, [?erl:atom(counters)]),
?erl:abstract(Counter),
?erl:abstract({2,Value})]);
abstract_count(Counter, Value) ->
?erl:abstract(Counter),
?erl:abstract({2,Value})]);
abstract_count(Counter, #state{statistics=true}, Value) ->
abstract_apply(gr_counter, update_counter, abstract_apply(gr_counter, update_counter,
[abstract_apply(table, [?erl:atom(counters)]), [abstract_apply(table, [?erl:atom(counters)]),
?erl:abstract(Counter), ?erl:abstract(Counter),
?erl:tuple([?erl:abstract(2), Value])
]).
?erl:tuple([?erl:abstract(2), Value])]).
%% @private Return an expression to get the value of a counter. %% @private Return an expression to get the value of a counter.
%% @todo Pass state record. Only Generate code if `statistics' is enabled. %% @todo Pass state record. Only Generate code if `statistics' is enabled.
-spec abstract_getcount(atom()) -> [syntaxTree()].
abstract_getcount(Counter) when is_atom(Counter) ->
abstract_getcount(?erl:abstract(Counter));
abstract_getcount(Counter) ->
-spec abstract_getcount(atom(), #state{}) -> [syntaxTree()].
abstract_getcount(Counter, State) when is_atom(Counter) ->
abstract_getcount(?erl:abstract(Counter), State);
abstract_getcount(_Counter, #state{statistics = false}) -> [?erl:abstract(0)];
abstract_getcount(Counter, #state{statistics = true}) ->
[abstract_apply(gr_counter, lookup_element, [abstract_apply(gr_counter, lookup_element,
[abstract_apply(table, [?erl:atom(counters)]), Counter])]. [abstract_apply(table, [?erl:atom(counters)]), Counter])].

Loading…
Cancel
Save