您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

657 行
24 KiB

  1. %% Copyright (c) 2012, Magnus Klaar <klaar@ninenines.eu>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. %% @doc Event filter implementation.
  15. %%
  16. %% An event query is constructed using the built in operators exported from
  17. %% this module. The filtering operators are used to specify which events
  18. %% should be included in the output of the query. The default output action
  19. %% is to copy all events matching the input filters associated with a query
  20. %% to the output. This makes it possible to construct and compose multiple
  21. %% queries at runtime.
  22. %%
  23. %% === Examples of built in filters ===
  24. %% ```
  25. %% %% Select all events where 'a' exists and is greater than 0.
  26. %% glc:gt(a, 0).
  27. %% %% Select all events where 'a' exists and is equal to 0.
  28. %% glc:eq(a, 0).
  29. %% %% Select all events where 'a' exists and is less than 0.
  30. %% glc:lt(a, 0).
  31. %%
  32. %% %% Select no input events. Used as black hole query.
  33. %% glc:null(false).
  34. %% %% Select all input events. Used as passthrough query.
  35. %% glc:null(true).
  36. %% '''
  37. %%
  38. %% === Examples of combining filters ===
  39. %% ```
  40. %% %% Select all events where both 'a' and 'b' exists and are greater than 0.
  41. %% glc:all([glc:gt(a, 0), glc:gt(b, 0)]).
  42. %% %% Select all events where 'a' or 'b' exists and are greater than 0.
  43. %% glc:any([glc:get(a, 0), glc:gt(b, 0)]).
  44. %% '''
  45. %%
  46. %% === Handling output events ===
  47. %%
  48. %% Once a query has been composed it is possible to override the output action
  49. %% with an erlang function. The function will be applied to each output event
  50. %% from the query. The return value from the function will be ignored.
  51. %%
  52. %% ```
  53. %% %% Write all input events as info reports to the error logger.
  54. %% glc:with(glc:null(true), fun(E) ->
  55. %% error_logger:info_report(gre:pairs(E)) end).
  56. %% '''
  57. %%
  58. -module(glc).
  59. -export([
  60. compile/2,
  61. handle/2,
  62. delete/1
  63. ]).
  64. -export([
  65. lt/2,
  66. eq/2,
  67. gt/2
  68. ]).
  69. -export([
  70. all/1,
  71. any/1,
  72. null/1,
  73. with/2
  74. ]).
  75. -record(module, {
  76. 'query' :: term(),
  77. tables :: [{atom(), ets:tid()}],
  78. qtree :: term()
  79. }).
  80. -type syntaxTree() :: erl_syntax:syntaxTree().
  81. -record(state, {
  82. event = undefined :: syntaxTree(),
  83. fields = [] :: [{atom(), syntaxTree()}],
  84. fieldc = 0 :: non_neg_integer(),
  85. paramvars = [] :: [{term(), syntaxTree()}],
  86. paramstab = undefined :: ets:tid()
  87. }).
  88. -type nextFun() :: fun((#state{}) -> [syntaxTree()]).
  89. -type q() :: tuple().
  90. -spec lt(atom(), term()) -> q().
  91. lt(Key, Term) when is_atom(Key) -> {Key, '<', Term}.
  92. -spec eq(atom(), term()) -> q().
  93. eq(Key, Term) when is_atom(Key) -> {Key, '=', Term}.
  94. -spec gt(atom(), term()) -> q().
  95. gt(Key, Term) when is_atom(Key) -> {Key, '>', Term}.
  96. %% @doc Apply multiple conditions to the input.
  97. %% For an event to be considered valid output the condition of all filters
  98. %% specified in the input must hold for the input event.
  99. -spec all([q()]) -> q().
  100. all(Conds) when is_list(Conds) ->
  101. {all, Conds}.
  102. %% @doc Apply one of multiple conditions to the input.
  103. -spec any([q()]) -> q().
  104. any(Conds) when is_list(Conds) ->
  105. {any, Conds}.
  106. %% @doc Always return `true' or `false'.
  107. -spec null(boolean()) -> q().
  108. null(Result) when is_boolean(Result) ->
  109. {null, Result}.
  110. %% @doc Apply a function to each output.
  111. -spec with(q(), fun((gre:event()) -> term())) -> q().
  112. with(Query, Fun) when is_function(Fun, 1) ->
  113. {with, Query, Fun}.
  114. %% @doc Compile a query to a module.
  115. %%
  116. %% On success the module representing the query is returned. The module and
  117. %% data associated with the query must be released using the {@link delete/1}
  118. %% function. The name of the query module is expected to be unique.
  119. -spec compile(atom(), list()) -> {ok, atom()}.
  120. compile(Module, Query) ->
  121. {ok, ModuleData} = module_data(Query),
  122. {ok, forms, Forms} = abstract_module(Module, ModuleData),
  123. {ok, Module, Binary} = compile_forms(Forms, []),
  124. {ok, loaded, Module} = load_binary(Module, Binary),
  125. {ok, Module}.
  126. %% @doc Handle an event using a compiled query.
  127. %%
  128. %% The input event is expected to have been returned from {@link gre:make/2}.
  129. -spec handle(atom(), gre:event()) -> ok.
  130. handle(Module, Event) ->
  131. Module:handle(Event).
  132. %% @doc Release a compiled query.
  133. %%
  134. %% This releases all resources allocated by a compiled query. The query name
  135. %% is expected to be associated with an existing query module. Calling this
  136. %% function will result in a runtime error.
  137. -spec delete(atom()) -> ok.
  138. delete(_Module) ->
  139. ok.
  140. %% @private Map a query to a module data term.
  141. -spec module_data(term()) -> {ok, #module{}}.
  142. module_data(Query) ->
  143. %% terms in the query which are not valid arguments to the
  144. %% erl_syntax:abstract/1 functions are stored in ETS.
  145. %% the terms are only looked up once they are necessary to
  146. %% continue evaluation of the query.
  147. Params = ets:new(params, [set,protected]),
  148. %% query counters are stored in a shared ETS table. this should
  149. %% be an optional feature. enable by defaults to simplify tests.
  150. Counters = ets:new(counters, [set,public]),
  151. ets:insert(Counters, [{input,0}, {filter,0}, {output,0}]),
  152. %% the abstract_tables/1 function expects a list of name-tid pairs.
  153. %% tables are referred to by name in the generated code. the table/1
  154. %% function maps names to tids.
  155. Tables = [{params,Params}, {counters,Counters}],
  156. Tree = query_tree(Query),
  157. {ok, #module{'query'=Query, tables=Tables, qtree=Tree}}.
  158. %% @private Map a query to a simplified query tree term.
  159. %%
  160. %% The simplified query tree is used to combine multiple queries into one
  161. %% query module. The goal of this is to reduce the filtering and dispatch
  162. %% overhead when multiple concurrent queries are executed.
  163. %%
  164. %% A fixed selection condition may be used to specify a property that an event
  165. %% must have in order to be considered part of the input stream for a query.
  166. %%
  167. %% For the sake of simplicity it is only possible to define selection
  168. %% conditions using the fields present in the context and identifiers
  169. %% of an event. The fields in the context are bound to the reserved
  170. %% names:
  171. %%
  172. %% - '$n': node name
  173. %% - '$a': application name
  174. %% - '$p': process identifier
  175. %% - '$t': timestamp
  176. %%
  177. %%
  178. %% If an event must be selected based on the runtime state of an event handler
  179. %% this must be done in the body of the handler.
  180. -type qcond() ::
  181. {atom(), '<', term()} |
  182. {atom(), '=', term()} |
  183. {atom(), '>', term()} |
  184. {any, [qcond()]} |
  185. {all, [qcond()]}.
  186. -type qbody() :: tuple().
  187. -type qtree() :: [{qcond(), qbody()}].
  188. -spec query_tree(term()) -> qtree().
  189. query_tree(Query) ->
  190. Query.
  191. %% abstract code geneation functions
  192. %% @private Generate an abstract dispatch module.
  193. -spec abstract_module(atom(), #module{}) -> {ok, forms, list()}.
  194. abstract_module(Module, Data) ->
  195. Forms = [erl_syntax:revert(E) || E <- abstract_module_(Module, Data)],
  196. case lists:keyfind(errors, 1, erl_syntax_lib:analyze_forms(Forms)) of
  197. false -> {ok, forms, Forms};
  198. {_, []} -> {ok, forms, Forms};
  199. {_, [_|_]}=Errors -> Errors
  200. end.
  201. %% @private Generate an abstract dispatch module.
  202. -spec abstract_module_(atom(), #module{}) -> [erl_syntax:syntaxTree()].
  203. abstract_module_(Module, #module{tables=Tables, qtree=Tree}=Data) ->
  204. {_, ParamsTable} = lists:keyfind(params, 1, Tables),
  205. AbstractMod = [
  206. %% -module(Module)
  207. erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
  208. %% -export([
  209. erl_syntax:attribute(
  210. erl_syntax:atom(export),
  211. [erl_syntax:list([
  212. %% info/1
  213. erl_syntax:arity_qualifier(
  214. erl_syntax:atom(info),
  215. erl_syntax:integer(1)),
  216. %% table/1
  217. erl_syntax:arity_qualifier(
  218. erl_syntax:atom(table),
  219. erl_syntax:integer(1)),
  220. %% handle/1
  221. erl_syntax:arity_qualifier(
  222. erl_syntax:atom(handle),
  223. erl_syntax:integer(1))])]),
  224. %% ]).
  225. %% info(Name) -> Term.
  226. erl_syntax:function(
  227. erl_syntax:atom(info),
  228. abstract_info(Data) ++
  229. [erl_syntax:clause(
  230. [erl_syntax:underscore()], none,
  231. [erl_syntax:application(
  232. erl_syntax:atom(erlang),
  233. erl_syntax:atom(error),
  234. [erl_syntax:atom(badarg)])])]),
  235. %% table(Name) -> ets:tid().
  236. erl_syntax:function(
  237. erl_syntax:atom(table),
  238. abstract_tables(Tables) ++
  239. [erl_syntax:clause(
  240. [erl_syntax:underscore()], none,
  241. [erl_syntax:application(
  242. erl_syntax:atom(erlang),
  243. erl_syntax:atom(error),
  244. [erl_syntax:atom(badarg)])])]),
  245. %% handle(Event) - entry function
  246. erl_syntax:function(
  247. erl_syntax:atom(handle),
  248. [erl_syntax:clause([erl_syntax:variable("Event")], none,
  249. [abstract_count(input),
  250. erl_syntax:application(none,
  251. erl_syntax:atom(handle_), [erl_syntax:variable("Event")])])]),
  252. %% input_(Node, App, Pid, Tags, Values) - filter roots
  253. erl_syntax:function(
  254. erl_syntax:atom(handle_),
  255. [erl_syntax:clause([erl_syntax:variable("Event")], none,
  256. abstract_filter(Tree, #state{
  257. event=erl_syntax:variable("Event"),
  258. paramstab=ParamsTable}))])
  259. ],
  260. %% Transform Term -> Key to Key -> Term
  261. ParamsList = [{K, V} || {V, K} <- ets:tab2list(ParamsTable)],
  262. ets:delete_all_objects(ParamsTable),
  263. ets:insert(ParamsTable, ParamsList),
  264. AbstractMod.
  265. %% @private Return the clauses of the table/1 function.
  266. abstract_tables(Tables) ->
  267. [erl_syntax:clause(
  268. [erl_syntax:abstract(K)], none,
  269. [erl_syntax:abstract(V)])
  270. || {K, V} <- Tables].
  271. %% @private Return the clauses of the info/1 function.
  272. abstract_info(#module{'query'=Query}) ->
  273. [erl_syntax:clause([erl_syntax:abstract(K)], none, V)
  274. || {K, V} <- [
  275. {'query', abstract_query(Query)},
  276. {input, abstract_getcount(input)},
  277. {filter, abstract_getcount(filter)},
  278. {output, abstract_getcount(output)}
  279. ]].
  280. %% @private Return the original query as an expression.
  281. abstract_query({with, _, _}) ->
  282. [erl_syntax:abstract([])];
  283. abstract_query(Query) ->
  284. [erl_syntax:abstract(Query)].
  285. %% @private Return a list of expressions to apply a filter.
  286. %% @todo Allow mulitple functions to be specified using `with/2'.
  287. -spec abstract_filter(q(), #state{}) -> [syntaxTree()].
  288. abstract_filter({with, Cond, Fun}, State) ->
  289. abstract_filter_(Cond,
  290. _OnMatch=fun(State2) ->
  291. [abstract_count(output)] ++ abstract_with(Fun, State2) end,
  292. _OnNomatch=fun(_State2) -> [abstract_count(filter)] end, State);
  293. abstract_filter(Cond, State) ->
  294. abstract_filter_(Cond,
  295. _OnMatch=fun(_State2) -> [abstract_count(output)] end,
  296. _OnNomatch=fun(_State2) -> [abstract_count(filter)] end, State).
  297. %% @private Return a list of expressions to apply a filter.
  298. %% A filter expects two continuation functions which generates the expressions
  299. %% to apply when the filter matches or fails to match. The state passed to the
  300. %% functions will be contain all variable bindings to previously accessed
  301. %% fields and parameters.
  302. -spec abstract_filter_(qcond(), nextFun(), nextFun(), #state{}) ->
  303. syntaxTree().
  304. abstract_filter_({null, true}, OnMatch, _OnNomatch, State) ->
  305. OnMatch(State);
  306. abstract_filter_({null, false}, _OnMatch, OnNomatch, State) ->
  307. OnNomatch(State);
  308. abstract_filter_({Key, Op, Value}, OnMatch, OnNomatch, State)
  309. when Op =:= '>'; Op =:= '='; Op =:= '<' ->
  310. Op2 = case Op of '=' -> '=:='; Op -> Op end,
  311. abstract_opfilter(Key, Op2, Value, OnMatch, OnNomatch, State);
  312. abstract_filter_({'any', Conds}, OnMatch, OnNomatch, State) ->
  313. abstract_any(Conds, OnMatch, OnNomatch, State);
  314. abstract_filter_({'all', Conds}, OnMatch, OnNomatch, State) ->
  315. abstract_all(Conds, OnMatch, OnNomatch, State).
  316. %% @private Return a branch based on a built in operator.
  317. -spec abstract_opfilter(atom(), atom(), term(), nextFun(),
  318. nextFun(), #state{}) -> [syntaxTree()].
  319. abstract_opfilter(Key, Opname, Value, OnMatch, OnNomatch, State) ->
  320. abstract_getkey(Key,
  321. _OnMatch=fun(#state{fields=Fields}=State2) ->
  322. {_, Field} = lists:keyfind(Key, 1, Fields),
  323. [erl_syntax:case_expr(
  324. erl_syntax:application(
  325. erl_syntax:atom(erlang), erl_syntax:atom(Opname),
  326. [Field, erl_syntax:abstract(Value)]),
  327. [erl_syntax:clause([erl_syntax:atom(true)], none,
  328. OnMatch(State2)),
  329. erl_syntax:clause([erl_syntax:atom(false)], none,
  330. OnNomatch(State2))])] end,
  331. _OnNomatch=fun(State2) -> OnNomatch(State2) end, State).
  332. %% @private Generate an `all' filter.
  333. %% An `all' filter is evaluated by testing all conditions that must hold. If
  334. %% any of the conditions does not hold the evaluation is short circuted at that
  335. %% point. This means that the `OnNomatch' branch is executed once for each
  336. %% condition. The `OnMatch' branch is only executed once.
  337. -spec abstract_all([qcond()], nextFun(), nextFun(), #state{}) ->
  338. [syntaxTree()].
  339. abstract_all([H|T], OnMatch, OnNomatch, State) ->
  340. abstract_filter_(H,
  341. _OnMatch=fun(State2) -> abstract_all(T, OnMatch, OnNomatch, State2)
  342. end, OnNomatch, State);
  343. abstract_all([], OnMatch, _OnNomatch, State) ->
  344. OnMatch(State).
  345. %% @private
  346. -spec abstract_any([qcond()], nextFun(), nextFun(), #state{}) ->
  347. [syntaxTree()].
  348. abstract_any([H|T], OnMatch, OnNomatch, State) ->
  349. abstract_filter_(H, OnMatch,
  350. _OnNomatch=fun(State2) -> abstract_any(T, OnMatch, OnNomatch, State2)
  351. end, State);
  352. abstract_any([], _OnMatch, OnNomatch, State) ->
  353. OnNomatch(State).
  354. %% @private
  355. -spec abstract_with(fun((gre:event()) -> term()), #state{}) -> [syntaxTree()].
  356. abstract_with(Fun, State) when is_function(Fun, 1) ->
  357. abstract_getparam(Fun, fun(#state{event=Event, paramvars=Params}) ->
  358. {_, Fun2} = lists:keyfind(Fun, 1, Params),
  359. [erl_syntax:application(none, Fun2, [Event])]
  360. end, State).
  361. %% @private Bind the value of a field to a variable.
  362. %% If the value of a field has already been bound to a variable the previous
  363. %% binding is reused over re-accessing the value. The `OnMatch' function is
  364. %% expected to access the variable stored in the state record. The `OnNomatch'
  365. %% function must not attempt to access the variable.
  366. -spec abstract_getkey(atom(), nextFun(), nextFun(), #state{}) ->
  367. [syntaxTree()].
  368. abstract_getkey(Key, OnMatch, OnNomatch, #state{fields=Fields}=State) ->
  369. case lists:keyfind(Key, 1, Fields) of
  370. {Key, _Variable} -> OnMatch(State);
  371. false -> abstract_getkey_(Key, OnMatch, OnNomatch, State)
  372. end.
  373. -spec abstract_getkey_(atom(), nextFun(), nextFun(), #state{}) ->
  374. [syntaxTree()].
  375. abstract_getkey_(Key, OnMatch, OnNomatch, #state{
  376. event=Event, fields=Fields}=State) ->
  377. [erl_syntax:case_expr(
  378. erl_syntax:application(
  379. erl_syntax:atom(gre), erl_syntax:atom(find),
  380. [erl_syntax:atom(Key), Event]),
  381. [erl_syntax:clause([
  382. erl_syntax:tuple([
  383. erl_syntax:atom(true),
  384. field_variable(Key)])], none,
  385. OnMatch(State#state{
  386. fields=[{Key, field_variable(Key)}|Fields]})),
  387. erl_syntax:clause([
  388. erl_syntax:atom(false)], none,
  389. OnNomatch(State))
  390. ]
  391. )].
  392. %% @private Bind the value of a parameter to a variable.
  393. %% During code generation the parameter value is used as the identity of the
  394. %% parameter. At runtime a unique integer is used as the identity.
  395. -spec abstract_getparam(term(), nextFun(), #state{}) -> [syntaxTree()].
  396. abstract_getparam(Term, OnBound, #state{paramvars=Params}=State) ->
  397. case lists:keyfind(Term, 1, Params) of
  398. {_, _Variable} -> OnBound(State);
  399. %% parameter not bound to variable in this scope.
  400. false -> abstract_getparam_(Term, OnBound, State)
  401. end.
  402. -spec abstract_getparam_(term(), nextFun(), #state{}) -> [syntaxTree()].
  403. abstract_getparam_(Term, OnBound, #state{paramstab=Table,
  404. paramvars=Params}=State) ->
  405. Key = case ets:lookup(Table, Term) of
  406. [{_, Key2}] ->
  407. Key2;
  408. [] ->
  409. Key2 = ets:info(Table, size),
  410. ets:insert(Table, {Term, Key2}),
  411. Key2
  412. end,
  413. [erl_syntax:match_expr(
  414. param_variable(Key),
  415. erl_syntax:application(
  416. erl_syntax:atom(ets),
  417. erl_syntax:atom(lookup_element),
  418. [erl_syntax:application(
  419. erl_syntax:atom(table),
  420. [erl_syntax:atom(params)]),
  421. erl_syntax:abstract(Key),
  422. erl_syntax:abstract(2)]))
  423. ] ++ OnBound(State#state{paramvars=[{Term, param_variable(Key)}|Params]}).
  424. %% @private Generate a variable name for the value of a field.
  425. %% @todo Encode non-alphanumeric characters as integer values.
  426. -spec field_variable(atom()) -> syntaxTree().
  427. field_variable(Key) ->
  428. erl_syntax:variable("Field_" ++ atom_to_list(Key)).
  429. %% @private Generate a variable name for the value of a parameter.
  430. -spec param_variable(integer()) -> syntaxTree().
  431. param_variable(Key) ->
  432. erl_syntax:variable("Param_" ++ integer_to_list(Key)).
  433. %% @private Return an expression to increment a counter.
  434. %% @todo Pass state record. Only Generate code if `statistics' is enabled.
  435. -spec abstract_count(atom()) -> syntaxTree().
  436. abstract_count(Counter) ->
  437. erl_syntax:application(
  438. erl_syntax:atom(ets),
  439. erl_syntax:atom(update_counter),
  440. [erl_syntax:application(
  441. erl_syntax:atom(table),
  442. [erl_syntax:atom(counters)]),
  443. erl_syntax:abstract(Counter),
  444. erl_syntax:abstract({2,1})]).
  445. %% @private Return an expression to get the value of a counter.
  446. %% @todo Pass state record. Only Generate code if `statistics' is enabled.
  447. -spec abstract_getcount(atom()) -> [syntaxTree()].
  448. abstract_getcount(Counter) ->
  449. [erl_syntax:application(
  450. erl_syntax:atom(ets),
  451. erl_syntax:atom(lookup_element),
  452. [erl_syntax:application(
  453. erl_syntax:atom(table),
  454. [erl_syntax:atom(counters)]),
  455. erl_syntax:abstract(Counter),
  456. erl_syntax:abstract(2)])].
  457. %% abstract code util functions
  458. %% @private Compile an abstract module.
  459. -spec compile_forms(term(), [term()]) -> {ok, atom(), binary()}.
  460. compile_forms(Forms, _Opts) ->
  461. case compile:forms(Forms) of
  462. {ok, Module, Binary} ->
  463. {ok, Module, Binary};
  464. {ok, Module, Binary, _Warnings} ->
  465. {ok, Module, Binary};
  466. Error ->
  467. erlang:error({compile_forms, Error})
  468. end.
  469. %% @private Load a module binary.
  470. -spec load_binary(atom(), binary()) -> {ok, loaded, atom()}.
  471. load_binary(Module, Binary) ->
  472. case code:load_binary(Module, "", Binary) of
  473. {module, Module} -> {ok, loaded, Module};
  474. {error, Reason} -> exit({error_loading_module, Module, Reason})
  475. end.
  476. -ifdef(TEST).
  477. -include_lib("eunit/include/eunit.hrl").
  478. setup_query(Module, Query) ->
  479. ?assertNot(erlang:module_loaded(Module)),
  480. ?assertEqual({ok, Module}, case (catch compile(Module, Query)) of
  481. {'EXIT',_}=Error -> ?debugFmt("~p", [Error]), Error; Else -> Else end),
  482. ?assert(erlang:function_exported(Module, table, 1)),
  483. ?assert(erlang:function_exported(Module, handle, 1)),
  484. {compiled, Module}.
  485. nullquery_compiles_test() ->
  486. {compiled, Mod} = setup_query(testmod1, glc:null(false)),
  487. ?assertError(badarg, Mod:table(noexists)).
  488. params_table_exists_test() ->
  489. {compiled, Mod} = setup_query(testmod2, glc:null(false)),
  490. ?assert(is_integer(Mod:table(params))),
  491. ?assertMatch([_|_], ets:info(Mod:table(params))).
  492. nullquery_exists_test() ->
  493. {compiled, Mod} = setup_query(testmod3, glc:null(false)),
  494. ?assert(erlang:function_exported(Mod, info, 1)),
  495. ?assertError(badarg, Mod:info(invalid)),
  496. ?assertEqual({null, false}, Mod:info('query')).
  497. init_counters_test() ->
  498. {compiled, Mod} = setup_query(testmod4, glc:null(false)),
  499. ?assertEqual(0, Mod:info(input)),
  500. ?assertEqual(0, Mod:info(filter)),
  501. ?assertEqual(0, Mod:info(output)).
  502. filtered_event_test() ->
  503. %% If no selection condition is specified no inputs can match.
  504. {compiled, Mod} = setup_query(testmod5, glc:null(false)),
  505. glc:handle(Mod, gre:make([], [list])),
  506. ?assertEqual(1, Mod:info(input)),
  507. ?assertEqual(1, Mod:info(filter)),
  508. ?assertEqual(0, Mod:info(output)).
  509. nomatch_event_test() ->
  510. %% If a selection condition but no body is specified the event
  511. %% is expected to count as filtered out if the condition does
  512. %% not hold.
  513. {compiled, Mod} = setup_query(testmod6, glc:eq('$n', 'noexists@nohost')),
  514. glc:handle(Mod, gre:make([{'$n', 'noexists2@nohost'}], [list])),
  515. ?assertEqual(1, Mod:info(input)),
  516. ?assertEqual(1, Mod:info(filter)),
  517. ?assertEqual(0, Mod:info(output)).
  518. opfilter_eq_test() ->
  519. %% If a selection condition but no body is specified the event
  520. %% counts as input to the query, but not as filtered out.
  521. {compiled, Mod} = setup_query(testmod7, glc:eq('$n', 'noexists@nohost')),
  522. glc:handle(Mod, gre:make([{'$n', 'noexists@nohost'}], [list])),
  523. ?assertEqual(1, Mod:info(input)),
  524. ?assertEqual(0, Mod:info(filter)),
  525. ?assertEqual(1, Mod:info(output)),
  526. done.
  527. opfilter_gt_test() ->
  528. {compiled, Mod} = setup_query(testmod8, glc:gt(a, 1)),
  529. glc:handle(Mod, gre:make([{'a', 2}], [list])),
  530. ?assertEqual(1, Mod:info(input)),
  531. ?assertEqual(0, Mod:info(filter)),
  532. glc:handle(Mod, gre:make([{'a', 0}], [list])),
  533. ?assertEqual(2, Mod:info(input)),
  534. ?assertEqual(1, Mod:info(filter)),
  535. ?assertEqual(1, Mod:info(output)),
  536. done.
  537. opfilter_lt_test() ->
  538. {compiled, Mod} = setup_query(testmod9, glc:lt(a, 1)),
  539. glc:handle(Mod, gre:make([{'a', 0}], [list])),
  540. ?assertEqual(1, Mod:info(input)),
  541. ?assertEqual(0, Mod:info(filter)),
  542. ?assertEqual(1, Mod:info(output)),
  543. glc:handle(Mod, gre:make([{'a', 2}], [list])),
  544. ?assertEqual(2, Mod:info(input)),
  545. ?assertEqual(1, Mod:info(filter)),
  546. ?assertEqual(1, Mod:info(output)),
  547. done.
  548. allholds_op_test() ->
  549. {compiled, Mod} = setup_query(testmod10,
  550. glc:all([glc:eq(a, 1), glc:eq(b, 2)])),
  551. glc:handle(Mod, gre:make([{'a', 1}], [list])),
  552. glc:handle(Mod, gre:make([{'a', 2}], [list])),
  553. ?assertEqual(2, Mod:info(input)),
  554. ?assertEqual(2, Mod:info(filter)),
  555. glc:handle(Mod, gre:make([{'b', 1}], [list])),
  556. glc:handle(Mod, gre:make([{'b', 2}], [list])),
  557. ?assertEqual(4, Mod:info(input)),
  558. ?assertEqual(4, Mod:info(filter)),
  559. glc:handle(Mod, gre:make([{'a', 1},{'b', 2}], [list])),
  560. ?assertEqual(5, Mod:info(input)),
  561. ?assertEqual(4, Mod:info(filter)),
  562. ?assertEqual(1, Mod:info(output)),
  563. done.
  564. anyholds_op_test() ->
  565. {compiled, Mod} = setup_query(testmod11,
  566. glc:any([glc:eq(a, 1), glc:eq(b, 2)])),
  567. glc:handle(Mod, gre:make([{'a', 2}], [list])),
  568. glc:handle(Mod, gre:make([{'b', 1}], [list])),
  569. ?assertEqual(2, Mod:info(input)),
  570. ?assertEqual(2, Mod:info(filter)),
  571. glc:handle(Mod, gre:make([{'a', 1}], [list])),
  572. glc:handle(Mod, gre:make([{'b', 2}], [list])),
  573. ?assertEqual(4, Mod:info(input)),
  574. ?assertEqual(2, Mod:info(filter)),
  575. done.
  576. with_function_test() ->
  577. Self = self(),
  578. {compiled, Mod} = setup_query(testmod12,
  579. glc:with(glc:eq(a, 1), fun(Event) -> Self ! gre:fetch(a, Event) end)),
  580. glc:handle(Mod, gre:make([{a,1}], [list])),
  581. ?assertEqual(1, Mod:info(output)),
  582. ?assertEqual(1, receive Msg -> Msg after 0 -> notcalled end),
  583. done.
  584. -endif.