From 088a0957175a25c430b9939744f6392c04babd3d Mon Sep 17 00:00:00 2001 From: Pedram Nimreezi Date: Thu, 23 Jan 2014 19:18:14 -0500 Subject: [PATCH] Add support for event fields that are notfound, and begin some documentation --- Makefile | 2 +- README.md | 0 README.org | 188 +++++++++++++++++++++++++++++++++++++++++ priv/edoc.css | 130 ++++++++++++++++++++++++++++ rebar.config | 2 + src/glc.erl | 70 ++++++++++++--- src/glc_code.erl | 28 +++++- src/glc_lib.erl | 29 +++++-- src/glc_ops.erl | 13 ++- src/gr_counter.erl | 2 +- src/gr_manager.erl | 21 ++++- src/gr_manager_sup.erl | 7 +- src/gr_param.erl | 2 +- src/gr_param_sup.erl | 8 +- src/gr_sup.erl | 12 ++- 15 files changed, 486 insertions(+), 28 deletions(-) delete mode 100644 README.md create mode 100644 README.org create mode 100644 priv/edoc.css diff --git a/Makefile b/Makefile index 7be9a3e..bd1a7ca 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ ct: build-plt: @$(DIALYZER) --build_plt --output_plt .$(APPNAME)_dialyzer.plt \ - --apps kernel stdlib sasl inets crypto public_key ssl + --apps kernel stdlib sasl inets crypto public_key ssl compiler syntax_tools dialyze: @$(DIALYZER) --src src --plt .$(APPNAME)_dialyzer.plt --no_native \ diff --git a/README.md b/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/README.org b/README.org new file mode 100644 index 0000000..9511729 --- /dev/null +++ b/README.org @@ -0,0 +1,188 @@ +# Goldrush # + +Goldrush is a small Erlang app that provides fast event stream processing + +# Features # +* Event processing compiled to a query module + - per module protected event processing statistics + - query module logic can be combined for any/all filters + - query module logic can be reduced to efficiently match event processing + +* Complex event processing logic + - match input events with greater than (gt) logic + - match input events with less than (lt) logic + - match input events with equal to (eq) logic + - match input events with wildcard (wc) logic + - match input events with notfound (nf) logic + - match no input events (null blackhole) logic + - match all input events (null passthrough) logic + +* Handle output events + - Once a query has been composed the output action can be overriden + with an erlang function. The function will be applied to each + output event from the query. + +# Usage # + To use goldrush in your application, you need to define it as a rebar dep or + include it in erlang's path. + + +Before composing modules, you'll need to define a query. The query syntax is +matches any number of `{erlang, terms}' and is composed as follows: + +* Simple Logic + - Simple logic is defined as any logic matching a single event filter + +Select all events where 'a' exists and is greater than 0. +#+BEGIN_EXAMPLE + glc:gt(a, 0). +#+END_EXAMPLE + +Select all events where 'a' exists and is equal to 0. +#+BEGIN_EXAMPLE + glc:eq(a, 0). +#+END_EXAMPLE + +Select all events where 'a' exists and is less than 0. +#+BEGIN_EXAMPLE + glc:lt(a, 0). +#+END_EXAMPLE + +Select all events where 'a' exists. +#+BEGIN_EXAMPLE + glc:wc(a). +#+END_EXAMPLE + +Select all events where 'a' does not exist. +#+BEGIN_EXAMPLE + glc:nf(a, 0). +#+END_EXAMPLE + +Select no input events. User as a black hole query. +#+BEGIN_EXAMPLE + glc:null(false). +#+END_EXAMPLE + +Select all input events. Used as a passthrough query. +#+BEGIN_EXAMPLE + glc:null(true). +#+END_EXAMPLE + + +* Combined Logic + - Combined logic is defined as logic matching multiple event filters + +Select all events where both 'a' `and' 'b' exists and are greater than 0. +#+BEGIN_EXAMPLE + glc:all([glc:gt(a, 0), glc:gt(b, 0)]). +#+END_EXAMPLE + +Select all events where 'a' `or' 'b' exists and are greater than 0. +#+BEGIN_EXAMPLE + glc:any([glc:gt(a, 0), glc:gt(b, 0)]). +#+END_EXAMPLE + +Select all events where 'a' `and' 'b' exists where 'a' is greater than 1 and 'b' is less than 2. +#+BEGIN_EXAMPLE + glc:all([glc:gt(a, 1), glc:lt(b, 2)]). +#+END_EXAMPLE + +Select all events where 'a' `or' 'b' exists where 'a' is greater than 1 and 'b' is less than 2. +#+BEGIN_EXAMPLE + glc:any([glc:gt(a, 1), glc:lt(b, 2)]). +#+END_EXAMPLE + + +* Reduced Logic + - Reduced logic is defined as logic which can be simplified to improve efficiency. + +Select all events where 'a' is equal to 1, 'b' is equal to 2 and 'c' is equal to 3 and collapse any duplicate logic. +#+BEGIN_EXAMPLE + glc_lib:reduce( + glc:all([ + glc:any([glc:eq(a, 1), glc:eq(b, 2)]), + glc:any([glc:eq(a, 1), glc:eq(c, 3)])])). +#+END_EXAMPLE + +The previous example will produce and is equivalent to: +#+BEGIN_EXAMPLE + glc:all([glc:eq(a, 1), glc:eq(b, 2), glc:eq(c, 3)]). +#+END_EXAMPLE + + + +# Composing Modules # + +To compose a module you will take your Query defined above and compile it. +#+BEGIN_EXAMPLE + glc:compile(Module, Query). +#+END_EXAMPLE + + +# Handling Events # + +At this point you will be able to handle an event using a compiled query. + +Begin by constructing an event list. +#+BEGIN_EXAMPLE + Event = gre:make([{'a', 2}], [list]). +#+END_EXAMPLE + +Now pass it to your query module to be handled. +#+BEGIN_EXAMPLE + glc:handle(Module, Event). +#+END_EXAMPLE + +* Handling output events + - You can override the output action with an erlang function. + +Write all input events as info reports to the error logger. +#+BEGIN_EXAMPLE + glc:with(glc:null(true), fun(E) -> + error_logger:info_report(gre:pairs(E)) end). +#+END_EXAMPLE + +Write all input events where `error_level' exists and is less than 5 as info reports to the error logger. +#+BEGIN_EXAMPLE + glc:with(glc:lt(error_level, 5), fun(E) -> + error_logger:info_report(gre:pairs(E)) end). +#+END_EXAMPLE + + +# Event Processing Statistics # + +Return the number of input events for this query module. +#+BEGIN_EXAMPLE +glc:input(Module). +#+END_EXAMPLE + +Return the number of output events for this query module. +#+BEGIN_EXAMPLE +glc:output(Module). +#+END_EXAMPLE + +Return the number of filtered events for this query module. +#+BEGIN_EXAMPLE +glc:filter(Module). +#+END_EXAMPLE + + +## How to build ## + + `$ ./rebar compile` + +or + + `$ make` + +## CHANGELOG ## + +### 0.1.6 ### +- Add notfound event matching + +### 0.1.5 ### +- Rewrite to make highly crash resilient + - per module supervision + - statistics data recovery +- Add wildcard event matching +- Add reset counters diff --git a/priv/edoc.css b/priv/edoc.css new file mode 100644 index 0000000..1d50def --- /dev/null +++ b/priv/edoc.css @@ -0,0 +1,130 @@ +/* Baseline rhythm */ +body { + font-size: 16px; + font-family: Helvetica, sans-serif; + margin: 8px; +} + +p { + font-size: 1em; /* 16px */ + line-height: 1.5em; /* 24px */ + margin: 0 0 1.5em 0; +} + +h1 { + font-size: 1.5em; /* 24px */ + line-height: 1em; /* 24px */ + margin-top: 1em; + margin-bottom: 0em; +} + +h2 { + font-size: 1.375em; /* 22px */ + line-height: 1.0909em; /* 24px */ + margin-top: 1.0909em; + margin-bottom: 0em; +} + +h3 { + font-size: 1.25em; /* 20px */ + line-height: 1.2em; /* 24px */ + margin-top: 1.2em; + margin-bottom: 0em; +} + +h4 { + font-size: 1.125em; /* 18px */ + line-height: 1.3333em; /* 24px */ + margin-top: 1.3333em; + margin-bottom: 0em; +} + +.class-for-16px { + font-size: 1em; /* 16px */ + line-height: 1.5em; /* 24px */ + margin-top: 1.5em; + margin-bottom: 0em; +} + +.class-for-14px { + font-size: 0.875em; /* 14px */ + line-height: 1.7143em; /* 24px */ + margin-top: 1.7143em; + margin-bottom: 0em; +} + +ul { + margin: 0 0 1.5em 0; +} + +/* Customizations */ +body { + color: #333; +} + +tt, code, pre { + font-family: "Andale Mono", "Inconsolata", "Monaco", "DejaVu Sans Mono", monospaced; +} + +tt, code { font-size: 0.875em } + +pre { + font-size: 0.875em; /* 14px */ + line-height: 1.7143em; /* 24px */ + margin: 0 1em 1.7143em; + padding: 0 1em; + background: #eee; +} + +.navbar img, hr { display: none } + +table { + border-collapse: collapse; +} + +h1 { + border-left: 0.5em solid #fa0; + padding-left: 0.5em; +} + +h2.indextitle { + font-size: 1.25em; /* 20px */ + line-height: 1.2em; /* 24px */ + margin: -8px -8px 0.6em; + background-color: #fa0; + color: white; + padding: 0.3em; +} + +ul.index { + list-style: none; + margin-left: 0em; + padding-left: 0; +} + +ul.index li { + display: inline; + padding-right: 0.75em +} + +div.spec p { + margin-bottom: 0; + padding-left: 1.25em; + background-color: #eee; +} + +h3.function { + border-left: 0.5em solid #fa0; + padding-left: 0.5em; + background: #fc9; +} +a, a:visited, a:hover, a:active { color: #C60 } +h2 a, h3 a { color: #333 } + +i { + font-size: 0.875em; /* 14px */ + line-height: 1.7143em; /* 24px */ + margin-top: 1.7143em; + margin-bottom: 0em; + font-style: normal; +} diff --git a/rebar.config b/rebar.config index 3faf52a..370a078 100644 --- a/rebar.config +++ b/rebar.config @@ -4,3 +4,5 @@ %% warn_missing_spec, warn_export_all ]}. +{edoc_opts, [{stylesheet_file, "./priv/edoc.css"}]}. + diff --git a/src/glc.erl b/src/glc.erl index 456113f..bf82333 100644 --- a/src/glc.erl +++ b/src/glc.erl @@ -44,7 +44,7 @@ %% %% Select all events where both 'a' and 'b' exists and are greater than 0. %% glc:all([glc:gt(a, 0), glc:gt(b, 0)]). %% %% Select all events where 'a' or 'b' exists and are greater than 0. -%% glc:any([glc:get(a, 0), glc:gt(b, 0)]). +%% glc:any([glc:gt(a, 0), glc:gt(b, 0)]). %% ''' %% %% === Handling output events === @@ -74,7 +74,8 @@ lt/2, eq/2, gt/2, - wc/1 + wc/1, + nf/1 ]). -export([ @@ -85,6 +86,9 @@ ]). -export([ + input/1, + output/1, + filter/1, union/1 ]). @@ -110,6 +114,10 @@ gt(Key, Term) -> wc(Key) -> glc_ops:wc(Key). +-spec nf(atom()) -> glc_ops:op(). +nf(Key) -> + glc_ops:nf(Key). + %% @doc Filter the input using multiple filters. %% %% For an input to be considered valid output the all filters specified @@ -192,6 +200,22 @@ compile(Module, Query, Reset) -> handle(Module, Event) -> Module:handle(Event). +%% @doc The number of input events for this query module. +-spec input(atom()) -> non_neg_integer(). +input(Module) -> + Module:info(input). + +%% @doc The number of output events for this query module. +-spec output(atom()) -> non_neg_integer(). +output(Module) -> + Module:info(output). + +%% @doc The number of filtered events for this query module. +-spec filter(atom()) -> non_neg_integer(). +filter(Module) -> + Module:info(filter). + + %% @doc Release a compiled query. %% %% This releases all resources allocated by a compiled query. The query name @@ -392,9 +416,35 @@ events_test_() -> ?assertEqual(1, Mod:info(output)) end }, + {"opfilter wildcard test", + fun() -> + {compiled, Mod} = setup_query(testmod8, glc:wc(a)), + glc:handle(Mod, gre:make([{b, 2}], [list])), + ?assertEqual(1, Mod:info(input)), + ?assertEqual(1, Mod:info(filter)), + ?assertEqual(0, Mod:info(output)), + glc:handle(Mod, gre:make([{a, 2}], [list])), + ?assertEqual(2, Mod:info(input)), + ?assertEqual(1, Mod:info(filter)), + ?assertEqual(1, Mod:info(output)) + end + }, + {"opfilter notfound test", + fun() -> + {compiled, Mod} = setup_query(testmod9, glc:nf(a)), + glc:handle(Mod, gre:make([{a, 2}], [list])), + ?assertEqual(1, Mod:info(input)), + ?assertEqual(1, Mod:info(filter)), + ?assertEqual(0, Mod:info(output)), + glc:handle(Mod, gre:make([{b, 2}], [list])), + ?assertEqual(2, Mod:info(input)), + ?assertEqual(1, Mod:info(filter)), + ?assertEqual(1, Mod:info(output)) + end + }, {"opfilter greater than test", fun() -> - {compiled, Mod} = setup_query(testmod8, glc:gt(a, 1)), + {compiled, Mod} = setup_query(testmod10, glc:gt(a, 1)), glc:handle(Mod, gre:make([{'a', 2}], [list])), ?assertEqual(1, Mod:info(input)), ?assertEqual(0, Mod:info(filter)), @@ -406,7 +456,7 @@ events_test_() -> }, {"opfilter less than test", fun() -> - {compiled, Mod} = setup_query(testmod9, glc:lt(a, 1)), + {compiled, Mod} = setup_query(testmod11, glc:lt(a, 1)), glc:handle(Mod, gre:make([{'a', 0}], [list])), ?assertEqual(1, Mod:info(input)), ?assertEqual(0, Mod:info(filter)), @@ -419,7 +469,7 @@ events_test_() -> }, {"allholds op test", fun() -> - {compiled, Mod} = setup_query(testmod10, + {compiled, Mod} = setup_query(testmod12, glc:all([glc:eq(a, 1), glc:eq(b, 2)])), glc:handle(Mod, gre:make([{'a', 1}], [list])), glc:handle(Mod, gre:make([{'a', 2}], [list])), @@ -437,7 +487,7 @@ events_test_() -> }, {"anyholds op test", fun() -> - {compiled, Mod} = setup_query(testmod11, + {compiled, Mod} = setup_query(testmod13, glc:any([glc:eq(a, 1), glc:eq(b, 2)])), glc:handle(Mod, gre:make([{'a', 2}], [list])), glc:handle(Mod, gre:make([{'b', 1}], [list])), @@ -452,7 +502,7 @@ events_test_() -> {"with function test", fun() -> Self = self(), - {compiled, Mod} = setup_query(testmod12, + {compiled, Mod} = setup_query(testmod14, glc:with(glc:eq(a, 1), fun(Event) -> Self ! gre:fetch(a, Event) end)), glc:handle(Mod, gre:make([{a,1}], [list])), ?assertEqual(1, Mod:info(output)), @@ -461,7 +511,7 @@ events_test_() -> }, {"delete test", fun() -> - {compiled, Mod} = setup_query(testmod13, glc:null(false)), + {compiled, Mod} = setup_query(testmod15, glc:null(false)), ?assert(is_atom(Mod:table(params))), ?assertMatch([_|_], gr_param:info(Mod:table(params))), ?assert(is_list(code:which(Mod))), @@ -481,7 +531,7 @@ events_test_() -> }, {"reset counters test", fun() -> - {compiled, Mod} = setup_query(testmod14, + {compiled, Mod} = setup_query(testmod16, glc:any([glc:eq(a, 1), glc:eq(b, 2)])), glc:handle(Mod, gre:make([{'a', 2}], [list])), glc:handle(Mod, gre:make([{'b', 1}], [list])), @@ -510,7 +560,7 @@ events_test_() -> {"ets data recovery test", fun() -> Self = self(), - {compiled, Mod} = setup_query(testmod15, + {compiled, Mod} = setup_query(testmod17, glc:with(glc:eq(a, 1), fun(Event) -> Self ! gre:fetch(a, Event) end)), glc:handle(Mod, gre:make([{a,1}], [list])), ?assertEqual(1, Mod:info(output)), diff --git a/src/glc_code.erl b/src/glc_code.erl index be75b9f..e107237 100644 --- a/src/glc_code.erl +++ b/src/glc_code.erl @@ -1,5 +1,24 @@ %% @doc Code generation functions. -module(glc_code). +-compile({nowarn_unused_function, {abstract_module,2}}). +-compile({nowarn_unused_function, {abstract_tables,1}}). +-compile({nowarn_unused_function, {abstract_reset,0}}). +-compile({nowarn_unused_function, {abstract_filter,2}}). +-compile({nowarn_unused_function, {abstract_filter_,4}}). +-compile({nowarn_unused_function, {abstract_opfilter,6}}). +-compile({nowarn_unused_function, {abstract_all,4}}). +-compile({nowarn_unused_function, {abstract_any,4}}). +-compile({nowarn_unused_function, {abstract_with,2}}). +-compile({nowarn_unused_function, {abstract_getkey,4}}). +-compile({nowarn_unused_function, {abstract_getkey_,4}}). +-compile({nowarn_unused_function, {abstract_getparam,3}}). +-compile({nowarn_unused_function, {abstract_getparam_,3}}). +-compile({nowarn_unused_function, {param_variable,1}}). +-compile({nowarn_unused_function, {field_variable,1}}). +-compile({nowarn_unused_function, {field_variable_,1}}). +-compile({nowarn_unused_function, {compile_forms,2}}). +-compile({nowarn_unused_function, {load_binary,2}}). + -export([ compile/2 @@ -165,7 +184,7 @@ abstract_filter(Cond, State) -> %% @private Return a list of expressions to apply a filter. %% A filter expects two continuation functions which generates the expressions %% to apply when the filter matches or fails to match. The state passed to the -%% functions will be contain all variable bindings to previously accessed +%% functions will contain all the variable bindings of previously accessed %% fields and parameters. -spec abstract_filter_(glc_ops:op(), nextFun(), nextFun(), #state{}) -> syntaxTree(). @@ -177,6 +196,11 @@ abstract_filter_({Key, '*'}, OnMatch, OnNomatch, State) -> abstract_getkey(Key, _OnMatch=fun(#state{}=State2) -> OnMatch(State2) end, _OnNomatch=fun(State2) -> OnNomatch(State2) end, State); +abstract_filter_({Key, '!'}, OnMatch, OnNomatch, State) -> + abstract_getkey(Key, + _OnNomatch=fun(State2) -> OnNomatch(State2) end, + _OnMatch=fun(#state{}=State2) -> OnMatch(State2) end, + State); abstract_filter_({Key, Op, Value}, OnMatch, OnNomatch, State) when Op =:= '>'; Op =:= '='; Op =:= '<' -> Op2 = case Op of '=' -> '=:='; Op -> Op end, @@ -353,7 +377,7 @@ abstract_getcount(Counter) -> ?erl:abstract(Counter)])]. %% @private Return an expression to reset a counter. --spec abstract_resetcount(atom()) -> [syntaxTree()]. +-spec abstract_resetcount(atom() | [filter | input | output]) -> [syntaxTree()]. abstract_resetcount(Counter) -> [abstract_apply(gr_counter, reset_counters, [abstract_apply(table, [?erl:atom(counters)]), diff --git a/src/glc_lib.erl b/src/glc_lib.erl index 427551f..b466f2b 100644 --- a/src/glc_lib.erl +++ b/src/glc_lib.erl @@ -74,7 +74,9 @@ matches({Key, '*'}, Event) -> case gre:find(Key, Event) of {true, _} -> true; false -> false - end. + end; +matches({Key, '!'}, Event) -> + not matches({Key, '*'}, Event). %% @private Repeatedly apply a function to a query. %% This is used for query transformation functions that must be applied @@ -87,6 +89,7 @@ repeat(Query, Fun) -> %% @doc Return the output action of a query. +-spec onoutput(glc_ops:op()) -> output | no_return(). onoutput({_, '<', _}) -> output; onoutput({_, '=', _}) -> @@ -95,10 +98,13 @@ onoutput({_, '>', _}) -> output; onoutput({_, '*'}) -> output; +onoutput({_, '!'}) -> + output; onoutput(Query) -> erlang:error(badarg, [Query]). %% @doc Modify the output action of a query. +-spec onoutput(Action :: any(), Query :: glc_ops:op()) -> no_return(). onoutput(Action, Query) -> erlang:error(badarg, [Action, Query]). @@ -238,6 +244,7 @@ deleteall(Filter, []) -> %% @private Test if a term is a valid filter. +-spec is_valid(glc_ops:op()) -> boolean(). is_valid({Field, '<', _Term}) when is_atom(Field) -> true; is_valid({Field, '=', _Term}) when is_atom(Field) -> @@ -246,6 +253,8 @@ is_valid({Field, '>', _Term}) when is_atom(Field) -> true; is_valid({Field, '*'}) when is_atom(Field) -> true; +is_valid({Field, '!'}) when is_atom(Field) -> + true; is_valid({null, true}) -> true; is_valid({null, false}) -> @@ -256,6 +265,7 @@ is_valid(_Other) -> %% @private Assert that a term is a valid filter. %% If the term is a valid filter. The original term will be returned. %% If the term is not a valid filter. A `badarg' error is thrown. +-spec valid(glc_ops:op()) -> boolean() | no_return(). valid(Term) -> is_valid(Term) orelse erlang:error(badarg, [Term]), Term. @@ -279,6 +289,13 @@ any_one_test() -> glc_lib:reduce(glc:any([glc:eq(a, 1)])) ). +all_two_test() -> + ?assertEqual(glc_lib:reduce(glc:all([glc:wc(a), glc:nf(b)])), + glc_lib:reduce(glc:any([ + glc:all([glc:wc(a)]), + glc:all([glc:wc(a), glc:nf(b)])])) + ). + any_sort_test() -> ?assertEqual(glc:any([glc:eq(a, 1), glc:eq(b, 2)]), glc_lib:reduce(glc:any([glc:eq(b, 2), glc:eq(a, 1)])) @@ -317,11 +334,12 @@ any_equiv_test() -> any_required_test() -> ?assertEqual( glc:all([ - glc:any([glc:eq(b, 2), glc:eq(c, 3)]), + glc:any([glc:nf(d), glc:eq(b, 2), glc:eq(c, 3)]), glc:eq(a, 1) ]), glc_lib:reduce( glc:any([ + glc:all([glc:eq(a, 1), glc:nf(d)]), glc:all([glc:eq(a, 1), glc:eq(b, 2)]), glc:all([glc:eq(a, 1), glc:eq(c, 3)])])) ). @@ -340,21 +358,22 @@ delete_from_all_test() -> ?assertEqual( glc:all([glc:eq(b,2)]), deleteall( - glc:all([glc:eq(a, 1),glc:eq(b,2)]), [glc:eq(a, 1)]) + glc:all([glc:eq(a, 1),glc:eq(b,2)]), [glc:eq(a, 1), glc:nf(a)]) ). delete_from_any_test() -> ?assertEqual( glc:any([glc:eq(b,2)]), deleteall( - glc:any([glc:eq(a, 1),glc:eq(b,2)]), [glc:eq(a, 1)]) + glc:any([glc:eq(a, 1),glc:eq(b,2)]), [glc:eq(a, 1), glc:wc(a)]) ). default_is_output_test_() -> [?_assertEqual(output, glc_lib:onoutput(glc:lt(a, 1))), ?_assertEqual(output, glc_lib:onoutput(glc:eq(a, 1))), ?_assertEqual(output, glc_lib:onoutput(glc:gt(a, 1))), - ?_assertEqual(output, glc_lib:onoutput(glc:wc(a))) + ?_assertEqual(output, glc_lib:onoutput(glc:wc(a))), + ?_assertEqual(output, glc_lib:onoutput(glc:nf(a))) ]. -ifdef(PROPER). diff --git a/src/glc_ops.erl b/src/glc_ops.erl index 05067c4..c7208f8 100644 --- a/src/glc_ops.erl +++ b/src/glc_ops.erl @@ -5,7 +5,8 @@ lt/2, eq/2, gt/2, - wc/1 + wc/1, + nf/1 ]). -export([ @@ -24,6 +25,7 @@ {atom(), '=', term()} | {atom(), '>', term()} | {atom(), '*'} | + {atom(), '!'} | {any, [op(), ...]} | {all, [op(), ...]} | {null, true|false}. @@ -51,13 +53,20 @@ gt(Key, Term) when is_atom(Key) -> gt(Key, Term) -> erlang:error(badarg, [Key, Term]). -%% @doc Test that a field value is exists. +%% @doc Test that a field exists. -spec wc(atom()) -> op(). wc(Key) when is_atom(Key) -> {Key, '*'}; wc(Key) -> erlang:error(badarg, [Key]). +%% @doc Test that a field is not found. +-spec nf(atom()) -> op(). +nf(Key) when is_atom(Key) -> + {Key, '!'}; +nf(Key) -> + erlang:error(badarg, [Key]). + %% @doc Filter the input using multiple filters. %% %% For an input to be considered valid output the all filters specified diff --git a/src/gr_counter.erl b/src/gr_counter.erl index 60662b9..b8da06a 100644 --- a/src/gr_counter.erl +++ b/src/gr_counter.erl @@ -75,7 +75,7 @@ reset_counters(Server, Counter) -> %% @doc %% Starts the server %% -%% @spec start_link() -> {ok, Pid} | ignore | {error, Error} +%% @spec start_link(Name) -> {ok, Pid} | ignore | {error, Error} %% @end %%-------------------------------------------------------------------- start_link(Name) -> diff --git a/src/gr_manager.erl b/src/gr_manager.erl index 93bf735..113e24b 100644 --- a/src/gr_manager.erl +++ b/src/gr_manager.erl @@ -12,8 +12,17 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. --module(gr_manager). +%% @doc Process table manager for goldrush. +%% +%% Manager responsible for the processes, which serve as heir of the +%% {@link gr_counter:start_link/0. Counter} and +%% {@link gr_param:start_link/0. Param} ets table processes. +%% This process creates the table and initial data then assigns itself +%% to inherit the ets table if any process responsible for it is killed. +%% It then waits to give it back while that process is recreated by its +%% supervisor. +-module(gr_manager). -behaviour(gen_server). %% API @@ -29,12 +38,14 @@ -define(SERVER, ?MODULE). --record(state, {table_id :: ets:tid(), managee :: atom()}). +-record(state, {table_id :: ets:tab(), managee :: atom()}). %%%=================================================================== %%% API %%%=================================================================== +%% Setup the initial data for the ets table +-spec setup(atom() | pid(), term()) -> ok. setup(Name, Data) -> gen_server:cast(Name, {setup, Data}). @@ -42,7 +53,8 @@ setup(Name, Data) -> %% @doc %% Starts the server %% -%% @spec start_link() -> {ok, Pid} | ignore | {error, Error} +%% @spec start_link(Name, Managee, Data) -> {ok, Pid} | ignore | +%% {error, Error} %% @end %%-------------------------------------------------------------------- start_link(Name, Managee, Data) -> @@ -125,6 +137,9 @@ handle_info({'ETS-TRANSFER', TableId, _Pid, Data}, State = #state{managee=Manage ets:give_away(TableId, ManageePid, Data), {noreply, State#state{table_id=TableId}}. +%% @doc Wait for a registered process to be associated to a process identifier. +%% @spec wait_for_pid(Managee) -> ManageePid +-spec wait_for_pid(atom()) -> pid(). wait_for_pid(Managee) when is_atom(Managee), Managee =/= undefined -> case whereis(Managee) of undefined -> diff --git a/src/gr_manager_sup.erl b/src/gr_manager_sup.erl index 49c21cd..30e6bba 100644 --- a/src/gr_manager_sup.erl +++ b/src/gr_manager_sup.erl @@ -12,8 +12,13 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @doc Table manager supervisor for all goldrush ets process tables. +%% +%% Manager supervisor responsible for the {@link gr_manager:start_link/3. +%% Manager} processes, which serve as heir of the +%% {@link gr_counter:start_link/0. Counter} and +%% {@link gr_param:start_link/0. Param} ets table processes. -module(gr_manager_sup). - -behaviour(supervisor). -type startlink_err() :: {'already_started', pid()} | 'shutdown' | term(). diff --git a/src/gr_param.erl b/src/gr_param.erl index 6fc9ec4..125e7e3 100644 --- a/src/gr_param.erl +++ b/src/gr_param.erl @@ -89,7 +89,7 @@ transform(Server) -> %% @doc %% Starts the server %% -%% @spec start_link() -> {ok, Pid} | ignore | {error, Error} +%% @spec start_link(Name) -> {ok, Pid} | ignore | {error, Error} %% @end %%-------------------------------------------------------------------- start_link(Name) -> diff --git a/src/gr_param_sup.erl b/src/gr_param_sup.erl index 25d240f..5a2e925 100644 --- a/src/gr_param_sup.erl +++ b/src/gr_param_sup.erl @@ -12,8 +12,14 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. --module(gr_param_sup). +%% @doc Second level supervisor for goldrush. +%% +%% Supervisor for the {@link gr_param:start_link/0. +%% Param}, process table responsible for params +%% {@link gr_param:start_link/0. Counter} and +%% their {@link gr_counter:start_link/0. Manager} supervisors. +-module(gr_param_sup). -behaviour(supervisor). -type startlink_err() :: {'already_started', pid()} | 'shutdown' | term(). diff --git a/src/gr_sup.erl b/src/gr_sup.erl index dab4d7c..4fd6056 100644 --- a/src/gr_sup.erl +++ b/src/gr_sup.erl @@ -13,20 +13,30 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + %% @doc Top level supervisor for goldrush. +%% +%% Main supervisor responsible for the {@link gr_counter_sup:start_link/0. +%% Counter}, {@link gr_param_sup:start_link/0. Param} and +%% their {@link gr_manager_sup:start_link/0. Manager} supervisors. -module(gr_sup). -behaviour(supervisor). +-type startlink_err() :: {'already_started', pid()} | 'shutdown' | term(). +-type startlink_ret() :: {'ok', pid()} | 'ignore' | {'error', startlink_err()}. + -export([start_link/0]). -export([init/1]). -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). +-spec start_link() -> startlink_ret(). start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). +-spec init([]) -> {ok, { {one_for_one, 50, 10}, [supervisor:child_spec()]} }. init([]) -> CounterSup = ?CHILD(gr_counter_sup, supervisor), ParamSup = ?CHILD(gr_param_sup, supervisor), MgrSup = ?CHILD(gr_manager_sup, supervisor), - {ok, {{one_for_one, 5, 10}, [CounterSup, ParamSup, MgrSup]}}. + {ok, {{one_for_one, 50, 10}, [CounterSup, ParamSup, MgrSup]}}.